just_one_lock 0.0.2 → 0.0.3
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/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/lib/just_one_lock.rb +20 -17
- data/lib/just_one_lock/blocking.rb +42 -0
- data/lib/just_one_lock/non_blocking.rb +37 -0
- data/lib/just_one_lock/version.rb +1 -1
- data/spec/just_one_lock/blocking_spec.rb +80 -0
- data/spec/just_one_lock/locking_object.rb +168 -0
- data/spec/just_one_lock/non_blocking_spec.rb +38 -0
- data/spec/just_one_lock_spec.rb +0 -243
- data/spec/spec_helper.rb +16 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef790c43dd30dabc1e75e662d8e042ff0a49c08f
|
4
|
+
data.tar.gz: 77ef1490b78280b803160512b96beaa8aeea376f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f33614e6f935dfbb3040e64f09590342988ebe8566ebaca24c315d180bd73040723d165f9ede1c1804fedde2285c4d9b257ac9924ddbc84ae5d2cf94db8f477
|
7
|
+
data.tar.gz: 68a3d589c4545bdcba5e5467861d8120687e69b1b7438e6b0c5f6b86f7f0854d3674c68236f824da86e7e9f60c1151dad914100ff510beb5e275b20ea1209ee8
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/lib/just_one_lock.rb
CHANGED
@@ -1,33 +1,36 @@
|
|
1
1
|
require 'just_one_lock/version'
|
2
|
-
require '
|
2
|
+
require 'just_one_lock/blocking'
|
3
|
+
require 'just_one_lock/non_blocking'
|
3
4
|
|
4
5
|
module JustOneLock
|
5
|
-
DEFAULT_TIMEOUT = 0.01
|
6
|
-
class AlreadyLocked < StandardError; end
|
7
|
-
|
8
6
|
class NullStream
|
9
7
|
class << self
|
10
8
|
def puts(str); end
|
11
|
-
def <<(o); self; end
|
12
9
|
end
|
13
10
|
end
|
14
11
|
|
15
|
-
|
16
|
-
File.open(lockname, File::RDWR|File::CREAT, 0644) do |file|
|
17
|
-
Timeout::timeout(timeout, JustOneLock::AlreadyLocked) { file.flock(File::LOCK_EX) }
|
12
|
+
@files = {}
|
18
13
|
|
19
|
-
|
14
|
+
def self.delete_unlocked_files
|
15
|
+
@files.each do |path, f|
|
16
|
+
if File.exists?(path) && f.closed?
|
17
|
+
File.delete(path) rescue nil
|
18
|
+
end
|
20
19
|
end
|
21
20
|
end
|
22
21
|
|
23
|
-
|
24
|
-
scope_name = scope.gsub(':', '_')
|
25
|
-
lock_path = File.join(lock_dir, scope_name + '.lock')
|
22
|
+
private
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
def self.write_pid(f)
|
25
|
+
f.rewind
|
26
|
+
f.write(Process.pid)
|
27
|
+
f.flush
|
28
|
+
f.truncate(f.pos)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.run(f, lockname, &block)
|
32
|
+
@files[lockname] = f
|
33
|
+
write_pid(f)
|
34
|
+
block.call
|
32
35
|
end
|
33
36
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module JustOneLock::Blocking
|
4
|
+
DEFAULT_TIMEOUT = 1
|
5
|
+
class AlreadyLocked < StandardError; end
|
6
|
+
|
7
|
+
def self.filelock(
|
8
|
+
lockname,
|
9
|
+
timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT,
|
10
|
+
delete_files: true,
|
11
|
+
&block
|
12
|
+
)
|
13
|
+
result = nil
|
14
|
+
File.open(lockname, File::RDWR|File::CREAT, 0644) do |f|
|
15
|
+
Timeout::timeout(timeout, JustOneLock::Blocking::AlreadyLocked) { f.flock(File::LOCK_EX) }
|
16
|
+
|
17
|
+
result = JustOneLock.run(f, lockname, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
JustOneLock.delete_unlocked_files if delete_files
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.prevent_multiple_executions(
|
25
|
+
lock_dir,
|
26
|
+
scope,
|
27
|
+
output: JustOneLock::NullStream,
|
28
|
+
timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT,
|
29
|
+
delete_files: true,
|
30
|
+
&block
|
31
|
+
)
|
32
|
+
scope_name = scope.gsub(':', '_')
|
33
|
+
lock_path = File.join(lock_dir, scope_name + '.lock')
|
34
|
+
|
35
|
+
begin
|
36
|
+
return filelock(lock_path, timeout: timeout, delete_files: delete_files, &block)
|
37
|
+
rescue JustOneLock::Blocking::AlreadyLocked => e
|
38
|
+
output.puts "Another process <#{scope}> already is running"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module JustOneLock::NonBlocking
|
2
|
+
def self.filelock(
|
3
|
+
lockname,
|
4
|
+
delete_files: true,
|
5
|
+
&block
|
6
|
+
)
|
7
|
+
result = nil
|
8
|
+
|
9
|
+
File.open(lockname, File::RDWR|File::CREAT, 0644) do |f|
|
10
|
+
if f.flock(File::LOCK_NB|File::LOCK_EX)
|
11
|
+
result = JustOneLock.run(f, lockname, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
JustOneLock.delete_unlocked_files if delete_files
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.prevent_multiple_executions(
|
21
|
+
lock_dir,
|
22
|
+
scope,
|
23
|
+
output: JustOneLock::NullStream,
|
24
|
+
&block
|
25
|
+
)
|
26
|
+
scope_name = scope.gsub(':', '_')
|
27
|
+
lock_path = File.join(lock_dir, scope_name + '.lock')
|
28
|
+
|
29
|
+
was_executed = filelock(lock_path, &block)
|
30
|
+
|
31
|
+
unless was_executed
|
32
|
+
output.puts "Another process <#{scope}> already is running"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'just_one_lock'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'timeout'
|
4
|
+
require 'just_one_lock/locking_object'
|
5
|
+
|
6
|
+
describe JustOneLock::Blocking do
|
7
|
+
it_behaves_like 'a locking object'
|
8
|
+
|
9
|
+
def parallel(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT, &block)
|
10
|
+
Timeout::timeout(5) do
|
11
|
+
dir, scope = dir_and_scope(lockpath)
|
12
|
+
(1..n).map do
|
13
|
+
Thread.new do
|
14
|
+
JustOneLock::Blocking.prevent_multiple_executions(dir, scope, timeout: timeout, delete_files: false, &block)
|
15
|
+
end
|
16
|
+
end.map(&:join)
|
17
|
+
end
|
18
|
+
|
19
|
+
JustOneLock.delete_unlocked_files
|
20
|
+
end
|
21
|
+
|
22
|
+
def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT, &block)
|
23
|
+
Timeout::timeout(5) do
|
24
|
+
dir, scope = dir_and_scope(lockpath)
|
25
|
+
|
26
|
+
(1..n).map do
|
27
|
+
fork {
|
28
|
+
JustOneLock::Blocking.prevent_multiple_executions(dir, scope, timeout: timeout, delete_files: false, &block)
|
29
|
+
}
|
30
|
+
end.map do |pid|
|
31
|
+
Process.waitpid(pid)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
JustOneLock.delete_unlocked_files
|
36
|
+
end
|
37
|
+
|
38
|
+
# Java doesn't support forking
|
39
|
+
if RUBY_PLATFORM != 'java'
|
40
|
+
it 'should work for multiple processes' do
|
41
|
+
write('/tmp/number.txt', '0')
|
42
|
+
|
43
|
+
parallel_forks(6, timeout: 10) do
|
44
|
+
number = File.read('/tmp/number.txt').to_i
|
45
|
+
sleep(JustOneLock::Blocking::DEFAULT_TIMEOUT / 100)
|
46
|
+
write('/tmp/number.txt', (number + 7).to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
number = File.read('/tmp/number.txt').to_i
|
50
|
+
|
51
|
+
expect(number).to eq(42)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should handle heavy forking' do
|
55
|
+
write('/tmp/number.txt', '0')
|
56
|
+
|
57
|
+
FORKS_NUMBER = 100
|
58
|
+
parallel_forks(FORKS_NUMBER, timeout: 10) do
|
59
|
+
number = File.read('/tmp/number.txt').to_i
|
60
|
+
write('/tmp/number.txt', (number + 1).to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
number = File.read('/tmp/number.txt').to_i
|
64
|
+
|
65
|
+
expect(number).to eq(FORKS_NUMBER)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'handles high amount of concurrent tasks' do
|
69
|
+
answer = 0
|
70
|
+
|
71
|
+
parallel(100, timeout: JustOneLock::Blocking::DEFAULT_TIMEOUT * 10) do
|
72
|
+
value = answer
|
73
|
+
answer = value + 1
|
74
|
+
end
|
75
|
+
|
76
|
+
expect(answer).to eq(100)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples 'a locking object' do
|
4
|
+
it 'runs simple ruby block as usual' do
|
5
|
+
Dir.mktmpdir do |lock_dir|
|
6
|
+
lockpath = File.join(lock_dir, 'sample.lock')
|
7
|
+
answer = 0
|
8
|
+
|
9
|
+
JustOneLock::Blocking.filelock lockpath do
|
10
|
+
answer += 42
|
11
|
+
end
|
12
|
+
|
13
|
+
expect(answer).to eq(42)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns value returned by block' do
|
18
|
+
Dir.mktmpdir do |lock_dir|
|
19
|
+
lockpath = File.join(lock_dir, 'sample.lock')
|
20
|
+
answer = 0
|
21
|
+
|
22
|
+
answer = JustOneLock::Blocking.filelock lockpath do
|
23
|
+
42
|
24
|
+
end
|
25
|
+
|
26
|
+
expect(answer).to eq(42)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'runs in parallel without race condition' do
|
31
|
+
answer = 0
|
32
|
+
|
33
|
+
parallel(2) do
|
34
|
+
value = answer
|
35
|
+
sleep(JustOneLock::Blocking::DEFAULT_TIMEOUT / 2)
|
36
|
+
answer = value + 21
|
37
|
+
end
|
38
|
+
|
39
|
+
expect(answer).to eq(42)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'creates lock file on disk during block execution' do
|
43
|
+
lockpath = Tempfile.new(['sample', '.lock']).path
|
44
|
+
parallel(2, lockpath: lockpath) do
|
45
|
+
expect(File.exist?(lockpath)).to eq(true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'runs in parallel without race condition' do
|
50
|
+
lockpath = Tempfile.new(['sample', '.lock']).path
|
51
|
+
|
52
|
+
answer = 0
|
53
|
+
|
54
|
+
begin
|
55
|
+
JustOneLock::Blocking.filelock(lockpath) do
|
56
|
+
raise '42'
|
57
|
+
end
|
58
|
+
rescue RuntimeError
|
59
|
+
end
|
60
|
+
|
61
|
+
JustOneLock::Blocking.filelock(lockpath) do
|
62
|
+
answer += 42
|
63
|
+
end
|
64
|
+
|
65
|
+
expect(answer).to eq(42)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'times out after specified number of seconds' do
|
69
|
+
Dir.mktmpdir do |lock_dir|
|
70
|
+
lockpath = File.join(lock_dir, 'sample.lock')
|
71
|
+
|
72
|
+
answer = 42
|
73
|
+
locked = false
|
74
|
+
|
75
|
+
Thread.new do
|
76
|
+
JustOneLock::Blocking.filelock lockpath do
|
77
|
+
locked = true
|
78
|
+
sleep 20
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Timeout::timeout(1) do
|
83
|
+
while locked == false
|
84
|
+
sleep 0.1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
expect do
|
89
|
+
JustOneLock::Blocking.filelock lockpath, timeout: 0.001 do
|
90
|
+
answer = 0
|
91
|
+
end
|
92
|
+
end.to raise_error(JustOneLock::Blocking::AlreadyLocked)
|
93
|
+
|
94
|
+
expect(answer).to eq(42)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Java doesn't support forking
|
99
|
+
if RUBY_PLATFORM != 'java'
|
100
|
+
|
101
|
+
it 'should unblock files when killing processes' do
|
102
|
+
lockpath = Tempfile.new(['sample', '.lock']).path
|
103
|
+
dir, scope = dir_and_scope(lockpath)
|
104
|
+
|
105
|
+
pid = fork {
|
106
|
+
JustOneLock::Blocking.prevent_multiple_executions(lock_dir, scope) do
|
107
|
+
sleep 10
|
108
|
+
end
|
109
|
+
}
|
110
|
+
|
111
|
+
Timeout::timeout(1) do
|
112
|
+
while !File.exist?(lockpath)
|
113
|
+
sleep 0.1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
answer = 0
|
118
|
+
|
119
|
+
thread = Thread.new {
|
120
|
+
JustOneLock::Blocking.prevent_multiple_executions(dir, scope) do
|
121
|
+
answer += 42
|
122
|
+
end
|
123
|
+
}
|
124
|
+
|
125
|
+
expect(answer).to eq(0)
|
126
|
+
Process.kill(9, pid)
|
127
|
+
thread.join
|
128
|
+
|
129
|
+
expect(answer).to eq(42)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should handle Pathname as well as string path' do
|
133
|
+
Dir.mktmpdir do |lock_dir|
|
134
|
+
lockpath = Pathname.new(File.join(lock_dir, 'sample.lock'))
|
135
|
+
|
136
|
+
answer = 0
|
137
|
+
JustOneLock::Blocking.filelock lockpath do
|
138
|
+
answer += 42
|
139
|
+
end
|
140
|
+
|
141
|
+
expect(answer).to eq(42)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# It failed for 1.8.7 (cannot convert to String)
|
147
|
+
it 'works for Tempfile' do
|
148
|
+
answer = 0
|
149
|
+
|
150
|
+
JustOneLock::Blocking.filelock Tempfile.new(['sample', '.lock']) do
|
151
|
+
answer += 42
|
152
|
+
end
|
153
|
+
|
154
|
+
expect(answer).to eq(42)
|
155
|
+
end
|
156
|
+
|
157
|
+
# If implemented the wrong way lock is created elsewhere
|
158
|
+
it 'creates file with exact path provided' do
|
159
|
+
filename = "/tmp/awesome-lock-#{rand}.lock"
|
160
|
+
|
161
|
+
JustOneLock::Blocking.filelock filename, delete_files: false do
|
162
|
+
end
|
163
|
+
|
164
|
+
expect(File.exist?(filename)).to eq(true)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'just_one_lock'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'timeout'
|
4
|
+
require 'just_one_lock/locking_object'
|
5
|
+
|
6
|
+
describe JustOneLock::NonBlocking do
|
7
|
+
it_behaves_like 'a locking object'
|
8
|
+
|
9
|
+
def parallel(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, &block)
|
10
|
+
Timeout::timeout(5) do
|
11
|
+
dir, scope = dir_and_scope(lockpath)
|
12
|
+
(1..n).map do
|
13
|
+
Thread.new do
|
14
|
+
JustOneLock::Blocking.prevent_multiple_executions(dir, scope, delete_files: false, &block)
|
15
|
+
end
|
16
|
+
end.map(&:join)
|
17
|
+
end
|
18
|
+
|
19
|
+
JustOneLock.delete_unlocked_files
|
20
|
+
end
|
21
|
+
|
22
|
+
def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, &block)
|
23
|
+
Timeout::timeout(5) do
|
24
|
+
dir, scope = dir_and_scope(lockpath)
|
25
|
+
|
26
|
+
(1..n).map do
|
27
|
+
fork {
|
28
|
+
JustOneLock::Blocking.prevent_multiple_executions(dir, scope, delete_files: false, &block)
|
29
|
+
}
|
30
|
+
end.map do |pid|
|
31
|
+
Process.waitpid(pid)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
JustOneLock.delete_unlocked_files
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
data/spec/just_one_lock_spec.rb
CHANGED
@@ -3,247 +3,4 @@ require 'tempfile'
|
|
3
3
|
require 'timeout'
|
4
4
|
|
5
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 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
|
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)
|
23
|
-
(1..n).map do
|
24
|
-
Thread.new do
|
25
|
-
JustOneLock.prevent_multiple_executions(dir, scope, timeout: timeout, &block)
|
26
|
-
end
|
27
|
-
end.map(&:join)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def parallel_forks(n = 2, lockpath: Tempfile.new(['sample', '.lock']).path, timeout: JustOneLock::DEFAULT_TIMEOUT, &block)
|
32
|
-
Timeout::timeout(5) do
|
33
|
-
dir, scope = dir_and_scope(lockpath)
|
34
|
-
|
35
|
-
(1..n).map do
|
36
|
-
fork {
|
37
|
-
JustOneLock.prevent_multiple_executions(dir, scope, timeout: timeout, &block)
|
38
|
-
}
|
39
|
-
end.map do |pid|
|
40
|
-
Process.waitpid(pid)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'runs simple ruby block as usual' do
|
46
|
-
Dir.mktmpdir do |dir|
|
47
|
-
lockpath = File.join(dir, 'sample.lock')
|
48
|
-
answer = 0
|
49
|
-
|
50
|
-
JustOneLock.filelock lockpath do
|
51
|
-
answer += 42
|
52
|
-
end
|
53
|
-
|
54
|
-
expect(answer).to eq(42)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'returns value returned by block' do
|
59
|
-
Dir.mktmpdir do |dir|
|
60
|
-
lockpath = File.join(dir, 'sample.lock')
|
61
|
-
answer = 0
|
62
|
-
|
63
|
-
answer = JustOneLock.filelock lockpath do
|
64
|
-
42
|
65
|
-
end
|
66
|
-
|
67
|
-
expect(answer).to eq(42)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'runs in parallel without race condition' do
|
72
|
-
answer = 0
|
73
|
-
|
74
|
-
parallel(2) do
|
75
|
-
value = answer
|
76
|
-
sleep(JustOneLock::DEFAULT_TIMEOUT / 2)
|
77
|
-
answer = value + 21
|
78
|
-
end
|
79
|
-
|
80
|
-
expect(answer).to eq(42)
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'handles high amount of concurrent tasks' do
|
84
|
-
answer = 0
|
85
|
-
|
86
|
-
parallel(100) do
|
87
|
-
value = answer
|
88
|
-
answer = value + 1
|
89
|
-
end
|
90
|
-
|
91
|
-
expect(answer).to eq(100)
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'creates lock file on disk during block execution' do
|
95
|
-
lockpath = Tempfile.new(['sample', '.lock']).path
|
96
|
-
parallel(2, lockpath: lockpath) do
|
97
|
-
expect(File.exist?(lockpath)).to eq(true)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'runs in parallel without race condition' do
|
102
|
-
lockpath = Tempfile.new(['sample', '.lock']).path
|
103
|
-
|
104
|
-
answer = 0
|
105
|
-
|
106
|
-
begin
|
107
|
-
JustOneLock.filelock(lockpath) do
|
108
|
-
raise '42'
|
109
|
-
end
|
110
|
-
rescue RuntimeError
|
111
|
-
end
|
112
|
-
|
113
|
-
JustOneLock.filelock(lockpath) do
|
114
|
-
answer += 42
|
115
|
-
end
|
116
|
-
|
117
|
-
expect(answer).to eq(42)
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'times out after specified number of seconds' do
|
121
|
-
Dir.mktmpdir do |dir|
|
122
|
-
lockpath = File.join(dir, 'sample.lock')
|
123
|
-
|
124
|
-
answer = 42
|
125
|
-
locked = false
|
126
|
-
|
127
|
-
Thread.new do
|
128
|
-
JustOneLock.filelock lockpath do
|
129
|
-
locked = true
|
130
|
-
sleep 20
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
Timeout::timeout(1) do
|
135
|
-
while locked == false
|
136
|
-
sleep 0.1
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
expect do
|
141
|
-
JustOneLock.filelock lockpath, timeout: 0.001 do
|
142
|
-
answer = 0
|
143
|
-
end
|
144
|
-
end.to raise_error(JustOneLock::AlreadyLocked)
|
145
|
-
|
146
|
-
expect(answer).to eq(42)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# Java doesn't support forking
|
151
|
-
if RUBY_PLATFORM != 'java'
|
152
|
-
|
153
|
-
it 'should work for multiple processes' do
|
154
|
-
write('/tmp/number.txt', '0')
|
155
|
-
|
156
|
-
parallel_forks(6) do
|
157
|
-
number = File.read('/tmp/number.txt').to_i
|
158
|
-
sleep(JustOneLock::DEFAULT_TIMEOUT / 100)
|
159
|
-
write('/tmp/number.txt', (number + 7).to_s)
|
160
|
-
end
|
161
|
-
|
162
|
-
number = File.read('/tmp/number.txt').to_i
|
163
|
-
|
164
|
-
expect(number).to eq(42)
|
165
|
-
end
|
166
|
-
|
167
|
-
it 'should handle heavy forking' do
|
168
|
-
write('/tmp/number.txt', '0')
|
169
|
-
|
170
|
-
FORKS_NUMBER = 100
|
171
|
-
parallel_forks(FORKS_NUMBER, timeout: 1) do
|
172
|
-
number = File.read('/tmp/number.txt').to_i
|
173
|
-
write('/tmp/number.txt', (number + 1).to_s)
|
174
|
-
end
|
175
|
-
|
176
|
-
number = File.read('/tmp/number.txt').to_i
|
177
|
-
|
178
|
-
expect(number).to eq(FORKS_NUMBER)
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'should unblock files when killing processes' do
|
182
|
-
lockpath = Tempfile.new(['sample', '.lock']).path
|
183
|
-
dir, scope = dir_and_scope(lockpath)
|
184
|
-
|
185
|
-
Dir.mktmpdir do |dir|
|
186
|
-
pid = fork {
|
187
|
-
JustOneLock.prevent_multiple_executions(dir, scope) do
|
188
|
-
sleep 10
|
189
|
-
end
|
190
|
-
}
|
191
|
-
|
192
|
-
Timeout::timeout(1) do
|
193
|
-
while !File.exist?(lockpath)
|
194
|
-
sleep 0.1
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
answer = 0
|
199
|
-
|
200
|
-
thread = Thread.new {
|
201
|
-
JustOneLock.prevent_multiple_executions(dir, scope) do
|
202
|
-
answer += 42
|
203
|
-
end
|
204
|
-
}
|
205
|
-
|
206
|
-
expect(answer).to eq(0)
|
207
|
-
Process.kill(9, pid)
|
208
|
-
thread.join
|
209
|
-
|
210
|
-
expect(answer).to eq(42)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'should handle Pathname as well as string path' do
|
215
|
-
Dir.mktmpdir do |dir|
|
216
|
-
lockpath = Pathname.new(File.join(dir, 'sample.lock'))
|
217
|
-
|
218
|
-
answer = 0
|
219
|
-
JustOneLock.filelock lockpath do
|
220
|
-
answer += 42
|
221
|
-
end
|
222
|
-
|
223
|
-
expect(answer).to eq(42)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
end
|
228
|
-
|
229
|
-
# It failed for 1.8.7 (cannot convert to String)
|
230
|
-
it 'works for Tempfile' do
|
231
|
-
answer = 0
|
232
|
-
|
233
|
-
JustOneLock.filelock Tempfile.new(['sample', '.lock']) do
|
234
|
-
answer += 42
|
235
|
-
end
|
236
|
-
|
237
|
-
expect(answer).to eq(42)
|
238
|
-
end
|
239
|
-
|
240
|
-
# If implemented the wrong way lock is created elsewhere
|
241
|
-
it 'creates file with exact path provided' do
|
242
|
-
filename = "/tmp/awesome-lock-#{rand}.lock"
|
243
|
-
|
244
|
-
JustOneLock.filelock filename do
|
245
|
-
end
|
246
|
-
|
247
|
-
expect(File.exist?(filename)).to eq(true)
|
248
|
-
end
|
249
6
|
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
def lock_dir
|
2
|
+
'/tmp'
|
3
|
+
end
|
4
|
+
|
5
|
+
# Helper because File.write won't work for older Ruby
|
6
|
+
def write(filename, contents)
|
7
|
+
File.open(filename.to_s, 'w') { |f| f.write(contents) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def dir_and_scope(lockpath)
|
11
|
+
path_segments = lockpath.split('/')
|
12
|
+
scope = path_segments.last
|
13
|
+
path_segments.delete scope
|
14
|
+
dir = path_segments.join('/')
|
15
|
+
[dir, scope]
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: just_one_lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Stankiewicz, Yury Kotov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,8 +69,14 @@ files:
|
|
69
69
|
- Rakefile
|
70
70
|
- just_one_lock.gemspec
|
71
71
|
- lib/just_one_lock.rb
|
72
|
+
- lib/just_one_lock/blocking.rb
|
73
|
+
- lib/just_one_lock/non_blocking.rb
|
72
74
|
- lib/just_one_lock/version.rb
|
75
|
+
- spec/just_one_lock/blocking_spec.rb
|
76
|
+
- spec/just_one_lock/locking_object.rb
|
77
|
+
- spec/just_one_lock/non_blocking_spec.rb
|
73
78
|
- spec/just_one_lock_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
74
80
|
homepage: http://github.com/beorc/just_one_lock
|
75
81
|
licenses:
|
76
82
|
- MIT
|
@@ -96,4 +102,8 @@ signing_key:
|
|
96
102
|
specification_version: 4
|
97
103
|
summary: Simple solution to prevent multiple executions using flock
|
98
104
|
test_files:
|
105
|
+
- spec/just_one_lock/blocking_spec.rb
|
106
|
+
- spec/just_one_lock/locking_object.rb
|
107
|
+
- spec/just_one_lock/non_blocking_spec.rb
|
99
108
|
- spec/just_one_lock_spec.rb
|
109
|
+
- spec/spec_helper.rb
|