Class: KnoxCall::CredentialsFile::Lock

Inherits:
Object
  • Object
show all
Defined in:
lib/knoxcall/credentials_file.rb

Overview

Sibling .lock file held by exclusive-create (O_CREAT|O_EXCL).

Protocol (identical in every SDK): retry every 100ms up to 10s; a lock file older than the stale window is broken and retried once.

Ownership-aware break/release: the lock file carries a unique owner tag (+pid time nonce+) written at acquire. A stale lock is broken by ATOMIC RENAME (only one racer wins the rename, so a competitor's freshly-created lock can never be deleted by path), and release only unlinks a lock whose on-disk content still matches what this instance wrote. This closes the double-acquire -> double-refresh race that would replay the single-use refresh token and trip server-side family revocation. The stale window is also kept safely above the bounded refresh HTTP timeout (REFRESH_TIMEOUT_SECONDS) so a live-but-slow refresh is never mistaken for a dead holder.

Constant Summary collapse

STALE_AFTER_SECONDS =

Must exceed the bounded refresh HTTP timeout (REFRESH_TIMEOUT_SECONDS) so a legitimately in-flight refresh is never broken as "stale".

60.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target, timeout: 10.0, retry_interval: 0.1, stale_after: STALE_AFTER_SECONDS) ⇒ Lock

Returns a new instance of Lock.



215
216
217
218
219
220
221
222
# File 'lib/knoxcall/credentials_file.rb', line 215

def initialize(target, timeout: 10.0, retry_interval: 0.1, stale_after: STALE_AFTER_SECONDS)
  @lock_path = "#{target}.lock"
  @timeout = timeout
  @retry_interval = retry_interval
  @stale_after = stale_after
  @held = false
  @own_content = nil
end

Instance Attribute Details

#lock_path ⇒ Object (readonly)

Returns the value of attribute lock_path.



213
214
215
# File 'lib/knoxcall/credentials_file.rb', line 213

def lock_path
  @lock_path
end

Instance Method Details

#acquire ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/knoxcall/credentials_file.rb', line 224

def acquire
  deadline = monotonic_now + @timeout
  stale_broken = false
  loop do
    return if try_acquire
    if !stale_broken && break_stale
      stale_broken = true
      return if try_acquire
    end
    if monotonic_now >= deadline
      raise Error, "timed out waiting for the credentials file lock (#{@lock_path})"
    end
    sleep @retry_interval
  end
end

#release ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/knoxcall/credentials_file.rb', line 240

def release
  return unless @held
  @held = false
  own = @own_content
  @own_content = nil
  begin
    # Only remove the lock if it is still OURS — if our lock was broken as
    # stale and re-taken by another process while we were suspended,
    # unlink by path would delete their live lock.
    if own && File.read(@lock_path) == own
      File.unlink(@lock_path)
    end
  rescue SystemCallError
    # already gone or unreadable
  end
end

#with_lock ⇒ Object



257
258
259
260
261
262
263
264
# File 'lib/knoxcall/credentials_file.rb', line 257

def with_lock
  acquire
  begin
    yield
  ensure
    release
  end
end