filelock 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +56 -0
- data/Rakefile +1 -0
- data/filelock.gemspec +24 -0
- data/lib/filelock.rb +21 -0
- data/lib/filelock/version.rb +3 -0
- data/spec/filelock_spec.rb +232 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ca5c3766ead1d38142a6a07fff24bb2b84eb1bf8
|
4
|
+
data.tar.gz: 7fa07b19de6de7a852412b07a3425d6992a0fbb2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cea750b7cf162bb5c7924a442a29acb02cb0ea7a2cd26baa29e3c3227e55a238bcf8ce6cc01bfca1d611f6b43aaa66481a6efeb4b00afea9e5389d62787997d7
|
7
|
+
data.tar.gz: 78f0673ea1fbadc2658c3e455b7d5eadb1eb5b6522313e6944bafc4314ec4d45fbcd5f2633cdefe1a36552d402c7fb06d1dc2a936ed7b4db257ccf22283e5aca
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Adam Stankiewicz
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Filelock [![Build Status][travis-img-url]][travis-url]
|
2
|
+
|
3
|
+
[travis-img-url]: https://travis-ci.org/sheerun/filelock.png
|
4
|
+
[travis-url]: https://travis-ci-org/sheerun/filelock
|
5
|
+
|
6
|
+
Heavily tested, but simple filelocking solution using [flock](http://linux.die.net/man/2/flock) command. It guarantees unlocking of files.
|
7
|
+
|
8
|
+
It works for sure on MRI 1.8, 1.9, 2.0, JRuby in both 1.8 and 1.9 mode, and Rubinius.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
Filelock '/tmp/path/to/lock' do
|
14
|
+
# do blocking operation
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
You can also pass the timeout for blocking operation (default is 60 seconds):
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
Filelock '/tmp/path/to/lock', :timeout => 10 do
|
22
|
+
# do blocking operation
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
Note that lock file directory must already exist, and lock file is not removed after unlock.
|
27
|
+
|
28
|
+
## FAQ
|
29
|
+
|
30
|
+
*Does it support NFS?*
|
31
|
+
|
32
|
+
No. You can use more complex [lockfile](https://github.com/ahoward/lockfile) gem if you want to support NFS.
|
33
|
+
|
34
|
+
*The code is so short. Why shouln't I just copy-paste it?*
|
35
|
+
|
36
|
+
Because even such short code can have issues in future. File locking is very fragile operation. You may expect new releases of this gem fixing discovered bogus behavior (or introducing awesome features).
|
37
|
+
|
38
|
+
You are encouraged to use it if you develop gem that uses flock command, and care about running it on different ruby versions and platforms. Each has its own quirks with regard to flock command.
|
39
|
+
|
40
|
+
*How it's different from [lockfile](https://github.com/ahoward/lockfile) gem?*
|
41
|
+
|
42
|
+
Lockfile is filelocking solution handling NFS filesystems, based on homemade locking solution. Filelock uses [flock](http://linux.die.net/man/2/flock) UNIX command to handle filelocking on very low level. Also lockfile allows you to specify retry timeout. In case of Ruby's flock command this is hard-cored to 0.1 seconds.
|
43
|
+
|
44
|
+
*How it's different from [cleverua-lockfile](https://github.com/cleverua/lockfile) gem?*
|
45
|
+
|
46
|
+
Cleverua removes lockfile after unlocking it. Thas has been proven fatal both in my tests and in [filelocking advices from the Internet](http://world.std.com/~swmcd/steven/tech/flock.html). You could try find a way to remove lock file without breaking Filelock tests. I will be glad to accept such pull-request.
|
47
|
+
|
48
|
+
## Challenge
|
49
|
+
|
50
|
+
Please try to break Filelock in some way (note it doesn't support NFS).
|
51
|
+
|
52
|
+
If you show at least one failing test, I'll put your name below:
|
53
|
+
|
54
|
+
## License
|
55
|
+
|
56
|
+
Filelock is MIT-licensed. You are awesome.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/filelock.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'filelock/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "filelock"
|
8
|
+
spec.version = Filelock::VERSION
|
9
|
+
spec.authors = ["Adam Stankiewicz"]
|
10
|
+
spec.email = ["sheerun@sher.pl"]
|
11
|
+
spec.description = %q{Heavily tested yet simple filelocking solution using flock}
|
12
|
+
spec.summary = %q{Heavily tested yet simple filelocking solution using flock}
|
13
|
+
spec.homepage = "http://github.com/sheerun/filelock"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
data/lib/filelock.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'filelock/version'
|
2
|
+
require 'timeout'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
if RUBY_PLATFORM == "java"
|
6
|
+
def Filelock(lockname, options = {}, &block)
|
7
|
+
lockname = lockname.path if lockname.is_a?(Tempfile)
|
8
|
+
File.open(lockname, File::RDWR|File::CREAT, 0644) do |file|
|
9
|
+
Thread.pass until file.flock(File::LOCK_EX)
|
10
|
+
Timeout::timeout(options.fetch(:timeout, 60)) { yield }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
else
|
14
|
+
def Filelock(lockname, options = {}, &block)
|
15
|
+
lockname = lockname.path if lockname.is_a?(Tempfile)
|
16
|
+
File.open(lockname, File::RDWR|File::CREAT, 0644) do |file|
|
17
|
+
file.flock(File::LOCK_EX)
|
18
|
+
Timeout::timeout(options.fetch(:timeout, 60)) { yield }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'filelock'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
describe Filelock do
|
6
|
+
|
7
|
+
# Helper because File.write won't work for older Ruby
|
8
|
+
def write(filename, contents)
|
9
|
+
File.open(filename.to_s, 'w') { |f| f.write(contents) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def parallel(n = 2, templock = nil, &block)
|
13
|
+
Timeout::timeout(5) do
|
14
|
+
templock ||= Tempfile.new(['sample', '.lock'])
|
15
|
+
|
16
|
+
(1..n).map do
|
17
|
+
Thread.new do
|
18
|
+
Filelock(templock, &block)
|
19
|
+
end
|
20
|
+
end.map(&:join)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parallel_forks(n = 2, templock = nil, &block)
|
25
|
+
Timeout::timeout(5) do
|
26
|
+
templock ||= Tempfile.new(['sample', '.lock'])
|
27
|
+
|
28
|
+
(1..n).map do
|
29
|
+
fork {
|
30
|
+
Filelock(templock, &block)
|
31
|
+
}
|
32
|
+
end.map do |pid|
|
33
|
+
Process.waitpid(pid)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'runs simple ruby block as usual' do
|
39
|
+
Dir.mktmpdir do |dir|
|
40
|
+
lockpath = File.join(dir, 'sample.lock')
|
41
|
+
answer = 0
|
42
|
+
|
43
|
+
Filelock lockpath do
|
44
|
+
answer += 42
|
45
|
+
end
|
46
|
+
|
47
|
+
expect(answer).to eq(42)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns value returned by block' do
|
52
|
+
Dir.mktmpdir do |dir|
|
53
|
+
lockpath = File.join(dir, 'sample.lock')
|
54
|
+
answer = Filelock lockpath do
|
55
|
+
42
|
56
|
+
end
|
57
|
+
|
58
|
+
expect(answer).to eq(42)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'runs in parallel without race condition' do
|
63
|
+
answer = 0
|
64
|
+
|
65
|
+
parallel(2) do
|
66
|
+
value = answer
|
67
|
+
sleep 0.1
|
68
|
+
answer = value + 21
|
69
|
+
end
|
70
|
+
|
71
|
+
expect(answer).to eq(42)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'handels high amount of concurrent tasks' do
|
75
|
+
answer = 0
|
76
|
+
|
77
|
+
parallel(100) do
|
78
|
+
value = answer
|
79
|
+
sleep 0.001
|
80
|
+
answer = value + 1
|
81
|
+
end
|
82
|
+
|
83
|
+
expect(answer).to eq(100)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'creates lock file on disk during block execution' do
|
87
|
+
Dir.mktmpdir do |dir|
|
88
|
+
lockpath = File.join(dir, 'sample.lock')
|
89
|
+
parallel(2, lockpath) do
|
90
|
+
expect(File.exist?(lockpath)).to eq(true)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'runs in parallel without race condition' do
|
96
|
+
Dir.mktmpdir do |dir|
|
97
|
+
lockpath = File.join(dir, 'sample.lock')
|
98
|
+
|
99
|
+
answer = 0
|
100
|
+
|
101
|
+
begin
|
102
|
+
Filelock(lockpath) do
|
103
|
+
raise '42'
|
104
|
+
end
|
105
|
+
rescue RuntimeError
|
106
|
+
end
|
107
|
+
|
108
|
+
Filelock(lockpath) do
|
109
|
+
answer += 42
|
110
|
+
end
|
111
|
+
|
112
|
+
expect(answer).to eq(42)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'times out after specified number of seconds' do
|
117
|
+
Dir.mktmpdir do |dir|
|
118
|
+
lockpath = File.join(dir, 'sample.lock')
|
119
|
+
|
120
|
+
answer = 42
|
121
|
+
|
122
|
+
begin
|
123
|
+
Filelock lockpath, :timeout => 1 do
|
124
|
+
sleep 2
|
125
|
+
answer = 0
|
126
|
+
end
|
127
|
+
rescue Timeout::Error
|
128
|
+
end
|
129
|
+
|
130
|
+
expect(answer).to eq(42)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Java doesn't support forking
|
135
|
+
if RUBY_PLATFORM != 'java'
|
136
|
+
|
137
|
+
it 'should work for multiple processes' do
|
138
|
+
write('/tmp/number.txt', '0')
|
139
|
+
|
140
|
+
parallel_forks(6) do
|
141
|
+
number = File.read('/tmp/number.txt').to_i
|
142
|
+
sleep 0.3
|
143
|
+
write('/tmp/number.txt', (number + 7).to_s)
|
144
|
+
end
|
145
|
+
|
146
|
+
number = File.read('/tmp/number.txt').to_i
|
147
|
+
|
148
|
+
expect(number).to eq(42)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should handle heavy forking' do
|
152
|
+
write('/tmp/number.txt', '0')
|
153
|
+
|
154
|
+
parallel_forks(100) do
|
155
|
+
number = File.read('/tmp/number.txt').to_i
|
156
|
+
sleep 0.001
|
157
|
+
write('/tmp/number.txt', (number + 1).to_s)
|
158
|
+
end
|
159
|
+
|
160
|
+
number = File.read('/tmp/number.txt').to_i
|
161
|
+
|
162
|
+
expect(number).to eq(100)
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should unblock files when killing processes' do
|
166
|
+
Dir.mktmpdir do |dir|
|
167
|
+
lockpath = File.join(dir, 'sample.lock')
|
168
|
+
|
169
|
+
Dir.mktmpdir do |dir|
|
170
|
+
pid = fork {
|
171
|
+
Filelock lockpath do
|
172
|
+
sleep 10
|
173
|
+
end
|
174
|
+
}
|
175
|
+
|
176
|
+
sleep 0.5
|
177
|
+
|
178
|
+
answer = 0
|
179
|
+
|
180
|
+
thread = Thread.new {
|
181
|
+
Filelock lockpath do
|
182
|
+
answer += 42
|
183
|
+
end
|
184
|
+
}
|
185
|
+
|
186
|
+
sleep 0.5
|
187
|
+
|
188
|
+
expect(answer).to eq(0)
|
189
|
+
Process.kill(9, pid)
|
190
|
+
thread.join
|
191
|
+
|
192
|
+
expect(answer).to eq(42)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should handle Pathname as well as string path' do
|
198
|
+
Dir.mktmpdir do |dir|
|
199
|
+
lockpath = Pathname.new(File.join(dir, 'sample.lock'))
|
200
|
+
|
201
|
+
answer = 0
|
202
|
+
Filelock lockpath do
|
203
|
+
answer += 42
|
204
|
+
end
|
205
|
+
|
206
|
+
expect(answer).to eq(42)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
# It failed for 1.8.7 (cannot convert to String)
|
213
|
+
it 'works for Tempfile' do
|
214
|
+
answer = 0
|
215
|
+
|
216
|
+
Filelock Tempfile.new(['sample', '.lock']) do
|
217
|
+
answer += 42
|
218
|
+
end
|
219
|
+
|
220
|
+
expect(answer).to eq(42)
|
221
|
+
end
|
222
|
+
|
223
|
+
# If implemented the wrong way lock is created elsewhere
|
224
|
+
it 'creates file with exact path provided' do
|
225
|
+
filename = "/tmp/awesome-lock-#{rand}.lock"
|
226
|
+
|
227
|
+
Filelock filename do
|
228
|
+
end
|
229
|
+
|
230
|
+
expect(File.exist?(filename)).to eq(true)
|
231
|
+
end
|
232
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: filelock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Stankiewicz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-16 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.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
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
|
+
description: Heavily tested yet simple filelocking solution using flock
|
56
|
+
email:
|
57
|
+
- sheerun@sher.pl
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .rspec
|
63
|
+
- .travis.yml
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- filelock.gemspec
|
69
|
+
- lib/filelock.rb
|
70
|
+
- lib/filelock/version.rb
|
71
|
+
- spec/filelock_spec.rb
|
72
|
+
homepage: http://github.com/sheerun/filelock
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.0.3
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Heavily tested yet simple filelocking solution using flock
|
96
|
+
test_files:
|
97
|
+
- spec/filelock_spec.rb
|
98
|
+
has_rdoc:
|