safe_flock 0.1.0 → 0.1.1
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/README.md +15 -1
- data/lib/safe_flock.rb +39 -3
- data/lib/safe_flock/lockfile.rb +59 -16
- data/lib/safe_flock/version.rb +6 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90677ce98f751bb3855c156aa1850a712213de79ab048b3cb33000dffc023c3a
|
4
|
+
data.tar.gz: 3131f49fd5b0c0f898d3de3e95568ab2d8636034ff4a8aa217d8d02b20cf9716
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a91c92fe2c33aa5c4b957b78ab9b694da2848f9c4d1f0fca3c74902ee4a96213995a6d32d10ab47f2f7e320c04d7d0c87faf79845853f6ac2cb53eb98fbc6c56
|
7
|
+
data.tar.gz: e499f43feb9b3ceef984c9c05eae963e12c7dbf0a2a4280d38fe7fc6d274dd3cfa173d29fede800323c4e13617a413975d2a1604f920bfbf535cd581a915cee9
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](http://badge.fury.io/rb/safe_flock) [](https://travis-ci.org/starjuice/safe_flock) [](http://inch-ci.org/github/starjuice/safe_flock)
|
2
|
+
|
1
3
|
# SafeFlock
|
2
4
|
|
3
5
|
Thread-safe, transferable, flock-based file lock.
|
@@ -24,6 +26,10 @@ Or install it yourself as:
|
|
24
26
|
|
25
27
|
$ gem install safe_flock
|
26
28
|
|
29
|
+
## Documentation
|
30
|
+
|
31
|
+
For documentation of the released gem, see [rubydoc.info](http://www.rubydoc.info/gems/safe_flock).
|
32
|
+
|
27
33
|
## Usage
|
28
34
|
|
29
35
|
Simple example:
|
@@ -41,9 +47,15 @@ The use case for which the helper was created:
|
|
41
47
|
```
|
42
48
|
require "safe_flock"
|
43
49
|
|
44
|
-
SafeFlock.create("/var/run/myapp/myapp.lck") do
|
50
|
+
SafeFlock.create("/var/run/myapp/myapp.lck") do |lock|
|
45
51
|
child = fork do
|
52
|
+
|
46
53
|
# ... mutually excluded processing
|
54
|
+
|
55
|
+
lock.unlock
|
56
|
+
|
57
|
+
# ... non-critical processing
|
58
|
+
|
47
59
|
end
|
48
60
|
end
|
49
61
|
Process.detach(child)
|
@@ -53,6 +65,8 @@ Process.detach(child)
|
|
53
65
|
|
54
66
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
55
67
|
|
68
|
+
To run the test suite, run `bundle exec rake spec`.
|
69
|
+
|
56
70
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
57
71
|
|
58
72
|
## Contributing
|
data/lib/safe_flock.rb
CHANGED
@@ -1,14 +1,50 @@
|
|
1
1
|
require "safe_flock/lockfile"
|
2
2
|
require "safe_flock/version"
|
3
3
|
|
4
|
+
##
|
5
|
+
# Thread-safe, transferable, flock-based lock file
|
6
|
+
#
|
4
7
|
module SafeFlock
|
5
8
|
|
6
|
-
|
9
|
+
##
|
10
|
+
# Raised when the file can't be locked in time
|
11
|
+
#
|
7
12
|
class Locked < RuntimeError; end
|
8
13
|
|
9
|
-
|
14
|
+
##
|
15
|
+
# Ensure mutual exclusion of a block with +flock+
|
16
|
+
#
|
17
|
+
# * The block is only executed if the lock can be acquired.
|
18
|
+
# * The lock is held for the duration of the block.
|
19
|
+
# * Any child process forked within the block holds the lock
|
20
|
+
# until it terminates or explicitly releases the lock
|
21
|
+
# (see SafeFlock::Lockfile#unlock}).
|
22
|
+
# * No other thread may enter the block while the lock is held.
|
23
|
+
# * The lock file _may_ be left in place after the lock is released,
|
24
|
+
# but this behaviour should not be relied upon.
|
25
|
+
#
|
26
|
+
# @param [String] path
|
27
|
+
# path of file to flock (created if necessary).
|
28
|
+
# Absolute pathname (see {Pathname#realpath}) recommended for per-path thread mutex
|
29
|
+
# and for processes in which the present working directory (see {Dir.chdir}) changes.
|
30
|
+
# @option options [Float] :max_wait
|
31
|
+
# approximate maximum seconds to wait for the lock.
|
32
|
+
# A zero +max_wait+ requests a non-blocking attempt
|
33
|
+
# (i.e. give up immediately if file already locked).
|
34
|
+
#
|
35
|
+
# @return [Object] the value of the block
|
36
|
+
#
|
37
|
+
# @raise [SafeFlock::Locked] if the lock could not be acquired.
|
38
|
+
# If +max_wait is zero, the file was already locked.
|
39
|
+
# Otherwise, timed out waiting for the lock.
|
40
|
+
# @raise [Exception] if an IO error occurs opening the lock file
|
41
|
+
# e.g. +Errno::EACCES+
|
42
|
+
#
|
43
|
+
# TODO implement configurable retry
|
44
|
+
#
|
45
|
+
def self.create(path, max_wait: 5.0)
|
10
46
|
raise(ArgumentError, "Block required") unless block_given?
|
11
|
-
lockfile = Lockfile.new(path,
|
47
|
+
lockfile = Lockfile.new(path, max_wait: max_wait)
|
12
48
|
begin
|
13
49
|
if lockfile.lock
|
14
50
|
begin
|
data/lib/safe_flock/lockfile.rb
CHANGED
@@ -2,18 +2,20 @@ require "time"
|
|
2
2
|
|
3
3
|
module SafeFlock
|
4
4
|
|
5
|
+
##
|
6
|
+
# Thread-safe, transferable, flock-based lock file implementation
|
7
|
+
#
|
8
|
+
# See {SafeFlock.create} for a safe way to wrap the creation, locking and unlocking of the lock file.
|
9
|
+
#
|
5
10
|
class Lockfile
|
6
11
|
|
7
|
-
class Error < RuntimeError; end
|
8
|
-
class Locked < RuntimeError; end
|
9
|
-
|
10
12
|
@@global_mutex = Mutex.new unless defined?(@@global_mutex)
|
11
13
|
@@path_mutex = {} unless defined?(@@path_mutex)
|
12
14
|
|
13
|
-
|
15
|
+
##
|
16
|
+
# Initialize (but do not lock)
|
14
17
|
#
|
15
|
-
#
|
16
|
-
# * +max_wait+ seconds to retry acquiring lock before giving up and raising +Error+ (+5.0+)
|
18
|
+
# See {SafeFlock.create} for a safe way to wrap the creation, locking and unlocking of the lock file.
|
17
19
|
#
|
18
20
|
def initialize(path, max_wait: 5.0)
|
19
21
|
@pid = $$
|
@@ -25,6 +27,25 @@ module SafeFlock
|
|
25
27
|
@lockfd = nil
|
26
28
|
end
|
27
29
|
|
30
|
+
##
|
31
|
+
# Lock the lock file
|
32
|
+
#
|
33
|
+
# See {SafeFlock.create} for a safe way to wrap the creation, locking and unlocking of the lock file.
|
34
|
+
#
|
35
|
+
# Attempt to {File#flock} the lock file, creating it if necessary.
|
36
|
+
#
|
37
|
+
# If +max_wait+ is zero, the attempt is non-blocking: if the file
|
38
|
+
# is already locked, give up immediately. Otherwise, continue
|
39
|
+
# trying to lock the file for approximately +max_wait+ seconds.
|
40
|
+
#
|
41
|
+
# The operation is performed under a per-path thread mutex to
|
42
|
+
# preserve mutual exclusion across threads.
|
43
|
+
#
|
44
|
+
# @return [true|false] whether the lock was acquired
|
45
|
+
#
|
46
|
+
# @raise [Exception] if an IO error occurs opening the lock file
|
47
|
+
# e.g. +Errno::EACCES+
|
48
|
+
#
|
28
49
|
def lock
|
29
50
|
deadline = Time.now.to_f + @max_wait
|
30
51
|
while !(is_locked = try_lock)
|
@@ -37,9 +58,16 @@ module SafeFlock
|
|
37
58
|
is_locked
|
38
59
|
end
|
39
60
|
|
40
|
-
|
41
|
-
#
|
42
|
-
#
|
61
|
+
##
|
62
|
+
# Unlock the lock file
|
63
|
+
#
|
64
|
+
# See {SafeFlock.create} for a safe way to wrap the creation, locking and unlocking of the lock file.
|
65
|
+
#
|
66
|
+
# Unlock the lock file in the current process.
|
67
|
+
#
|
68
|
+
# The only intended use case for this method is in a forked child process that does significant work
|
69
|
+
# after the mutually exclusive work for which it required the lock. In such cases, the process may
|
70
|
+
# call +unlock+ after the mutually exclusive work is complete.
|
43
71
|
#
|
44
72
|
def unlock
|
45
73
|
if @lockfd
|
@@ -51,21 +79,36 @@ module SafeFlock
|
|
51
79
|
end
|
52
80
|
end
|
53
81
|
|
54
|
-
|
82
|
+
##
|
83
|
+
# The path to the lock file
|
84
|
+
#
|
85
|
+
attr_reader :path
|
86
|
+
|
87
|
+
##
|
88
|
+
# The process that created the lock file
|
89
|
+
#
|
90
|
+
attr_reader :pid
|
91
|
+
|
92
|
+
##
|
93
|
+
# A unique identifier for the thread that created the lock file
|
94
|
+
#
|
95
|
+
attr_reader :thread_id
|
55
96
|
|
56
97
|
private
|
57
98
|
|
58
99
|
def try_lock
|
100
|
+
flocked = false
|
59
101
|
begin
|
60
102
|
if try_mutex_lock
|
61
103
|
@lockfd = File.new(@path, "a")
|
62
|
-
@lockfd.flock(File::LOCK_EX | File::LOCK_NB)
|
104
|
+
flocked = @lockfd.flock(File::LOCK_EX | File::LOCK_NB)
|
105
|
+
end
|
106
|
+
ensure
|
107
|
+
if !flocked
|
108
|
+
@lockfd.close if @lockfd and !@lockfd.closed?
|
109
|
+
@lockfd = nil
|
110
|
+
mutex_unlock
|
63
111
|
end
|
64
|
-
rescue
|
65
|
-
@lockfd.close if @lockfd and !@lockfd.closed?
|
66
|
-
@lockfd = nil
|
67
|
-
mutex_unlock
|
68
|
-
raise
|
69
112
|
end
|
70
113
|
end
|
71
114
|
|
data/lib/safe_flock/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_flock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sheldon Hearn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-12-
|
11
|
+
date: 2018-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|