just_one_lock 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +3 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/just_one_lock.gemspec +24 -0
- data/lib/just_one_lock.rb +23 -0
- data/lib/just_one_lock/version.rb +3 -0
- data/spec/just_one_lock_spec.rb +246 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bd505c4500de6a9d5ac9fca007c2d204bfe2f6a5
|
4
|
+
data.tar.gz: 2817838753a95afe43173d5e867adfb8f9006d8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 661c30672498de83b0380075cb0629e9d003ae132c41f906b3a90a171adfb6a0037f95f3fed7720a21fb16a08cddd545701cd26d4a6f4d7aee9046bffbb57439
|
7
|
+
data.tar.gz: ccc5ae6dfde9a056f54d551f2084c7d6796318611ad01ee9c48fc3058ba8decdf4f750c2362128b57829df75ca36b8eec3877d903bb89990baaa066a0ea5f219
|
data/.bundle/config
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
just_one_lock (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
rake (10.3.2)
|
11
|
+
rspec (3.0.0)
|
12
|
+
rspec-core (~> 3.0.0)
|
13
|
+
rspec-expectations (~> 3.0.0)
|
14
|
+
rspec-mocks (~> 3.0.0)
|
15
|
+
rspec-core (3.0.4)
|
16
|
+
rspec-support (~> 3.0.0)
|
17
|
+
rspec-expectations (3.0.4)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.0.0)
|
20
|
+
rspec-mocks (3.0.4)
|
21
|
+
rspec-support (~> 3.0.0)
|
22
|
+
rspec-support (3.0.4)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
bundler (~> 1.3)
|
29
|
+
just_one_lock!
|
30
|
+
rake
|
31
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 beorc
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -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 'just_one_lock/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "just_one_lock"
|
8
|
+
spec.version = JustOneLock::VERSION
|
9
|
+
spec.authors = ["Adam Stankiewicz, Yury Kotov"]
|
10
|
+
spec.email = ["bairkan@gmail.com"]
|
11
|
+
spec.description = %q{Simple solution to prevent multiple executions using flock}
|
12
|
+
spec.summary = %q{Simple solution to prevent multiple executions using flock}
|
13
|
+
spec.homepage = "http://github.com/beorc/just_one_lock"
|
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", '~> 10.3'
|
23
|
+
spec.add_development_dependency "rspec", '~> 3.0'
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'just_one_lock/version'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module JustOneLock
|
5
|
+
def self.filelock(lockname, options = {}, &block)
|
6
|
+
File.open(lockname, File::RDWR|File::CREAT, 0644) do |file|
|
7
|
+
Timeout::timeout(options.fetch(:timeout, 60)) { file.flock(File::LOCK_EX) }
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.prevent_multiple_executions(lock_dir, scope, &block)
|
13
|
+
scope_name = scope.gsub(':', '_')
|
14
|
+
lock_path = File.join(lock_dir, scope_name + '.lock')
|
15
|
+
|
16
|
+
begin
|
17
|
+
return filelock(lock_path, &block)
|
18
|
+
rescue Timeout::Error => e
|
19
|
+
puts "Another process <#{scope}> already is running"
|
20
|
+
exit(1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'just_one_lock'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
describe JustOneLock 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
|
+
JustOneLock.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
|
+
JustOneLock.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
|
+
JustOneLock.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 = 0
|
55
|
+
|
56
|
+
answer = JustOneLock.filelock lockpath do
|
57
|
+
42
|
58
|
+
end
|
59
|
+
|
60
|
+
expect(answer).to eq(42)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'runs in parallel without race condition' do
|
65
|
+
answer = 0
|
66
|
+
|
67
|
+
parallel(2) do
|
68
|
+
value = answer
|
69
|
+
sleep 0.1
|
70
|
+
answer = value + 21
|
71
|
+
end
|
72
|
+
|
73
|
+
expect(answer).to eq(42)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'handels high amount of concurrent tasks' do
|
77
|
+
answer = 0
|
78
|
+
|
79
|
+
parallel(100) do
|
80
|
+
value = answer
|
81
|
+
sleep 0.001
|
82
|
+
answer = value + 1
|
83
|
+
end
|
84
|
+
|
85
|
+
expect(answer).to eq(100)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'creates lock file on disk during block execution' do
|
89
|
+
Dir.mktmpdir do |dir|
|
90
|
+
lockpath = File.join(dir, 'sample.lock')
|
91
|
+
parallel(2, lockpath) do
|
92
|
+
expect(File.exist?(lockpath)).to eq(true)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'runs in parallel without race condition' do
|
98
|
+
Dir.mktmpdir do |dir|
|
99
|
+
lockpath = File.join(dir, 'sample.lock')
|
100
|
+
|
101
|
+
answer = 0
|
102
|
+
|
103
|
+
begin
|
104
|
+
JustOneLock.filelock(lockpath) do
|
105
|
+
raise '42'
|
106
|
+
end
|
107
|
+
rescue RuntimeError
|
108
|
+
end
|
109
|
+
|
110
|
+
JustOneLock.filelock(lockpath) do
|
111
|
+
answer += 42
|
112
|
+
end
|
113
|
+
|
114
|
+
expect(answer).to eq(42)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'times out after specified number of seconds' do
|
119
|
+
Dir.mktmpdir do |dir|
|
120
|
+
lockpath = File.join(dir, 'sample.lock')
|
121
|
+
|
122
|
+
answer = 42
|
123
|
+
locked = false
|
124
|
+
|
125
|
+
Thread.new do
|
126
|
+
JustOneLock.filelock lockpath do
|
127
|
+
locked = true
|
128
|
+
sleep 20
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
Timeout::timeout(1) do
|
133
|
+
while locked == false
|
134
|
+
sleep 0.1
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
expect do
|
139
|
+
JustOneLock.filelock lockpath, timeout: 0.001 do
|
140
|
+
answer = 0
|
141
|
+
end
|
142
|
+
end.to raise_error(Timeout::Error)
|
143
|
+
|
144
|
+
expect(answer).to eq(42)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Java doesn't support forking
|
149
|
+
if RUBY_PLATFORM != 'java'
|
150
|
+
|
151
|
+
it 'should work for multiple processes' do
|
152
|
+
write('/tmp/number.txt', '0')
|
153
|
+
|
154
|
+
parallel_forks(6) do
|
155
|
+
number = File.read('/tmp/number.txt').to_i
|
156
|
+
sleep 0.3
|
157
|
+
write('/tmp/number.txt', (number + 7).to_s)
|
158
|
+
end
|
159
|
+
|
160
|
+
number = File.read('/tmp/number.txt').to_i
|
161
|
+
|
162
|
+
expect(number).to eq(42)
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should handle heavy forking' do
|
166
|
+
write('/tmp/number.txt', '0')
|
167
|
+
|
168
|
+
parallel_forks(100) do
|
169
|
+
number = File.read('/tmp/number.txt').to_i
|
170
|
+
sleep 0.001
|
171
|
+
write('/tmp/number.txt', (number + 1).to_s)
|
172
|
+
end
|
173
|
+
|
174
|
+
number = File.read('/tmp/number.txt').to_i
|
175
|
+
|
176
|
+
expect(number).to eq(100)
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should unblock files when killing processes' do
|
180
|
+
Dir.mktmpdir do |dir|
|
181
|
+
lockpath = File.join(dir, 'sample.lock')
|
182
|
+
|
183
|
+
Dir.mktmpdir do |dir|
|
184
|
+
pid = fork {
|
185
|
+
JustOneLock.filelock lockpath do
|
186
|
+
sleep 10
|
187
|
+
end
|
188
|
+
}
|
189
|
+
|
190
|
+
sleep 0.5
|
191
|
+
|
192
|
+
answer = 0
|
193
|
+
|
194
|
+
thread = Thread.new {
|
195
|
+
JustOneLock.filelock lockpath do
|
196
|
+
answer += 42
|
197
|
+
end
|
198
|
+
}
|
199
|
+
|
200
|
+
sleep 0.5
|
201
|
+
|
202
|
+
expect(answer).to eq(0)
|
203
|
+
Process.kill(9, pid)
|
204
|
+
thread.join
|
205
|
+
|
206
|
+
expect(answer).to eq(42)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should handle Pathname as well as string path' do
|
212
|
+
Dir.mktmpdir do |dir|
|
213
|
+
lockpath = Pathname.new(File.join(dir, 'sample.lock'))
|
214
|
+
|
215
|
+
answer = 0
|
216
|
+
JustOneLock.filelock lockpath do
|
217
|
+
answer += 42
|
218
|
+
end
|
219
|
+
|
220
|
+
expect(answer).to eq(42)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
# It failed for 1.8.7 (cannot convert to String)
|
227
|
+
it 'works for Tempfile' do
|
228
|
+
answer = 0
|
229
|
+
|
230
|
+
JustOneLock.filelock Tempfile.new(['sample', '.lock']) do
|
231
|
+
answer += 42
|
232
|
+
end
|
233
|
+
|
234
|
+
expect(answer).to eq(42)
|
235
|
+
end
|
236
|
+
|
237
|
+
# If implemented the wrong way lock is created elsewhere
|
238
|
+
it 'creates file with exact path provided' do
|
239
|
+
filename = "/tmp/awesome-lock-#{rand}.lock"
|
240
|
+
|
241
|
+
JustOneLock.filelock filename do
|
242
|
+
end
|
243
|
+
|
244
|
+
expect(File.exist?(filename)).to eq(true)
|
245
|
+
end
|
246
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: just_one_lock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Stankiewicz, Yury Kotov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-28 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: '10.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Simple solution to prevent multiple executions using flock
|
56
|
+
email:
|
57
|
+
- bairkan@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".bundle/config"
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- Gemfile
|
66
|
+
- Gemfile.lock
|
67
|
+
- LICENSE
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- just_one_lock.gemspec
|
71
|
+
- lib/just_one_lock.rb
|
72
|
+
- lib/just_one_lock/version.rb
|
73
|
+
- spec/just_one_lock_spec.rb
|
74
|
+
homepage: http://github.com/beorc/just_one_lock
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.2.2
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Simple solution to prevent multiple executions using flock
|
98
|
+
test_files:
|
99
|
+
- spec/just_one_lock_spec.rb
|