just_one_lock 0.0.1 → 0.0.2
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 +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
|
|