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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d93a87058706f01d75abc1892f295c1448bbe62829dbd71fd05f50b21660c24
4
- data.tar.gz: 50307bf731dcba44c41e0304154cc38e67d8ef3f93e45de02bd7568c05ea12d6
3
+ metadata.gz: 90677ce98f751bb3855c156aa1850a712213de79ab048b3cb33000dffc023c3a
4
+ data.tar.gz: 3131f49fd5b0c0f898d3de3e95568ab2d8636034ff4a8aa217d8d02b20cf9716
5
5
  SHA512:
6
- metadata.gz: 40506d496027784cd008d5ff95fd855e85d84ea492ff2f7be40a84bb3393fdce11be11ecf0a249d820ea953708eb4e3078a596d433ce3313cc47e5b312021002
7
- data.tar.gz: 71c2b6401b2e6b93e4172d53b8a93b0fdd6c282c2da976050f7ca5c6923aaf69de94297e48027fe4f23118f57fb7b991a6d9bf1c9723499aa40bec6f7408e2a2
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
@@ -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
- class Error < RuntimeError; end
9
+ ##
10
+ # Raised when the file can't be locked in time
11
+ #
7
12
  class Locked < RuntimeError; end
8
13
 
9
- def self.create(path, options = {})
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, options)
47
+ lockfile = Lockfile.new(path, max_wait: max_wait)
12
48
  begin
13
49
  if lockfile.lock
14
50
  begin
@@ -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
- # +path+ full pathname of mutually agreed lock file.
15
+ ##
16
+ # Initialize (but do not lock)
14
17
  #
15
- # +options+
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
- # If the lock is inherited by a forked child process, it will hold the lock until
41
- # the child calls +unlock+ (or terminates) _and_ the parent's +Lockfile.create+
42
- # block terminates. The parent should not call +unlock+.
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
- attr_reader :path, :pid, :thread_id
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
 
@@ -1,3 +1,8 @@
1
1
  module SafeFlock
2
- VERSION = "0.1.0"
2
+ ##
3
+ # Library version
4
+ #
5
+ # Semantically versioned, see {https://semver.org/}.
6
+ #
7
+ VERSION = "0.1.1"
3
8
  end
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.0
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-02 00:00:00.000000000 Z
11
+ date: 2018-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler