safe_flock 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/safe_flock.svg)](http://badge.fury.io/rb/safe_flock) [![Build Status](https://travis-ci.org/starjuice/safe_flock.svg?branch=master)](https://travis-ci.org/starjuice/safe_flock) [![Inline docs](http://inch-ci.org/github/starjuice/safe_flock.svg?branch=master)](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
|