reentrant_flock 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/.gitignore +9 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/reentrant_flock/version.rb +3 -0
- data/lib/reentrant_flock.rb +179 -0
- data/reentrant_flock.gemspec +27 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6d20c3216b95cf824f521614caca8e048843f649
|
4
|
+
data.tar.gz: 8957df239f692bfa3703545431070fd8f311c606
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 178c166ac4fc276c43ebc1c59160e6787ddfb92b52ad9dc3715343c2c61819f2de1a1e8270c02191071ba2f96cbfbdc0e56133b641c9559838f5c3ed4ada9a2d
|
7
|
+
data.tar.gz: bf0f978871509dab41c0192f7191530fc07eeec7f349dc33cf3d92cc84433ef96ef1064d5932c20105541fdd72fed0ca48605adfc2e86a1f756cd4b07ad891ee
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# ReentrantFlock
|
2
|
+
|
3
|
+
A reentrant/recursive flock.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'reentrant_flock'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install reentrant_flock
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'reentrant_flock'
|
25
|
+
|
26
|
+
File.open('/tmp/lock', File::RDWR | File::CREAT) do |fp|
|
27
|
+
ReentrantFlock.synchronize(fp, File::LOCK_EX) do
|
28
|
+
ReentrantFlock.synchronize(fp, File::LOCK_EX) do
|
29
|
+
# not to be blocked by myself
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'reentrant_flock'
|
37
|
+
|
38
|
+
def with_lock(&block)
|
39
|
+
File.open('/tmp/lock', File::RDWR | File::CREAT) do |fp|
|
40
|
+
ReentrantFlock.synchronize(fp, File::LOCK_EX, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
with_lock do
|
45
|
+
with_lock do
|
46
|
+
# not to be blocked by myself
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
`ReentrantFlock.synchronize` would raise `ReentrantFlock::AlreadyLocked` if `File::LOCK_NB` is given and already locked by somebody else.
|
52
|
+
|
53
|
+
See [File#flock](https://ruby-doc.org/core-2.2.0/File.html#method-i-flock) for flags.
|
54
|
+
|
55
|
+
## Development
|
56
|
+
|
57
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
58
|
+
|
59
|
+
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).
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sonots/reentrant_flock.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "reentrant_flock"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require "reentrant_flock/version"
|
2
|
+
|
3
|
+
class ReentrantFlock
|
4
|
+
class AlreadyLocked < StandardError; end
|
5
|
+
end
|
6
|
+
|
7
|
+
class ReentrantFlock
|
8
|
+
class << self
|
9
|
+
# Note that File#flock is automatically unlocked when a file is closed,
|
10
|
+
# but ReentrantFlock.flock cannot automatically reset internal counts
|
11
|
+
# when a file is closed. Use ReentrantFlock.synchronize to assure
|
12
|
+
# decrementing internal counts.
|
13
|
+
#
|
14
|
+
# @param fp [File]
|
15
|
+
# @param operation [PARAM]
|
16
|
+
# @see File#flock for more details
|
17
|
+
# @param [PARAM] operation See File#flock
|
18
|
+
# @return see File#flock
|
19
|
+
def flock(fp, operation)
|
20
|
+
if (operation & File::LOCK_UN) > 0
|
21
|
+
unlock(fp)
|
22
|
+
else
|
23
|
+
lock(fp, operation)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param fp [File]
|
28
|
+
# @param [PARAM] operation See File#flock
|
29
|
+
# @yield
|
30
|
+
# @raise [AlreadyLocked] if already locked and LOCK_NB operation is specified
|
31
|
+
# @return [Object] the result of block
|
32
|
+
def synchronize(fp, operation)
|
33
|
+
raise 'Must be called with a block' unless block_given?
|
34
|
+
|
35
|
+
begin
|
36
|
+
raise AlreadyLocked if lock(fp, operation) == false
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
unlock(fp)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param fp [File]
|
44
|
+
# @return [Boolean] true if locked by self
|
45
|
+
def self_locked?(fp)
|
46
|
+
k = key(fp)
|
47
|
+
Thread.current.key?(k) and Thread.current[k] >= 1
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @return [0] if current thread holds the lock
|
53
|
+
# @return [false] if current thread holds the lock
|
54
|
+
def lock(fp, operation)
|
55
|
+
c = incr(key(fp))
|
56
|
+
if c <= 1
|
57
|
+
# flock returns 0 if successfully locked.
|
58
|
+
# LOCK_NB returns false if somebody else already locked
|
59
|
+
fp.flock(operation)
|
60
|
+
else
|
61
|
+
0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def unlock(fp)
|
66
|
+
k = key(fp)
|
67
|
+
c = decr(k)
|
68
|
+
if c <= 0
|
69
|
+
fp.flock(File::LOCK_UN)
|
70
|
+
del(k)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def key(fp)
|
75
|
+
"reentrant_flock_#{fp.path}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def incr(key)
|
79
|
+
Thread.current[key] ||= 0
|
80
|
+
Thread.current[key] += 1
|
81
|
+
end
|
82
|
+
|
83
|
+
def decr(key)
|
84
|
+
Thread.current[key] ||= 0
|
85
|
+
Thread.current[key] -= 1
|
86
|
+
end
|
87
|
+
|
88
|
+
def del(key)
|
89
|
+
Thread.current[key] = nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# There is a fact that
|
95
|
+
#
|
96
|
+
# ```
|
97
|
+
# fp = File.open('a', 'w')
|
98
|
+
# fp.flock(File::LOCK_EX)
|
99
|
+
# fp.flock(File::LOCK_EX) # does not block
|
100
|
+
# fp = File.open('a', 'w')
|
101
|
+
# fp.flock(File::LOCK_EX) # block
|
102
|
+
# ```
|
103
|
+
#
|
104
|
+
# That is, File#flock is orginally reentrant for the same
|
105
|
+
# file object because its file descriptor has lock information
|
106
|
+
# on kernel layer. So, this instance version, which holds an
|
107
|
+
# instance of File, may not worth to be prepared.
|
108
|
+
class ReentrantFlock
|
109
|
+
attr_reader :fp
|
110
|
+
|
111
|
+
def initialize(fp)
|
112
|
+
@fp = fp
|
113
|
+
@mutex = Mutex.new
|
114
|
+
@counts = Hash.new(0)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @param [PARAM] operation See File#flock
|
118
|
+
# @return see File#flock
|
119
|
+
def flock(operation)
|
120
|
+
if (operation & File::LOCK_UN) > 0
|
121
|
+
unlock
|
122
|
+
else
|
123
|
+
lock(operation)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# @param [PARAM] operation See File#flock
|
128
|
+
# @yield
|
129
|
+
# @raise [AlreadyLocked] if already locked and LOCK_NB operation is specified
|
130
|
+
# @return [Object] the result of block
|
131
|
+
def synchronize(operation)
|
132
|
+
raise 'Must be called with a block' unless block_given?
|
133
|
+
|
134
|
+
begin
|
135
|
+
raise AlreadyLocked if lock(operation) == false
|
136
|
+
yield
|
137
|
+
ensure
|
138
|
+
unlock
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Boolean] true if locked by self
|
143
|
+
def self_locked?
|
144
|
+
@mutex.synchronize { @counts[Thread.current] >= 1 }
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def lock(operation)
|
150
|
+
c = incr
|
151
|
+
if c <= 1
|
152
|
+
# flock returns 0 if successfully locked.
|
153
|
+
# LOCK_NB returns false if somebody else already locked
|
154
|
+
fp.flock(operation)
|
155
|
+
else
|
156
|
+
0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def unlock
|
161
|
+
c = decr
|
162
|
+
if c <= 0
|
163
|
+
fp.flock(File::LOCK_UN)
|
164
|
+
del
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def incr
|
169
|
+
@mutex.synchronize { @counts[Thread.current] += 1 }
|
170
|
+
end
|
171
|
+
|
172
|
+
def decr
|
173
|
+
@mutex.synchronize { @counts[Thread.current] -= 1 }
|
174
|
+
end
|
175
|
+
|
176
|
+
def del
|
177
|
+
@mutex.synchronize { @counts.delete(Thread.current) }
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "reentrant_flock/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "reentrant_flock"
|
8
|
+
spec.version = ReentrantFlock::VERSION
|
9
|
+
spec.authors = ["Naotoshi Seo"]
|
10
|
+
spec.email = ["sonots@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A reentrant/recursive flock.}
|
13
|
+
spec.description = %q{A reentrant/recursive flock.}
|
14
|
+
spec.homepage = "https://github.com/sonots/reentrant_flock"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "test-unit"
|
26
|
+
spec.add_development_dependency "test-unit-power_assert"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reentrant_flock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Naotoshi Seo
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: test-unit-power_assert
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A reentrant/recursive flock.
|
70
|
+
email:
|
71
|
+
- sonots@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- CHANGELOG.md
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- lib/reentrant_flock.rb
|
85
|
+
- lib/reentrant_flock/version.rb
|
86
|
+
- reentrant_flock.gemspec
|
87
|
+
homepage: https://github.com/sonots/reentrant_flock
|
88
|
+
licenses: []
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.6.13
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: A reentrant/recursive flock.
|
110
|
+
test_files: []
|