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 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