Module: KairosMcp::Daemon::PidLock
- Defined in:
- lib/kairos_mcp/daemon/pid_lock.rb
Overview
PID lock using flock(LOCK_EX|LOCK_NB).
Design rationale (see design v0.2 §3.1):
-
flock is atomic and OS-managed; no stale PID file detection required.
-
If the daemon process dies, the OS releases the lock automatically.
-
On acquisition we write our PID for operational visibility, but the lock itself — not the file contents — is the source of truth.
Defined Under Namespace
Classes: AlreadyLocked
Class Method Summary collapse
-
.acquire!(path) ⇒ File
Acquire an exclusive, non-blocking flock on ‘path`.
-
.read_pid(file) ⇒ Object
Read the pid written in the (already-opened) file, if any.
-
.read_pid_from(path) ⇒ Object
Read the pid directly from disk (used when we don’t hold the file).
-
.release(file, path) ⇒ Object
Release and remove the pid file.
Class Method Details
.acquire!(path) ⇒ File
Acquire an exclusive, non-blocking flock on ‘path`. Returns the opened File handle (caller must keep a reference for the duration of the lock — GC-closing the file releases the lock).
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/kairos_mcp/daemon/pid_lock.rb', line 33 def self.acquire!(path) FileUtils.mkdir_p(File.dirname(path)) # Open rw + create. We deliberately do NOT truncate before the flock, # so a failing acquisition does not wipe the holder's pid. file = File.open(path, File::RDWR | File::CREAT, 0o644) unless file.flock(File::LOCK_EX | File::LOCK_NB) holder = read_pid(file) file.close raise AlreadyLocked.new(path, holder) end # We own the lock — rewrite pid file file.truncate(0) file.rewind file.write("#{Process.pid}\n") file.flush file end |
.read_pid(file) ⇒ Object
Read the pid written in the (already-opened) file, if any. Used only for diagnostics in AlreadyLocked.
83 84 85 86 87 88 89 90 |
# File 'lib/kairos_mcp/daemon/pid_lock.rb', line 83 def self.read_pid(file) file.rewind contents = file.read pid = contents.to_s.strip.to_i pid.positive? ? pid : nil rescue StandardError nil end |
.read_pid_from(path) ⇒ Object
Read the pid directly from disk (used when we don’t hold the file).
93 94 95 96 97 98 99 100 |
# File 'lib/kairos_mcp/daemon/pid_lock.rb', line 93 def self.read_pid_from(path) return nil unless File.exist?(path) pid = File.read(path).to_s.strip.to_i pid.positive? ? pid : nil rescue StandardError nil end |
.release(file, path) ⇒ Object
Release and remove the pid file. CF-1 fix: delete WHILE holding the lock to prevent unlink race. Safe to call with nil or an already-closed file.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/kairos_mcp/daemon/pid_lock.rb', line 57 def self.release(file, path) return if file.nil? begin # Delete while still holding the lock — prevents a replacement # daemon from acquiring the same path before we unlink it. File.delete(path) if File.exist?(path) rescue Errno::ENOENT # Already gone — fine. end begin file.flock(File::LOCK_UN) rescue StandardError # Already unlocked or closed — ignore end begin file.close unless file.closed? rescue StandardError # ignore end end |