just_one_lock 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/lib/just_one_lock.rb +17 -7
- data/lib/just_one_lock/version.rb +1 -1
- data/spec/just_one_lock_spec.rb +57 -54
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4555943b102ddd4bac3f2c732d54194126999e29
|
4
|
+
data.tar.gz: 56f6671b415251bb76ee48aa37b68155dcf2bfc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 991fdcbbc3634896fb0e83a68f51fe7ffa24c90b124894a3977b5ee6779d5b76efdb3ebbd2dcce7f06a4e5a27c178ce8b973940cad8aadbf824806d6716921ab
|
7
|
+
data.tar.gz: 77d27fdd32f70e77a11b3ff915d9b3ee8fcd064505fac4ab7a968d9829529825b000bc6352788aeefc53956edff10f77c8ea038e89eac3e36f399d6022427c82
|
data/Gemfile.lock
CHANGED
data/lib/just_one_lock.rb
CHANGED
@@ -2,22 +2,32 @@ require 'just_one_lock/version'
|
|
2
2
|
require 'timeout'
|
3
3
|
|
4
4
|
module JustOneLock
|
5
|
-
|
5
|
+
DEFAULT_TIMEOUT = 0.01
|
6
|
+
class AlreadyLocked < StandardError; end
|
7
|
+
|
8
|
+
class NullStream
|
9
|
+
class << self
|
10
|
+
def puts(str); end
|
11
|
+
def <<(o); self; end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.filelock(lockname, timeout: JustOneLock::DEFAULT_TIMEOUT, &block)
|
6
16
|
File.open(lockname, File::RDWR|File::CREAT, 0644) do |file|
|
7
|
-
Timeout::timeout(
|
17
|
+
Timeout::timeout(timeout, JustOneLock::AlreadyLocked) { file.flock(File::LOCK_EX) }
|
18
|
+
|
8
19
|
yield
|
9
20
|
end
|
10
21
|
end
|
11
22
|
|
12
|
-
def self.prevent_multiple_executions(lock_dir, scope, &block)
|
23
|
+
def self.prevent_multiple_executions(lock_dir, scope, output: JustOneLock::NullStream, timeout: JustOneLock::DEFAULT_TIMEOUT, &block)
|
13
24
|
scope_name = scope.gsub(':', '_')
|
14
25
|
lock_path = File.join(lock_dir, scope_name + '.lock')
|
15
26
|
|
16
27
|
begin
|
17
|
-
return filelock(lock_path, &block)
|
18
|
-
rescue
|
19
|
-
puts "Another process <#{scope}> already is running"
|
20
|
-
exit(1)
|
28
|
+
return filelock(lock_path, timeout: timeout, &block)
|
29
|
+
rescue JustOneLock::AlreadyLocked => e
|
30
|
+
output.puts "Another process <#{scope}> already is running"
|
21
31
|
end
|
22
32
|
end
|
23
33
|
end
|
data/spec/just_one_lock_spec.rb
CHANGED
@@ -9,25 +9,32 @@ describe JustOneLock do
|
|
9
9
|
File.open(filename.to_s, 'w') { |f| f.write(contents) }
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
12
|
+
def dir_and_scope(lockpath)
|
13
|
+
path_segments = lockpath.split('/')
|
14
|
+
scope = path_segments.last
|
15
|
+
path_segments.delete scope
|
16
|
+
dir = path_segments.join('/')
|
17
|
+
[dir, scope]
|
18
|
+
end
|
15
19
|
|
20
|
+
def parallel(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::DEFAULT_TIMEOUT, &block)
|
21
|
+
Timeout::timeout(5) do
|
22
|
+
dir, scope = dir_and_scope(lockpath)
|
16
23
|
(1..n).map do
|
17
24
|
Thread.new do
|
18
|
-
JustOneLock.
|
25
|
+
JustOneLock.prevent_multiple_executions(dir, scope, timeout: timeout, &block)
|
19
26
|
end
|
20
27
|
end.map(&:join)
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
|
-
def parallel_forks(n = 2,
|
31
|
+
def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::DEFAULT_TIMEOUT, &block)
|
25
32
|
Timeout::timeout(5) do
|
26
|
-
|
33
|
+
dir, scope = dir_and_scope(lockpath)
|
27
34
|
|
28
35
|
(1..n).map do
|
29
36
|
fork {
|
30
|
-
JustOneLock.
|
37
|
+
JustOneLock.prevent_multiple_executions(dir, scope, timeout: timeout, &block)
|
31
38
|
}
|
32
39
|
end.map do |pid|
|
33
40
|
Process.waitpid(pid)
|
@@ -66,19 +73,18 @@ describe JustOneLock do
|
|
66
73
|
|
67
74
|
parallel(2) do
|
68
75
|
value = answer
|
69
|
-
sleep
|
76
|
+
sleep(JustOneLock::DEFAULT_TIMEOUT / 2)
|
70
77
|
answer = value + 21
|
71
78
|
end
|
72
79
|
|
73
80
|
expect(answer).to eq(42)
|
74
81
|
end
|
75
82
|
|
76
|
-
it '
|
83
|
+
it 'handles high amount of concurrent tasks' do
|
77
84
|
answer = 0
|
78
85
|
|
79
86
|
parallel(100) do
|
80
87
|
value = answer
|
81
|
-
sleep 0.001
|
82
88
|
answer = value + 1
|
83
89
|
end
|
84
90
|
|
@@ -86,33 +92,29 @@ describe JustOneLock do
|
|
86
92
|
end
|
87
93
|
|
88
94
|
it 'creates lock file on disk during block execution' do
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
expect(File.exist?(lockpath)).to eq(true)
|
93
|
-
end
|
95
|
+
lockpath = Tempfile.new(['sample', '.lock']).path
|
96
|
+
parallel(2, lockpath: lockpath) do
|
97
|
+
expect(File.exist?(lockpath)).to eq(true)
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
97
101
|
it 'runs in parallel without race condition' do
|
98
|
-
|
99
|
-
lockpath = File.join(dir, 'sample.lock')
|
102
|
+
lockpath = Tempfile.new(['sample', '.lock']).path
|
100
103
|
|
101
|
-
|
102
|
-
|
103
|
-
begin
|
104
|
-
JustOneLock.filelock(lockpath) do
|
105
|
-
raise '42'
|
106
|
-
end
|
107
|
-
rescue RuntimeError
|
108
|
-
end
|
104
|
+
answer = 0
|
109
105
|
|
106
|
+
begin
|
110
107
|
JustOneLock.filelock(lockpath) do
|
111
|
-
|
108
|
+
raise '42'
|
112
109
|
end
|
110
|
+
rescue RuntimeError
|
111
|
+
end
|
113
112
|
|
114
|
-
|
113
|
+
JustOneLock.filelock(lockpath) do
|
114
|
+
answer += 42
|
115
115
|
end
|
116
|
+
|
117
|
+
expect(answer).to eq(42)
|
116
118
|
end
|
117
119
|
|
118
120
|
it 'times out after specified number of seconds' do
|
@@ -139,7 +141,7 @@ describe JustOneLock do
|
|
139
141
|
JustOneLock.filelock lockpath, timeout: 0.001 do
|
140
142
|
answer = 0
|
141
143
|
end
|
142
|
-
end.to raise_error(
|
144
|
+
end.to raise_error(JustOneLock::AlreadyLocked)
|
143
145
|
|
144
146
|
expect(answer).to eq(42)
|
145
147
|
end
|
@@ -153,7 +155,7 @@ describe JustOneLock do
|
|
153
155
|
|
154
156
|
parallel_forks(6) do
|
155
157
|
number = File.read('/tmp/number.txt').to_i
|
156
|
-
sleep
|
158
|
+
sleep(JustOneLock::DEFAULT_TIMEOUT / 100)
|
157
159
|
write('/tmp/number.txt', (number + 7).to_s)
|
158
160
|
end
|
159
161
|
|
@@ -165,46 +167,47 @@ describe JustOneLock do
|
|
165
167
|
it 'should handle heavy forking' do
|
166
168
|
write('/tmp/number.txt', '0')
|
167
169
|
|
168
|
-
|
170
|
+
FORKS_NUMBER = 100
|
171
|
+
parallel_forks(FORKS_NUMBER, timeout: 1) do
|
169
172
|
number = File.read('/tmp/number.txt').to_i
|
170
|
-
sleep 0.001
|
171
173
|
write('/tmp/number.txt', (number + 1).to_s)
|
172
174
|
end
|
173
175
|
|
174
176
|
number = File.read('/tmp/number.txt').to_i
|
175
177
|
|
176
|
-
expect(number).to eq(
|
178
|
+
expect(number).to eq(FORKS_NUMBER)
|
177
179
|
end
|
178
180
|
|
179
181
|
it 'should unblock files when killing processes' do
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
Dir.mktmpdir do |dir|
|
184
|
-
pid = fork {
|
185
|
-
JustOneLock.filelock lockpath do
|
186
|
-
sleep 10
|
187
|
-
end
|
188
|
-
}
|
182
|
+
lockpath = Tempfile.new(['sample', '.lock']).path
|
183
|
+
dir, scope = dir_and_scope(lockpath)
|
189
184
|
|
190
|
-
|
185
|
+
Dir.mktmpdir do |dir|
|
186
|
+
pid = fork {
|
187
|
+
JustOneLock.prevent_multiple_executions(dir, scope) do
|
188
|
+
sleep 10
|
189
|
+
end
|
190
|
+
}
|
191
191
|
|
192
|
-
|
192
|
+
Timeout::timeout(1) do
|
193
|
+
while !File.exist?(lockpath)
|
194
|
+
sleep 0.1
|
195
|
+
end
|
196
|
+
end
|
193
197
|
|
194
|
-
|
195
|
-
JustOneLock.filelock lockpath do
|
196
|
-
answer += 42
|
197
|
-
end
|
198
|
-
}
|
198
|
+
answer = 0
|
199
199
|
|
200
|
-
|
200
|
+
thread = Thread.new {
|
201
|
+
JustOneLock.prevent_multiple_executions(dir, scope) do
|
202
|
+
answer += 42
|
203
|
+
end
|
204
|
+
}
|
201
205
|
|
202
|
-
|
203
|
-
|
204
|
-
|
206
|
+
expect(answer).to eq(0)
|
207
|
+
Process.kill(9, pid)
|
208
|
+
thread.join
|
205
209
|
|
206
|
-
|
207
|
-
end
|
210
|
+
expect(answer).to eq(42)
|
208
211
|
end
|
209
212
|
end
|
210
213
|
|