Class: Space::Src::State::Lock
- Inherits:
-
Object
- Object
- Space::Src::State::Lock
- Defined in:
- lib/space_src/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/space_src/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/space_src/state/lock.rb', line 25 def self.path_for(state_file) "#{state_file}.lock" end |