philiprehberger-lock_kit 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +42 -7
- data/lib/philiprehberger/lock_kit/file_lock.rb +34 -1
- data/lib/philiprehberger/lock_kit/pid_lock.rb +44 -8
- data/lib/philiprehberger/lock_kit/version.rb +1 -1
- data/lib/philiprehberger/lock_kit.rb +62 -4
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea17573729655a3a771771a4491e4ad67313ccdd8c1910964efc84e28cc55cd1
|
|
4
|
+
data.tar.gz: 3d045b798cd8f8b4df88af8127d4fb1ecd30c0ba252a1058fc90095fc4b98b75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08161efb6474fd3cc5b04d6896200df0e6c3e638e7eb89e898e2de4e0efee4ab2ac7decf99eea5f472a78c2914bbb1d5e4665e4d80a4fd7d72efdb3d4c942505'
|
|
7
|
+
data.tar.gz: 1776595c803f9c9285afc8bb6b7fc9f26cf32db45f1067a5f6084d585f741b5c27d6bce6ab2d1824c2db4941991e44bd1c0ac53e71f845e4011ac079b4f5f4af
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-04-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `with_retry_lock(path, retries: 3, delay: 0.1, backoff: 2, timeout: nil, auto_cleanup: true, ttl: nil)` module method for lock acquisition with exponential backoff retries
|
|
14
|
+
|
|
15
|
+
## [0.3.0] - 2026-04-10
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `ttl:` option for `with_file_lock`, `with_pid_lock`, `FileLock#acquire`, and `PidLock#acquire` — locks auto-expire after the specified duration
|
|
19
|
+
- `FileLock#expired?` and `PidLock#expired?` instance methods for checking TTL expiration
|
|
20
|
+
- `LockKit.expired?(path)` module-level method for checking lock expiration
|
|
21
|
+
- Stale lock cleanup now considers expired TTL locks as stale
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Align feature request issue template with guide structure
|
|
25
|
+
|
|
10
26
|
## [0.2.3] - 2026-04-09
|
|
11
27
|
|
|
12
28
|
### Fixed
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://rubygems.org/gems/philiprehberger-lock_kit)
|
|
5
5
|
[](https://github.com/philiprehberger/rb-lock-kit/commits/main)
|
|
6
6
|
|
|
7
|
-
File-based and PID locking for process coordination with stale
|
|
7
|
+
File-based and PID locking for process coordination with TTL expiration, stale detection, read-write locks, and lock owner identification
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
|
|
@@ -64,6 +64,37 @@ Philiprehberger::LockKit.with_write_lock('/tmp/data.lock', timeout: 5) do
|
|
|
64
64
|
end
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
### Retry Lock
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Retries lock acquisition with exponential backoff
|
|
71
|
+
Philiprehberger::LockKit.with_retry_lock('/tmp/my.lock', retries: 5, delay: 0.2, backoff: 2) do
|
|
72
|
+
# acquired after retrying if initially contended
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# With timeout per attempt, TTL, and custom cleanup
|
|
76
|
+
Philiprehberger::LockKit.with_retry_lock('/tmp/my.lock', retries: 3, delay: 0.1, backoff: 2, timeout: 5, ttl: 30) do
|
|
77
|
+
# each attempt waits up to 5s; lock expires after 30s
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Lock with TTL (Time-to-Live)
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Lock expires after 30 seconds — treated as stale once elapsed
|
|
85
|
+
Philiprehberger::LockKit.with_file_lock('/tmp/my.lock', ttl: 30) do
|
|
86
|
+
# work that should not hold the lock indefinitely
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# PID locks also support TTL
|
|
90
|
+
Philiprehberger::LockKit.with_pid_lock('my_worker', ttl: 60) do
|
|
91
|
+
# expires after 60 seconds
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check if a lock has expired
|
|
95
|
+
Philiprehberger::LockKit.expired?('/tmp/my.lock') # => true/false
|
|
96
|
+
```
|
|
97
|
+
|
|
67
98
|
### Automatic Stale Lock Cleanup
|
|
68
99
|
|
|
69
100
|
```ruby
|
|
@@ -133,12 +164,14 @@ Philiprehberger::LockKit.stale?('/tmp/my_worker.pid') # => true/false
|
|
|
133
164
|
|
|
134
165
|
| Method | Description |
|
|
135
166
|
|--------|-------------|
|
|
136
|
-
| `.with_file_lock(path, timeout: nil, auto_cleanup: false, on_wait: nil) { }` | Execute block with exclusive file lock |
|
|
137
|
-
| `.
|
|
167
|
+
| `.with_file_lock(path, timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil) { }` | Execute block with exclusive file lock |
|
|
168
|
+
| `.with_retry_lock(path, retries: 3, delay: 0.1, backoff: 2, timeout: nil, auto_cleanup: true, ttl: nil) { }` | Execute block with file lock, retrying with exponential backoff |
|
|
169
|
+
| `.with_pid_lock(name, dir: Dir.tmpdir, auto_cleanup: false, ttl: nil) { }` | Execute block with PID file lock |
|
|
138
170
|
| `.with_read_lock(path, timeout: nil) { }` | Execute block with shared read lock |
|
|
139
171
|
| `.with_write_lock(path, timeout: nil) { }` | Execute block with exclusive write lock |
|
|
140
172
|
| `.locked?(path)` | Check if a file is currently locked |
|
|
141
173
|
| `.stale?(pid_file)` | Check if a PID file references a dead process |
|
|
174
|
+
| `.expired?(path)` | Check if a lock has expired based on its TTL |
|
|
142
175
|
| `.owner(path)` | Get lock owner metadata (pid, hostname, acquired_at) |
|
|
143
176
|
| `.break!(path, force: false)` | Break a lock (stale only by default, any with force) |
|
|
144
177
|
|
|
@@ -147,9 +180,10 @@ Philiprehberger::LockKit.stale?('/tmp/my_worker.pid') # => true/false
|
|
|
147
180
|
| Method | Description |
|
|
148
181
|
|--------|-------------|
|
|
149
182
|
| `.new(path)` | Create a file lock instance |
|
|
150
|
-
| `#acquire(timeout: nil, auto_cleanup: false, on_wait: nil)` | Acquire exclusive lock with optional timeout, cleanup,
|
|
183
|
+
| `#acquire(timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil)` | Acquire exclusive lock with optional timeout, cleanup, wait callback, and TTL |
|
|
151
184
|
| `#release` | Release the lock and close the file handle |
|
|
152
|
-
| `#locked?` | Check if the file is currently locked |
|
|
185
|
+
| `#locked?` | Check if the file is currently locked (false if expired) |
|
|
186
|
+
| `#expired?` | Check if the lock has expired based on its TTL |
|
|
153
187
|
| `#owner` | Get lock owner metadata |
|
|
154
188
|
|
|
155
189
|
### `LockKit::PidLock`
|
|
@@ -157,9 +191,10 @@ Philiprehberger::LockKit.stale?('/tmp/my_worker.pid') # => true/false
|
|
|
157
191
|
| Method | Description |
|
|
158
192
|
|--------|-------------|
|
|
159
193
|
| `.new(name, dir: Dir.tmpdir)` | Create a PID lock instance |
|
|
160
|
-
| `#acquire(auto_cleanup: false)` | Acquire PID lock, raises if held by a living process |
|
|
194
|
+
| `#acquire(auto_cleanup: false, ttl: nil)` | Acquire PID lock with optional TTL, raises if held by a living process |
|
|
161
195
|
| `#release` | Release lock and remove PID file |
|
|
162
|
-
| `#locked?` | Check if lock is held by a living process |
|
|
196
|
+
| `#locked?` | Check if lock is held by a living process (false if expired) |
|
|
197
|
+
| `#expired?` | Check if the lock has expired based on its TTL |
|
|
163
198
|
| `#stale?` | Check if PID file references a dead process |
|
|
164
199
|
| `#owner` | Get lock owner metadata |
|
|
165
200
|
|
|
@@ -22,9 +22,12 @@ module Philiprehberger
|
|
|
22
22
|
# @param timeout [Numeric, nil] seconds to wait before raising; nil means non-blocking single attempt
|
|
23
23
|
# @param auto_cleanup [Boolean] when true, check for stale locks and remove them
|
|
24
24
|
# @param on_wait [Proc, nil] callback invoked every 0.5s while waiting; receives elapsed seconds
|
|
25
|
+
# @param ttl [Numeric, nil] time-to-live in seconds; lock expires after this duration
|
|
25
26
|
# @return [true] when the lock is acquired
|
|
26
27
|
# @raise [LockKit::Error] if the lock cannot be acquired
|
|
27
|
-
def acquire(timeout: nil, auto_cleanup: false, on_wait: nil)
|
|
28
|
+
def acquire(timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil)
|
|
29
|
+
@ttl = ttl
|
|
30
|
+
|
|
28
31
|
if auto_cleanup
|
|
29
32
|
cleanup_stale_lock
|
|
30
33
|
end
|
|
@@ -79,10 +82,12 @@ module Philiprehberger
|
|
|
79
82
|
#
|
|
80
83
|
# Opens the file, attempts a non-blocking exclusive lock, and immediately
|
|
81
84
|
# releases it. Returns true if the lock attempt fails (file is locked).
|
|
85
|
+
# Returns false if the lock has expired (TTL elapsed).
|
|
82
86
|
#
|
|
83
87
|
# @return [Boolean]
|
|
84
88
|
def locked?
|
|
85
89
|
return false unless File.exist?(@path)
|
|
90
|
+
return false if expired?
|
|
86
91
|
|
|
87
92
|
f = File.open(@path, File::CREAT | File::RDWR)
|
|
88
93
|
got_lock = f.flock(File::LOCK_EX | File::LOCK_NB)
|
|
@@ -96,6 +101,24 @@ module Philiprehberger
|
|
|
96
101
|
end
|
|
97
102
|
end
|
|
98
103
|
|
|
104
|
+
# Check whether the lock has expired based on its TTL
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean] true if the lock metadata contains an expires_at time that has passed
|
|
107
|
+
def expired?
|
|
108
|
+
return false unless File.exist?(@meta_path)
|
|
109
|
+
|
|
110
|
+
content = File.read(@meta_path).strip
|
|
111
|
+
return false if content.empty?
|
|
112
|
+
|
|
113
|
+
data = JSON.parse(content)
|
|
114
|
+
expires_at = data['expires_at']
|
|
115
|
+
return false unless expires_at
|
|
116
|
+
|
|
117
|
+
Time.parse(expires_at) <= Time.now
|
|
118
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
119
|
+
false
|
|
120
|
+
end
|
|
121
|
+
|
|
99
122
|
# Read lock owner metadata
|
|
100
123
|
#
|
|
101
124
|
# @return [Hash, nil] hash with :pid, :hostname, :acquired_at keys or nil
|
|
@@ -123,6 +146,7 @@ module Philiprehberger
|
|
|
123
146
|
'hostname' => Socket.gethostname,
|
|
124
147
|
'acquired_at' => Time.now.iso8601
|
|
125
148
|
}
|
|
149
|
+
metadata['expires_at'] = (Time.now + @ttl).iso8601 if @ttl
|
|
126
150
|
File.write(@meta_path, JSON.generate(metadata))
|
|
127
151
|
end
|
|
128
152
|
|
|
@@ -142,6 +166,15 @@ module Philiprehberger
|
|
|
142
166
|
return if content.empty?
|
|
143
167
|
|
|
144
168
|
data = JSON.parse(content)
|
|
169
|
+
|
|
170
|
+
# Check TTL expiration
|
|
171
|
+
expires_at = data['expires_at']
|
|
172
|
+
if expires_at && Time.parse(expires_at) <= Time.now
|
|
173
|
+
FileUtils.rm_f(meta_path)
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Check process liveness
|
|
145
178
|
pid = data['pid']
|
|
146
179
|
return unless pid
|
|
147
180
|
|
|
@@ -29,18 +29,26 @@ module Philiprehberger
|
|
|
29
29
|
# automatically cleaned up.
|
|
30
30
|
#
|
|
31
31
|
# @param auto_cleanup [Boolean] when true, automatically remove stale locks
|
|
32
|
+
# @param ttl [Numeric, nil] time-to-live in seconds; lock expires after this duration
|
|
32
33
|
# @return [true] when the lock is acquired
|
|
33
34
|
# @raise [LockKit::Error] if the lock is held by a living process
|
|
34
|
-
def acquire(auto_cleanup: false)
|
|
35
|
-
|
|
36
|
-
existing_pid = read_pid
|
|
35
|
+
def acquire(auto_cleanup: false, ttl: nil)
|
|
36
|
+
@ttl = ttl
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if File.exist?(@pid_path)
|
|
39
|
+
# Check TTL expiration first
|
|
40
|
+
if lock_expired?
|
|
41
|
+
FileUtils.rm_f(@pid_path)
|
|
42
|
+
else
|
|
43
|
+
existing_pid = read_pid
|
|
44
|
+
|
|
45
|
+
if existing_pid && process_alive?(existing_pid)
|
|
46
|
+
raise Error, "Lock '#{@name}' is held by process #{existing_pid}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Stale PID file — remove it
|
|
50
|
+
FileUtils.rm_f(@pid_path)
|
|
40
51
|
end
|
|
41
|
-
|
|
42
|
-
# Stale PID file — remove it
|
|
43
|
-
FileUtils.rm_f(@pid_path)
|
|
44
52
|
end
|
|
45
53
|
|
|
46
54
|
metadata = {
|
|
@@ -48,6 +56,7 @@ module Philiprehberger
|
|
|
48
56
|
'hostname' => Socket.gethostname,
|
|
49
57
|
'acquired_at' => Time.now.iso8601
|
|
50
58
|
}
|
|
59
|
+
metadata['expires_at'] = (Time.now + ttl).iso8601 if ttl
|
|
51
60
|
File.write(@pid_path, JSON.generate(metadata))
|
|
52
61
|
@acquired = true
|
|
53
62
|
true
|
|
@@ -68,9 +77,12 @@ module Philiprehberger
|
|
|
68
77
|
|
|
69
78
|
# Check whether the lock is currently held by a living process
|
|
70
79
|
#
|
|
80
|
+
# Returns false if the lock has expired (TTL elapsed).
|
|
81
|
+
#
|
|
71
82
|
# @return [Boolean]
|
|
72
83
|
def locked?
|
|
73
84
|
return false unless File.exist?(@pid_path)
|
|
85
|
+
return false if expired?
|
|
74
86
|
|
|
75
87
|
pid = read_pid
|
|
76
88
|
return false unless pid
|
|
@@ -90,6 +102,13 @@ module Philiprehberger
|
|
|
90
102
|
!process_alive?(pid)
|
|
91
103
|
end
|
|
92
104
|
|
|
105
|
+
# Check whether the lock has expired based on its TTL
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean] true if the lock metadata contains an expires_at time that has passed
|
|
108
|
+
def expired?
|
|
109
|
+
lock_expired?
|
|
110
|
+
end
|
|
111
|
+
|
|
93
112
|
# Read lock owner metadata from the PID file
|
|
94
113
|
#
|
|
95
114
|
# @return [Hash, nil] hash with :pid, :hostname, :acquired_at keys or nil
|
|
@@ -103,6 +122,23 @@ module Philiprehberger
|
|
|
103
122
|
|
|
104
123
|
private
|
|
105
124
|
|
|
125
|
+
def lock_expired?
|
|
126
|
+
return false unless File.exist?(@pid_path)
|
|
127
|
+
|
|
128
|
+
content = File.read(@pid_path).strip
|
|
129
|
+
return false if content.empty?
|
|
130
|
+
|
|
131
|
+
data = JSON.parse(content)
|
|
132
|
+
return false unless data.is_a?(Hash)
|
|
133
|
+
|
|
134
|
+
expires_at = data['expires_at']
|
|
135
|
+
return false unless expires_at
|
|
136
|
+
|
|
137
|
+
Time.parse(expires_at) <= Time.now
|
|
138
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
106
142
|
# @return [Integer, nil]
|
|
107
143
|
def read_pid
|
|
108
144
|
content = File.read(@pid_path).strip
|
|
@@ -17,12 +17,13 @@ module Philiprehberger
|
|
|
17
17
|
# @param timeout [Numeric, nil] seconds to wait before raising
|
|
18
18
|
# @param auto_cleanup [Boolean] automatically remove stale locks before acquiring
|
|
19
19
|
# @param on_wait [Proc, nil] callback invoked every 0.5s while waiting; receives elapsed seconds
|
|
20
|
+
# @param ttl [Numeric, nil] time-to-live in seconds; lock expires after this duration
|
|
20
21
|
# @yield block to execute while the lock is held
|
|
21
22
|
# @return [Object] the return value of the block
|
|
22
23
|
# @raise [Error] if the lock cannot be acquired
|
|
23
|
-
def self.with_file_lock(path, timeout: nil, auto_cleanup: false, on_wait: nil, &block)
|
|
24
|
+
def self.with_file_lock(path, timeout: nil, auto_cleanup: false, on_wait: nil, ttl: nil, &block)
|
|
24
25
|
lock = FileLock.new(path)
|
|
25
|
-
lock.acquire(timeout: timeout, auto_cleanup: auto_cleanup, on_wait: on_wait)
|
|
26
|
+
lock.acquire(timeout: timeout, auto_cleanup: auto_cleanup, on_wait: on_wait, ttl: ttl)
|
|
26
27
|
begin
|
|
27
28
|
block.call
|
|
28
29
|
ensure
|
|
@@ -35,12 +36,13 @@ module Philiprehberger
|
|
|
35
36
|
# @param name [String] lock name
|
|
36
37
|
# @param dir [String] directory for the PID file
|
|
37
38
|
# @param auto_cleanup [Boolean] automatically remove stale locks before acquiring
|
|
39
|
+
# @param ttl [Numeric, nil] time-to-live in seconds; lock expires after this duration
|
|
38
40
|
# @yield block to execute while the lock is held
|
|
39
41
|
# @return [Object] the return value of the block
|
|
40
42
|
# @raise [Error] if the lock is held by another process
|
|
41
|
-
def self.with_pid_lock(name, dir: Dir.tmpdir, auto_cleanup: false, &block)
|
|
43
|
+
def self.with_pid_lock(name, dir: Dir.tmpdir, auto_cleanup: false, ttl: nil, &block)
|
|
42
44
|
lock = PidLock.new(name, dir: dir)
|
|
43
|
-
lock.acquire(auto_cleanup: auto_cleanup)
|
|
45
|
+
lock.acquire(auto_cleanup: auto_cleanup, ttl: ttl)
|
|
44
46
|
begin
|
|
45
47
|
block.call
|
|
46
48
|
ensure
|
|
@@ -87,6 +89,35 @@ module Philiprehberger
|
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
|
|
92
|
+
# Execute a block with a file lock, retrying with exponential backoff on failure
|
|
93
|
+
#
|
|
94
|
+
# @param path [String] path to the lock file
|
|
95
|
+
# @param retries [Integer] maximum number of attempts
|
|
96
|
+
# @param delay [Numeric] initial delay in seconds between retries
|
|
97
|
+
# @param backoff [Numeric] multiplier applied to delay after each failed attempt
|
|
98
|
+
# @param timeout [Numeric, nil] seconds to wait per acquire attempt
|
|
99
|
+
# @param auto_cleanup [Boolean] automatically remove stale locks before acquiring
|
|
100
|
+
# @param ttl [Numeric, nil] time-to-live in seconds; lock expires after this duration
|
|
101
|
+
# @yield block to execute while the lock is held
|
|
102
|
+
# @return [Object] the return value of the block
|
|
103
|
+
# @raise [Error] if all retry attempts are exhausted
|
|
104
|
+
def self.with_retry_lock(path, retries: 3, delay: 0.1, backoff: 2, timeout: nil, auto_cleanup: true, ttl: nil, &block)
|
|
105
|
+
attempts = 0
|
|
106
|
+
current_delay = delay
|
|
107
|
+
|
|
108
|
+
loop do
|
|
109
|
+
attempts += 1
|
|
110
|
+
begin
|
|
111
|
+
return with_file_lock(path, timeout: timeout, auto_cleanup: auto_cleanup, ttl: ttl, &block)
|
|
112
|
+
rescue Error
|
|
113
|
+
raise if attempts >= retries
|
|
114
|
+
|
|
115
|
+
sleep current_delay
|
|
116
|
+
current_delay *= backoff
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
90
121
|
# Check if a file is currently locked by another process
|
|
91
122
|
#
|
|
92
123
|
# @param path [String] path to the lock file
|
|
@@ -123,6 +154,33 @@ module Philiprehberger
|
|
|
123
154
|
false
|
|
124
155
|
end
|
|
125
156
|
|
|
157
|
+
# Check if a lock has expired based on its TTL
|
|
158
|
+
#
|
|
159
|
+
# @param path [String] path to the lock file or PID file
|
|
160
|
+
# @return [Boolean] true if the lock metadata contains an expires_at time that has passed
|
|
161
|
+
def self.expired?(path)
|
|
162
|
+
# Check file lock metadata
|
|
163
|
+
meta_path = "#{path}.meta"
|
|
164
|
+
target = if File.exist?(meta_path)
|
|
165
|
+
meta_path
|
|
166
|
+
elsif File.exist?(path)
|
|
167
|
+
path
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
return false unless target
|
|
171
|
+
|
|
172
|
+
content = File.read(target).strip
|
|
173
|
+
return false if content.empty?
|
|
174
|
+
|
|
175
|
+
data = JSON.parse(content)
|
|
176
|
+
expires_at = data['expires_at']
|
|
177
|
+
return false unless expires_at
|
|
178
|
+
|
|
179
|
+
Time.parse(expires_at) <= Time.now
|
|
180
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
126
184
|
# Get lock owner metadata
|
|
127
185
|
#
|
|
128
186
|
# @param path [String] path to the lock file or PID file
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-lock_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: File locks using flock and PID file locks with stale detection for coordinating
|
|
14
14
|
between processes. Timeout support and automatic cleanup.
|
|
@@ -53,6 +53,6 @@ requirements: []
|
|
|
53
53
|
rubygems_version: 3.5.22
|
|
54
54
|
signing_key:
|
|
55
55
|
specification_version: 4
|
|
56
|
-
summary: File-based and PID locking for process coordination with
|
|
57
|
-
read-write locks, and lock owner identification
|
|
56
|
+
summary: File-based and PID locking for process coordination with TTL expiration,
|
|
57
|
+
stale detection, read-write locks, and lock owner identification
|
|
58
58
|
test_files: []
|