Class: Philiprehberger::LockKit::FileLock
- Inherits:
-
Object
- Object
- Philiprehberger::LockKit::FileLock
- Defined in:
- lib/philiprehberger/lock_kit/file_lock.rb
Overview
Exclusive file lock using flock(2)
Provides process-level mutual exclusion via the filesystem. The lock is advisory and relies on all participants using the same lock file path.
Instance Method Summary collapse
-
#acquire(timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil) ⇒ true
Acquire an exclusive lock on the file.
-
#expired? ⇒ Boolean
Check whether the lock has expired based on its TTL.
-
#initialize(path) ⇒ FileLock
constructor
A new instance of FileLock.
-
#locked? ⇒ Boolean
Check whether the file is currently locked by another process.
-
#owner ⇒ Hash?
Read lock owner metadata.
-
#release ⇒ void
Release the lock and close the file handle.
Constructor Details
#initialize(path) ⇒ FileLock
Returns a new instance of FileLock.
14 15 16 17 18 |
# File 'lib/philiprehberger/lock_kit/file_lock.rb', line 14 def initialize(path) @path = path @meta_path = "#{path}.meta" @file = nil end |
Instance Method Details
#acquire(timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil) ⇒ true
Acquire an exclusive lock on the file
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/philiprehberger/lock_kit/file_lock.rb', line 28 def acquire(timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil) @ttl = ttl if auto_cleanup cleanup_stale_lock end @file = File.open(@path, File::CREAT | File::RDWR) if timeout.nil? unless @file.flock(File::LOCK_EX | File::LOCK_NB) close_file raise Error, "Could not acquire lock on #{@path}" end else deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) last_callback = start_time until @file.flock(File::LOCK_EX | File::LOCK_NB) now = Process.clock_gettime(Process::CLOCK_MONOTONIC) remaining = deadline - now if remaining <= 0 close_file raise Error, "Timeout acquiring lock on #{@path} after #{timeout}s" end if on_wait && (now - last_callback) >= 0.5 elapsed = (now - start_time).round(1) on_wait.call(elapsed) last_callback = now end sleep [0.05, remaining].min end end true end |
#expired? ⇒ Boolean
Check whether the lock has expired based on its TTL
107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/philiprehberger/lock_kit/file_lock.rb', line 107 def expired? return false unless File.exist?(@meta_path) content = File.read(@meta_path).strip return false if content.empty? data = JSON.parse(content) expires_at = data['expires_at'] return false unless expires_at Time.parse(expires_at) <= Time.now rescue JSON::ParserError, Errno::ENOENT false end |
#locked? ⇒ Boolean
Check whether the file is currently locked by another process
Opens the file, attempts a non-blocking exclusive lock, and immediately releases it. Returns true if the lock attempt fails (file is locked). Returns false if the lock has expired (TTL elapsed).
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/philiprehberger/lock_kit/file_lock.rb', line 88 def locked? return false unless File.exist?(@path) return false if expired? f = File.open(@path, File::CREAT | File::RDWR) got_lock = f.flock(File::LOCK_EX | File::LOCK_NB) if got_lock f.flock(File::LOCK_UN) f.close false else f.close true end end |
#owner ⇒ Hash?
Read lock owner metadata
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/philiprehberger/lock_kit/file_lock.rb', line 125 def owner return nil unless File.exist?(@meta_path) content = File.read(@meta_path).strip return nil if content.empty? data = JSON.parse(content) { pid: data['pid'], hostname: data['hostname'], acquired_at: data['acquired_at'] ? Time.parse(data['acquired_at']) : nil } rescue JSON::ParserError, Errno::ENOENT nil end |
#release ⇒ void
This method returns an undefined value.
Release the lock and close the file handle
73 74 75 76 77 78 79 |
# File 'lib/philiprehberger/lock_kit/file_lock.rb', line 73 def release return unless @file @file.flock(File::LOCK_UN) close_file end |