Class: RepoTender::State::Lock
- Inherits:
-
Object
- Object
- RepoTender::State::Lock
- Defined in:
- lib/repo_tender/state/lock.rb
Overview
Advisory process-level lock on a sidecar file derived from the state file path. Serializes overlapping ‘sync` runs so the later run bails cleanly rather than clobbering the in-flight run’s state with a last-writer-wins atomic rename (CF10).
The lockfile is a persistent zero-byte sentinel — never unlinked. Deleting a flock’d file while another fd holds it creates a race where both processes think they hold the lock on different inodes.
‘LOCK_NB` is for daemon safety, not reactor yielding: a blocking `LOCK_EX` would wedge a launchd tick forever behind a hung run. `flock` and the file I/O it guards are ordinary blocking syscalls with no Async scheduler hook — fine here, they’re sub-millisecond on a local state dir, same as the rest of ‘State::Store`.
Constant Summary collapse
- NOT_ACQUIRED =
:not_acquired
Class Method Summary collapse
-
.acquire(state_file) ⇒ Object
Acquires an exclusive non-blocking advisory lock on the sidecar lockfile.
-
.path_for(state_file) ⇒ Object
Returns the sidecar lockfile path for a given state_file path.
Class Method Details
.acquire(state_file) ⇒ Object
Acquires an exclusive non-blocking advisory lock on the sidecar lockfile. Creates the file (and its parent directory) if missing.
Yields to the block and returns its value when the lock is acquired; releases the lock in an ‘ensure` so it is freed on normal return, explicit `return`, AND any escaping exception (including Interrupt / SignalException).
Returns ‘NOT_ACQUIRED` without yielding when another process already holds the lock. The caller decides what to do (e.g. bail cleanly with a Success).
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/repo_tender/state/lock.rb', line 40 def self.acquire(state_file) lock_path = path_for(state_file) FileUtils.mkdir_p(File.dirname(lock_path)) f = File.open(lock_path, File::RDWR | File::CREAT) # Everything that can raise — `flock` itself can (EINTR on a # signal, ENOLCK on some filesystems) — runs inside the `begin`, # so the `ensure` always closes the fd (no leak). Closing the fd # releases the lock (it lives on the open file description), so # no explicit LOCK_UN is needed — and `close` can't be skipped # by a raising unlock. begin return NOT_ACQUIRED unless f.flock(File::LOCK_EX | File::LOCK_NB) yield ensure f.close end end |
.path_for(state_file) ⇒ Object
Returns the sidecar lockfile path for a given state_file path.
25 26 27 |
# File 'lib/repo_tender/state/lock.rb', line 25 def self.path_for(state_file) "#{state_file}.lock" end |