philiprehberger-lock_kit 0.1.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 +7 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE +21 -0
- data/README.md +119 -0
- data/lib/philiprehberger/lock_kit/file_lock.rb +85 -0
- data/lib/philiprehberger/lock_kit/pid_lock.rb +109 -0
- data/lib/philiprehberger/lock_kit/version.rb +7 -0
- data/lib/philiprehberger/lock_kit.rb +74 -0
- metadata +56 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4323796d385deb9f9a2280dbd05aad5a442738b62fd126f2a9309ded2009d4e7
|
|
4
|
+
data.tar.gz: a552375e35331d9bcbeb02d7ffd09a1c27c9edd26114eb76a299cae9ff54f0b9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cd045a2839a2885aa218b11146c2af5c43ee04de2c13934ba1c4dbb02e587bd9f6d1491a7f0cce7d31c8e01f6646cd54e0a45780623e5af0cc8631b1d5b2002c
|
|
7
|
+
data.tar.gz: 5f309ce7a1b7d03c4c86686e23766264e51118b3f8abc13c3c8e7cebd5ac9ef0bac66f07ebc544f06f753b0bf4cd6e33173278341cd71c79a56a1c9c948d473a
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this gem will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-03-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Initial release
|
|
14
|
+
- `FileLock` class with exclusive file locking via `flock(2)`
|
|
15
|
+
- `PidLock` class with PID file locking and stale process detection
|
|
16
|
+
- `with_file_lock` convenience method with optional timeout
|
|
17
|
+
- `with_pid_lock` convenience method with automatic cleanup
|
|
18
|
+
- `locked?` and `stale?` module-level query methods
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 philiprehberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# philiprehberger-lock_kit
|
|
2
|
+
|
|
3
|
+
[](https://github.com/philiprehberger/rb-lock-kit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/philiprehberger-lock_kit)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/sponsors/philiprehberger)
|
|
7
|
+
|
|
8
|
+
File-based and PID locking for process coordination
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- Ruby >= 3.1
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "philiprehberger-lock_kit"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install directly:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install philiprehberger-lock_kit
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "philiprehberger/lock_kit"
|
|
32
|
+
|
|
33
|
+
Philiprehberger::LockKit.with_file_lock("/tmp/my.lock") do
|
|
34
|
+
# exclusive work here
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### File Locking with Timeout
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
Philiprehberger::LockKit.with_file_lock("/tmp/my.lock", timeout: 5) do
|
|
42
|
+
# waits up to 5 seconds for the lock
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### PID File Locking
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
Philiprehberger::LockKit.with_pid_lock("my_worker") do
|
|
50
|
+
# only one process with this name can run at a time
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Manual File Lock
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
lock = Philiprehberger::LockKit::FileLock.new("/tmp/my.lock")
|
|
58
|
+
lock.acquire(timeout: 10)
|
|
59
|
+
# ... do work ...
|
|
60
|
+
lock.release
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Manual PID Lock
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
lock = Philiprehberger::LockKit::PidLock.new("my_worker", dir: "/var/run")
|
|
67
|
+
lock.acquire
|
|
68
|
+
# ... do work ...
|
|
69
|
+
lock.release
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Checking Lock Status
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
Philiprehberger::LockKit.locked?("/tmp/my.lock") # => true/false
|
|
76
|
+
Philiprehberger::LockKit.stale?("/tmp/my_worker.pid") # => true/false
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `LockKit`
|
|
82
|
+
|
|
83
|
+
| Method | Description |
|
|
84
|
+
|--------|-------------|
|
|
85
|
+
| `.with_file_lock(path, timeout: nil) { }` | Execute block with exclusive file lock |
|
|
86
|
+
| `.with_pid_lock(name, dir: Dir.tmpdir) { }` | Execute block with PID file lock |
|
|
87
|
+
| `.locked?(path)` | Check if a file is currently locked |
|
|
88
|
+
| `.stale?(pid_file)` | Check if a PID file references a dead process |
|
|
89
|
+
|
|
90
|
+
### `LockKit::FileLock`
|
|
91
|
+
|
|
92
|
+
| Method | Description |
|
|
93
|
+
|--------|-------------|
|
|
94
|
+
| `.new(path)` | Create a file lock instance |
|
|
95
|
+
| `#acquire(timeout: nil)` | Acquire exclusive lock, optional timeout in seconds |
|
|
96
|
+
| `#release` | Release the lock and close the file handle |
|
|
97
|
+
| `#locked?` | Check if the file is currently locked |
|
|
98
|
+
|
|
99
|
+
### `LockKit::PidLock`
|
|
100
|
+
|
|
101
|
+
| Method | Description |
|
|
102
|
+
|--------|-------------|
|
|
103
|
+
| `.new(name, dir: Dir.tmpdir)` | Create a PID lock instance |
|
|
104
|
+
| `#acquire` | Acquire PID lock, raises if held by a living process |
|
|
105
|
+
| `#release` | Release lock and remove PID file |
|
|
106
|
+
| `#locked?` | Check if lock is held by a living process |
|
|
107
|
+
| `#stale?` | Check if PID file references a dead process |
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bundle install
|
|
113
|
+
bundle exec rspec
|
|
114
|
+
bundle exec rubocop
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module LockKit
|
|
5
|
+
# Exclusive file lock using flock(2)
|
|
6
|
+
#
|
|
7
|
+
# Provides process-level mutual exclusion via the filesystem. The lock is
|
|
8
|
+
# advisory and relies on all participants using the same lock file path.
|
|
9
|
+
class FileLock
|
|
10
|
+
# @param path [String] path to the lock file
|
|
11
|
+
def initialize(path)
|
|
12
|
+
@path = path
|
|
13
|
+
@file = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Acquire an exclusive lock on the file
|
|
17
|
+
#
|
|
18
|
+
# @param timeout [Numeric, nil] seconds to wait before raising; nil means non-blocking single attempt
|
|
19
|
+
# @return [true] when the lock is acquired
|
|
20
|
+
# @raise [LockKit::Error] if the lock cannot be acquired
|
|
21
|
+
def acquire(timeout: nil)
|
|
22
|
+
@file = File.open(@path, File::CREAT | File::RDWR)
|
|
23
|
+
|
|
24
|
+
if timeout.nil?
|
|
25
|
+
unless @file.flock(File::LOCK_EX | File::LOCK_NB)
|
|
26
|
+
close_file
|
|
27
|
+
raise Error, "Could not acquire lock on #{@path}"
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
31
|
+
|
|
32
|
+
until @file.flock(File::LOCK_EX | File::LOCK_NB)
|
|
33
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
34
|
+
if remaining <= 0
|
|
35
|
+
close_file
|
|
36
|
+
raise Error, "Timeout acquiring lock on #{@path} after #{timeout}s"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
sleep [0.05, remaining].min
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Release the lock and close the file handle
|
|
47
|
+
#
|
|
48
|
+
# @return [void]
|
|
49
|
+
def release
|
|
50
|
+
return unless @file
|
|
51
|
+
|
|
52
|
+
@file.flock(File::LOCK_UN)
|
|
53
|
+
close_file
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check whether the file is currently locked by another process
|
|
57
|
+
#
|
|
58
|
+
# Opens the file, attempts a non-blocking exclusive lock, and immediately
|
|
59
|
+
# releases it. Returns true if the lock attempt fails (file is locked).
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def locked?
|
|
63
|
+
return false unless File.exist?(@path)
|
|
64
|
+
|
|
65
|
+
f = File.open(@path, File::CREAT | File::RDWR)
|
|
66
|
+
got_lock = f.flock(File::LOCK_EX | File::LOCK_NB)
|
|
67
|
+
if got_lock
|
|
68
|
+
f.flock(File::LOCK_UN)
|
|
69
|
+
f.close
|
|
70
|
+
false
|
|
71
|
+
else
|
|
72
|
+
f.close
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def close_file
|
|
80
|
+
@file&.close
|
|
81
|
+
@file = nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
module Philiprehberger
|
|
6
|
+
module LockKit
|
|
7
|
+
# PID-file based lock with stale process detection
|
|
8
|
+
#
|
|
9
|
+
# Writes the current process ID to a file. Other processes can check the
|
|
10
|
+
# file to determine if the lock holder is still alive, enabling automatic
|
|
11
|
+
# recovery from crashed processes.
|
|
12
|
+
class PidLock
|
|
13
|
+
# @param name [String] lock name (used as the PID file basename)
|
|
14
|
+
# @param dir [String] directory for the PID file (defaults to system tmpdir)
|
|
15
|
+
def initialize(name, dir: Dir.tmpdir)
|
|
16
|
+
@name = name
|
|
17
|
+
@dir = dir
|
|
18
|
+
@pid_path = File.join(dir, "#{name}.pid")
|
|
19
|
+
@acquired = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Acquire the PID lock
|
|
23
|
+
#
|
|
24
|
+
# Creates a PID file containing the current process ID. If a PID file
|
|
25
|
+
# already exists, checks whether the owning process is still alive. Stale
|
|
26
|
+
# PID files from dead processes are automatically cleaned up.
|
|
27
|
+
#
|
|
28
|
+
# @return [true] when the lock is acquired
|
|
29
|
+
# @raise [LockKit::Error] if the lock is held by a living process
|
|
30
|
+
def acquire
|
|
31
|
+
if File.exist?(@pid_path)
|
|
32
|
+
existing_pid = read_pid
|
|
33
|
+
|
|
34
|
+
if existing_pid && process_alive?(existing_pid)
|
|
35
|
+
raise Error, "Lock '#{@name}' is held by process #{existing_pid}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Stale PID file — remove it
|
|
39
|
+
FileUtils.rm_f(@pid_path)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
File.write(@pid_path, Process.pid.to_s)
|
|
43
|
+
@acquired = true
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Release the lock by removing the PID file
|
|
48
|
+
#
|
|
49
|
+
# Only removes the file if it was written by this process.
|
|
50
|
+
#
|
|
51
|
+
# @return [void]
|
|
52
|
+
def release
|
|
53
|
+
return unless @acquired
|
|
54
|
+
|
|
55
|
+
File.delete(@pid_path) if File.exist?(@pid_path) && read_pid == Process.pid
|
|
56
|
+
|
|
57
|
+
@acquired = false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check whether the lock is currently held by a living process
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
def locked?
|
|
64
|
+
return false unless File.exist?(@pid_path)
|
|
65
|
+
|
|
66
|
+
pid = read_pid
|
|
67
|
+
return false unless pid
|
|
68
|
+
|
|
69
|
+
process_alive?(pid)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check whether the PID file references a dead process
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def stale?
|
|
76
|
+
return false unless File.exist?(@pid_path)
|
|
77
|
+
|
|
78
|
+
pid = read_pid
|
|
79
|
+
return true unless pid
|
|
80
|
+
|
|
81
|
+
!process_alive?(pid)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# @return [Integer, nil]
|
|
87
|
+
def read_pid
|
|
88
|
+
content = File.read(@pid_path).strip
|
|
89
|
+
return nil if content.empty?
|
|
90
|
+
|
|
91
|
+
Integer(content)
|
|
92
|
+
rescue ArgumentError, Errno::ENOENT
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @param pid [Integer]
|
|
97
|
+
# @return [Boolean]
|
|
98
|
+
def process_alive?(pid)
|
|
99
|
+
Process.kill(0, pid)
|
|
100
|
+
true
|
|
101
|
+
rescue Errno::ESRCH
|
|
102
|
+
false
|
|
103
|
+
rescue Errno::EPERM
|
|
104
|
+
# Process exists but we don't have permission to signal it
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lock_kit/version'
|
|
4
|
+
require_relative 'lock_kit/file_lock'
|
|
5
|
+
require_relative 'lock_kit/pid_lock'
|
|
6
|
+
|
|
7
|
+
module Philiprehberger
|
|
8
|
+
module LockKit
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
11
|
+
# Execute a block while holding an exclusive file lock
|
|
12
|
+
#
|
|
13
|
+
# @param path [String] path to the lock file
|
|
14
|
+
# @param timeout [Numeric, nil] seconds to wait before raising
|
|
15
|
+
# @yield block to execute while the lock is held
|
|
16
|
+
# @return [Object] the return value of the block
|
|
17
|
+
# @raise [Error] if the lock cannot be acquired
|
|
18
|
+
def self.with_file_lock(path, timeout: nil, &block)
|
|
19
|
+
lock = FileLock.new(path)
|
|
20
|
+
lock.acquire(timeout: timeout)
|
|
21
|
+
begin
|
|
22
|
+
block.call
|
|
23
|
+
ensure
|
|
24
|
+
lock.release
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Execute a block while holding a PID file lock
|
|
29
|
+
#
|
|
30
|
+
# @param name [String] lock name
|
|
31
|
+
# @param dir [String] directory for the PID file
|
|
32
|
+
# @yield block to execute while the lock is held
|
|
33
|
+
# @return [Object] the return value of the block
|
|
34
|
+
# @raise [Error] if the lock is held by another process
|
|
35
|
+
def self.with_pid_lock(name, dir: Dir.tmpdir, &block)
|
|
36
|
+
lock = PidLock.new(name, dir: dir)
|
|
37
|
+
lock.acquire
|
|
38
|
+
begin
|
|
39
|
+
block.call
|
|
40
|
+
ensure
|
|
41
|
+
lock.release
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if a file is currently locked by another process
|
|
46
|
+
#
|
|
47
|
+
# @param path [String] path to the lock file
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def self.locked?(path)
|
|
50
|
+
FileLock.new(path).locked?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check if a PID file references a dead process
|
|
54
|
+
#
|
|
55
|
+
# @param pid_file [String] path to the PID file
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def self.stale?(pid_file)
|
|
58
|
+
return false unless File.exist?(pid_file)
|
|
59
|
+
|
|
60
|
+
content = File.read(pid_file).strip
|
|
61
|
+
return true if content.empty?
|
|
62
|
+
|
|
63
|
+
pid = Integer(content)
|
|
64
|
+
Process.kill(0, pid)
|
|
65
|
+
false
|
|
66
|
+
rescue ArgumentError
|
|
67
|
+
true
|
|
68
|
+
rescue Errno::ESRCH
|
|
69
|
+
true
|
|
70
|
+
rescue Errno::EPERM
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: philiprehberger-lock_kit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Philip Rehberger
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: File locks using flock and PID file locks with stale detection for coordinating
|
|
14
|
+
between processes. Timeout support and automatic cleanup.
|
|
15
|
+
email:
|
|
16
|
+
- me@philiprehberger.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/philiprehberger/lock_kit.rb
|
|
25
|
+
- lib/philiprehberger/lock_kit/file_lock.rb
|
|
26
|
+
- lib/philiprehberger/lock_kit/pid_lock.rb
|
|
27
|
+
- lib/philiprehberger/lock_kit/version.rb
|
|
28
|
+
homepage: https://github.com/philiprehberger/rb-lock-kit
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata:
|
|
32
|
+
homepage_uri: https://github.com/philiprehberger/rb-lock-kit
|
|
33
|
+
source_code_uri: https://github.com/philiprehberger/rb-lock-kit
|
|
34
|
+
changelog_uri: https://github.com/philiprehberger/rb-lock-kit/blob/main/CHANGELOG.md
|
|
35
|
+
bug_tracker_uri: https://github.com/philiprehberger/rb-lock-kit/issues
|
|
36
|
+
rubygems_mfa_required: 'true'
|
|
37
|
+
post_install_message:
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 3.1.0
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubygems_version: 3.5.22
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: File-based and PID locking for process coordination
|
|
56
|
+
test_files: []
|