multiprocessing 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 +7 -0
- data/.gitignore +3 -1
- data/.yardopts +4 -0
- data/README.ja.md +121 -0
- data/README.md +85 -91
- data/Rakefile +24 -1
- data/lib/multiprocessing.rb +41 -1
- data/lib/multiprocessing/conditionvariable.rb +82 -60
- data/lib/multiprocessing/externalobject.rb +5 -3
- data/lib/multiprocessing/mutex.rb +126 -96
- data/lib/multiprocessing/processerror.rb +5 -0
- data/lib/multiprocessing/queue.rb +95 -27
- data/lib/multiprocessing/semaphore.rb +78 -27
- data/lib/multiprocessing/version.rb +12 -1
- data/spec/multiprocessing/conditionvariable_spec.rb +206 -0
- data/spec/multiprocessing/mutex_spec.rb +468 -74
- data/spec/multiprocessing/queue_spec.rb +341 -97
- data/spec/multiprocessing/semaphore_spec.rb +136 -33
- data/spec/spec_helper.rb +15 -1
- metadata +11 -12
- data/lib/multiprocessing/process.rb +0 -43
- data/spec/multiprocessing/process_spec.rb +0 -51
@@ -2,33 +2,66 @@ require File.expand_path(File.dirname(__FILE__) + '/mutex')
|
|
2
2
|
require File.expand_path(File.dirname(__FILE__) + '/conditionvariable')
|
3
3
|
|
4
4
|
module MultiProcessing
|
5
|
+
|
6
|
+
##
|
7
|
+
#
|
8
|
+
# Like Mutex but can manage multiple resources.
|
9
|
+
#
|
10
|
+
# Note that Semaphore uses 4 pipes( 1 pipe, 1 Mutex, 1 ConditionVariable).
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# require 'multiprocessing'
|
14
|
+
#
|
15
|
+
# s = MultiProcessing::Semaphore.new 2
|
16
|
+
# 3.times do
|
17
|
+
# fork do
|
18
|
+
# s.synchronize do
|
19
|
+
# puts "pid: #{Process.pid}"
|
20
|
+
# sleep 1
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# Process.waitall
|
25
|
+
# # => 2 processes prints its pid immediately
|
26
|
+
# # but the other does late.
|
27
|
+
#
|
5
28
|
class Semaphore
|
6
29
|
|
30
|
+
##
|
31
|
+
#
|
32
|
+
# A new instance of Semaphore
|
33
|
+
#
|
34
|
+
# @param [Fixnum] count is initial number of resource
|
35
|
+
#
|
7
36
|
def initialize count
|
8
37
|
@count_pout, @count_pin = IO.pipe
|
9
38
|
@count_pin.syswrite "1"*count
|
10
|
-
#@count_pin.write "1"*count
|
11
|
-
#@count_pin.flush
|
12
39
|
@mutex = Mutex.new
|
13
40
|
@cond = ConditionVariable.new
|
14
41
|
end
|
15
42
|
|
16
43
|
def count_nonsynchronize
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
44
|
+
MultiProcessing.try_handle_interrupt(RuntimeError => :never) do
|
45
|
+
n = 0
|
46
|
+
begin
|
47
|
+
loop do
|
48
|
+
@count_pout.read_nonblock 1
|
49
|
+
n += 1
|
50
|
+
end
|
51
|
+
rescue Errno::EAGAIN
|
52
|
+
@count_pin.syswrite "1"*n
|
22
53
|
end
|
23
|
-
|
24
|
-
@count_pin.syswrite "1"*n
|
25
|
-
#@count_pin.write "1"*n
|
26
|
-
#@count_pin.flush
|
54
|
+
n
|
27
55
|
end
|
28
|
-
return n
|
29
56
|
end
|
30
57
|
private :count_nonsynchronize
|
31
58
|
|
59
|
+
##
|
60
|
+
#
|
61
|
+
# Returns current number of resources.
|
62
|
+
#
|
63
|
+
# @return [Fixnum]
|
64
|
+
#
|
32
65
|
def count
|
33
66
|
@mutex.synchronize do
|
34
67
|
count_nonsynchronize
|
@@ -36,6 +69,12 @@ module MultiProcessing
|
|
36
69
|
end
|
37
70
|
alias :value :count
|
38
71
|
|
72
|
+
##
|
73
|
+
#
|
74
|
+
# Attempts to get the resource and wait if it isn't available.
|
75
|
+
#
|
76
|
+
# @return [Semaphore] self
|
77
|
+
#
|
39
78
|
def P
|
40
79
|
@mutex.synchronize do
|
41
80
|
while count_nonsynchronize == 0
|
@@ -48,6 +87,13 @@ module MultiProcessing
|
|
48
87
|
alias :lock :P
|
49
88
|
alias :wait :P
|
50
89
|
|
90
|
+
##
|
91
|
+
#
|
92
|
+
# Attempts to get the resource and returns immediately
|
93
|
+
# Returns true if the resource granted.
|
94
|
+
#
|
95
|
+
# @return [Boolean]
|
96
|
+
#
|
51
97
|
def try_P
|
52
98
|
begin
|
53
99
|
@mutex.synchronize do
|
@@ -61,36 +107,41 @@ module MultiProcessing
|
|
61
107
|
alias :try_lock :try_P
|
62
108
|
alias :try_wait :try_P
|
63
109
|
|
110
|
+
##
|
111
|
+
#
|
112
|
+
# Releases the resource.
|
113
|
+
#
|
114
|
+
# @return [Semaphore] self
|
115
|
+
#
|
64
116
|
def V
|
65
|
-
|
66
|
-
@
|
67
|
-
|
68
|
-
|
69
|
-
|
117
|
+
MultiProcessing.try_handle_interrupt(RuntimeError => :never) do
|
118
|
+
@mutex.synchronize do
|
119
|
+
@count_pin.syswrite 1
|
120
|
+
@cond.signal
|
121
|
+
end
|
122
|
+
self
|
70
123
|
end
|
71
|
-
return self
|
72
124
|
end
|
73
125
|
alias :signal :V
|
74
126
|
alias :unlock :V
|
75
127
|
alias :post :V
|
76
128
|
|
129
|
+
##
|
130
|
+
#
|
131
|
+
# Obtains a resource, runs the block, and releases the resource when the block completes.
|
132
|
+
#
|
133
|
+
# @return [Object] returned value of the block
|
134
|
+
#
|
77
135
|
def synchronize
|
78
136
|
self.P
|
79
137
|
begin
|
80
|
-
yield
|
138
|
+
ret = yield
|
81
139
|
ensure
|
82
140
|
self.V
|
83
141
|
end
|
142
|
+
ret
|
84
143
|
end
|
85
144
|
|
86
145
|
end
|
87
146
|
end
|
88
147
|
|
89
|
-
if $0 == __FILE__
|
90
|
-
s = MultiProcessing::Semaphore.new 1
|
91
|
-
fork
|
92
|
-
s.wait
|
93
|
-
puts Process.pid
|
94
|
-
sleep 1
|
95
|
-
s.post
|
96
|
-
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'timeout'
|
3
|
+
require 'thwait'
|
4
|
+
|
5
|
+
describe MultiProcessing::ConditionVariable do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@cond = MultiProcessing::ConditionVariable.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "being waited by no process" do
|
12
|
+
|
13
|
+
describe "#signal" do
|
14
|
+
it "returns false" do
|
15
|
+
@cond.signal.should be_false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#broadcast" do
|
20
|
+
it "returns 0" do
|
21
|
+
@cond.broadcast.should == 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context "being waited by one process" do
|
28
|
+
|
29
|
+
before do
|
30
|
+
@pid = fork do
|
31
|
+
mutex = MultiProcessing::Mutex.new
|
32
|
+
mutex.synchronize do
|
33
|
+
@cond.wait(mutex)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@detached_thread = Process.detach(@pid)
|
37
|
+
sleep 0.01
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#signal" do
|
41
|
+
|
42
|
+
it "returns true" do
|
43
|
+
@cond.signal.should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "makes waiting process restart" do
|
47
|
+
@cond.signal
|
48
|
+
timeout(1){ @detached_thread.value }.success?.should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#broadcast" do
|
54
|
+
|
55
|
+
it "returns number of waited processes" do
|
56
|
+
@cond.broadcast.should == 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it "makes waiting process restart" do
|
60
|
+
@cond.broadcast
|
61
|
+
timeout(1){ @detached_thread.value }.success?.should be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
after do
|
67
|
+
begin
|
68
|
+
Process.kill(:TERM, @pid)
|
69
|
+
rescue Errno::ESRCH
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
context "being waited by multiple processes" do
|
76
|
+
|
77
|
+
before do
|
78
|
+
Process.waitall
|
79
|
+
@pid1 = fork do
|
80
|
+
mutex = MultiProcessing::Mutex.new
|
81
|
+
mutex.synchronize do
|
82
|
+
@cond.wait(mutex)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@pid2 = fork do
|
86
|
+
mutex = MultiProcessing::Mutex.new
|
87
|
+
mutex.synchronize do
|
88
|
+
@cond.wait(mutex)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
@detached_thread1 = Process.detach(@pid1)
|
92
|
+
@detached_thread2 = Process.detach(@pid2)
|
93
|
+
sleep 0.01
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#signal" do
|
97
|
+
|
98
|
+
it "returns true" do
|
99
|
+
@cond.signal.should be_true
|
100
|
+
end
|
101
|
+
|
102
|
+
it "makes waiting process restart" do
|
103
|
+
@cond.signal
|
104
|
+
threads = [@detached_thread1, @detached_thread2]
|
105
|
+
thwait = ThreadsWait.new(threads)
|
106
|
+
timeout(1){ thwait.next_wait.value }.success?.should be_true
|
107
|
+
thwait.threads[0].should be_alive
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#broadcast" do
|
113
|
+
|
114
|
+
it "returns number of waited process" do
|
115
|
+
@cond.broadcast.should == 2
|
116
|
+
end
|
117
|
+
|
118
|
+
it "makes waiting process restart" do
|
119
|
+
@cond.broadcast
|
120
|
+
timeout(1){ @detached_thread1.value }.success?.should be_true
|
121
|
+
timeout(1){ @detached_thread2.value }.success?.should be_true
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
after do
|
127
|
+
begin
|
128
|
+
Process.kill(:TERM, @pid1)
|
129
|
+
rescue Errno::ESRCH
|
130
|
+
end
|
131
|
+
begin
|
132
|
+
Process.kill(:TERM,@pid2)
|
133
|
+
rescue Errno::ESRCH
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "#wait" do
|
140
|
+
|
141
|
+
context "called with MultiProcessing::Mutex" do
|
142
|
+
|
143
|
+
before do
|
144
|
+
@mutex = MultiProcessing::Mutex.new
|
145
|
+
@pid = fork do
|
146
|
+
sleep 0.02
|
147
|
+
@cond.signal
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "until signal" do
|
152
|
+
it "blocks" do
|
153
|
+
@mutex.synchronize do
|
154
|
+
proc{timeout(0.01){@cond.wait(@mutex)}}.should raise_error Timeout::Error
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "after signal" do
|
160
|
+
it "restarts" do
|
161
|
+
@mutex.synchronize do
|
162
|
+
proc{timeout(0.03){@cond.wait(@mutex)}}.should_not raise_error Timeout::Error
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
after do
|
168
|
+
begin
|
169
|
+
Process.kill :KILL, @pid
|
170
|
+
rescue Errno::ESRCH
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
context "called with object except MultiProcessing::Mutex" do
|
177
|
+
it "raises MultiProcessing::ProcessError" do
|
178
|
+
proc{@cond.wait(Object.new)}.should raise_error TypeError
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "called with unlocked Mutex" do
|
183
|
+
|
184
|
+
it "raises MultiProcessing::ProcessError" do
|
185
|
+
mutex = MultiProcessing::Mutex.new
|
186
|
+
proc{@cond.wait(mutex)}.should raise_error ArgumentError
|
187
|
+
end
|
188
|
+
|
189
|
+
it "still works" do
|
190
|
+
mutex = MultiProcessing::Mutex.new
|
191
|
+
begin
|
192
|
+
@cond.wait(mutex)
|
193
|
+
rescue ArgumentError
|
194
|
+
end
|
195
|
+
@cond.signal.should be_false
|
196
|
+
# this means that inner variables are in the state that no process are waiting it
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
|
@@ -7,103 +7,497 @@ describe MultiProcessing::Mutex do
|
|
7
7
|
@mutex = MultiProcessing::Mutex.new
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
@mutex.lock.should === @mutex
|
12
|
-
end
|
10
|
+
describe "#lock" do
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
12
|
+
it "returns itself" do
|
13
|
+
@mutex.lock.should === @mutex
|
14
|
+
end
|
18
15
|
|
19
|
-
|
20
|
-
proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
|
21
|
-
end
|
16
|
+
context "when locked in same thread" do
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
before do
|
19
|
+
@mutex.lock
|
20
|
+
end
|
26
21
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
22
|
+
it "raises ProcessError" do
|
23
|
+
proc{ @mutex.lock }.should raise_error MultiProcessing::ProcessError
|
24
|
+
end
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@mutex.lock
|
36
|
-
@mutex.unlock
|
37
|
-
end
|
38
|
-
@mutex.lock
|
39
|
-
sleep 0.2
|
40
|
-
process.join(0).should be_nil
|
41
|
-
@mutex.unlock
|
42
|
-
proc{ timeout(1){ process.join } }.should_not raise_error Timeout::Error
|
43
|
-
end
|
26
|
+
after do
|
27
|
+
@mutex.unlock
|
28
|
+
end
|
44
29
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when locked in another thread but same process" do
|
33
|
+
|
34
|
+
before do
|
35
|
+
Thread.new do
|
36
|
+
@mutex.lock
|
37
|
+
end
|
38
|
+
sleep 0.01
|
39
|
+
end
|
40
|
+
|
41
|
+
it "blocks" do
|
42
|
+
proc{ timeout(0.01){ @mutex.lock } }.should raise_error Timeout::Error
|
50
43
|
end
|
44
|
+
|
51
45
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
|
47
|
+
context "when locked in another process" do
|
48
|
+
|
49
|
+
before do
|
50
|
+
fork do
|
51
|
+
@mutex.lock
|
52
|
+
sleep 0.03
|
53
|
+
@mutex.unlock
|
54
|
+
end
|
55
|
+
sleep 0.01
|
56
|
+
end
|
57
|
+
|
58
|
+
it "blocks" do
|
59
|
+
proc{ timeout(0.01){ @mutex.lock } }.should raise_error Timeout::Error
|
60
|
+
end
|
61
|
+
|
55
62
|
end
|
56
|
-
|
63
|
+
|
57
64
|
end
|
58
65
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
describe "#owned?" do
|
67
|
+
|
68
|
+
context "initial state" do
|
69
|
+
it "returns false" do
|
70
|
+
@mutex.should_not be_owned
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "during locked by current thread" do
|
75
|
+
it "returns true" do
|
76
|
+
@mutex.lock
|
77
|
+
@mutex.should be_owned
|
78
|
+
@mutex.unlock
|
79
|
+
@mutex.synchronize do
|
80
|
+
@mutex.should be_owned
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "during locked by another process" do
|
86
|
+
|
87
|
+
before do
|
88
|
+
fork do
|
89
|
+
@mutex.synchronize do
|
90
|
+
sleep 0.02
|
91
|
+
end
|
92
|
+
end
|
93
|
+
sleep 0.01
|
94
|
+
end
|
95
|
+
|
96
|
+
it "returns false" do
|
97
|
+
@mutex.should_not be_owned
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
context "after unlocked" do
|
103
|
+
|
104
|
+
before do
|
105
|
+
fork do
|
106
|
+
@mutex.synchronize do
|
107
|
+
sleep 0.01
|
108
|
+
end
|
109
|
+
end
|
110
|
+
sleep 0.02
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns false" do
|
114
|
+
@mutex.should_not be_owned
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
64
119
|
end
|
65
120
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
121
|
+
describe "#unlock" do
|
122
|
+
|
123
|
+
context "when locked in same thread" do
|
124
|
+
|
125
|
+
before do
|
126
|
+
@mutex.lock
|
127
|
+
end
|
128
|
+
|
129
|
+
it "return itself" do
|
130
|
+
@mutex.unlock.should === @mutex
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when another thread in same process is blocked" do
|
134
|
+
|
135
|
+
before do
|
136
|
+
@thread = Thread.new do
|
137
|
+
@mutex.lock
|
138
|
+
@mutex.unlock
|
139
|
+
end
|
140
|
+
sleep 0.01
|
141
|
+
end
|
142
|
+
|
143
|
+
it "makes the other blocked thread restart" do
|
144
|
+
@mutex.unlock
|
145
|
+
@thread.join(0.01).should_not be_nil
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
context "when another thread in another process is blocked" do
|
151
|
+
|
152
|
+
before do
|
153
|
+
@pid = fork do
|
154
|
+
@mutex.lock
|
155
|
+
@mutex.unlock
|
156
|
+
end
|
157
|
+
@detached_thread = Process.detach(@pid)
|
158
|
+
sleep 0.01
|
159
|
+
end
|
160
|
+
|
161
|
+
it "makes the other blocked thread restart" do
|
162
|
+
@mutex.unlock
|
163
|
+
timeout(1){ @detached_thread.value }.should be_success
|
164
|
+
end
|
165
|
+
|
166
|
+
after do
|
167
|
+
begin
|
168
|
+
Process.kill :TERM, @pid
|
169
|
+
rescue Errno::ESRCH
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
context "when locked in another thread but same process" do
|
179
|
+
|
180
|
+
before do
|
181
|
+
Thread.new do
|
182
|
+
@mutex.lock
|
183
|
+
sleep 0.02
|
184
|
+
@mutex.unlock
|
185
|
+
end
|
186
|
+
sleep 0.01
|
187
|
+
end
|
188
|
+
|
189
|
+
it "raise ProcessError" do
|
190
|
+
proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
|
191
|
+
end
|
192
|
+
|
193
|
+
it "is still locked" do
|
194
|
+
begin
|
195
|
+
@mutex.unlock
|
196
|
+
rescue MultiProcessing::ProcessError
|
197
|
+
end
|
198
|
+
@mutex.should be_locked
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
context "when locked in another process" do
|
204
|
+
|
205
|
+
before do
|
206
|
+
@pid = fork do
|
207
|
+
@mutex.lock
|
208
|
+
sleep 0.02
|
209
|
+
@mutex.unlock
|
210
|
+
end
|
211
|
+
sleep 0.01
|
212
|
+
end
|
213
|
+
|
214
|
+
it "raises ProcessError" do
|
215
|
+
proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
|
216
|
+
end
|
217
|
+
|
218
|
+
it "is still locked" do
|
219
|
+
begin
|
220
|
+
@mutex.unlock
|
221
|
+
rescue MultiProcessing::ProcessError
|
222
|
+
end
|
223
|
+
@mutex.should be_locked
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when not locked" do
|
229
|
+
it "raises ProcessError" do
|
230
|
+
proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context "forked on locking" do
|
235
|
+
|
236
|
+
context "forked process calls #unlock before forking process calls it" do
|
237
|
+
it "raises ProcessError at unlocking on forked process" do
|
238
|
+
@mutex.lock
|
239
|
+
pid = fork do
|
240
|
+
begin
|
241
|
+
@mutex.unlock
|
242
|
+
rescue MultiProcessing::ProcessError
|
243
|
+
exit! true # OK
|
244
|
+
end
|
245
|
+
exit! false # NG
|
246
|
+
end
|
247
|
+
sleep 0.01
|
248
|
+
@mutex.should be_locked
|
249
|
+
@mutex.unlock
|
250
|
+
timeout(1){ Process.detach(pid).value }.should be_success
|
251
|
+
@mutex.should_not be_locked
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context "forking process calls #unlock before forked process calls it" do
|
256
|
+
it "raises ProcessError at unlocking on forked process" do
|
257
|
+
@mutex.lock
|
258
|
+
pid = fork do
|
259
|
+
begin
|
260
|
+
sleep 0.01
|
261
|
+
@mutex.unlock
|
262
|
+
rescue MultiProcessing::ProcessError
|
263
|
+
exit! true # OK
|
264
|
+
end
|
265
|
+
exit! false # NG
|
266
|
+
end
|
267
|
+
@mutex.unlock
|
268
|
+
timeout(1){ Process.detach(pid).value }.should be_success
|
269
|
+
@mutex.should_not be_locked
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
71
273
|
end
|
72
|
-
|
73
|
-
proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
|
74
|
-
process.value.success?.should be_true
|
274
|
+
|
75
275
|
end
|
76
276
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
277
|
+
|
278
|
+
describe "#locked?" do
|
279
|
+
|
280
|
+
context "when not locked" do
|
281
|
+
it "returns false" do
|
282
|
+
@mutex.locked?.should be_false
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "when locked" do
|
287
|
+
it "returns true" do
|
288
|
+
fork{ @mutex.lock }
|
289
|
+
sleep 0.01
|
290
|
+
@mutex.locked?.should be_true
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
81
294
|
end
|
82
295
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
296
|
+
describe "#try_lock" do
|
297
|
+
|
298
|
+
context "when not locked" do
|
299
|
+
|
300
|
+
it "returns true" do
|
301
|
+
@mutex.try_lock.should be_true
|
302
|
+
end
|
303
|
+
|
304
|
+
it "gets locked" do
|
305
|
+
fork{ @mutex.try_lock }
|
306
|
+
sleep 0.01
|
307
|
+
@mutex.should be_locked
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
context "when locked" do
|
313
|
+
|
314
|
+
before do
|
315
|
+
fork do
|
316
|
+
@mutex.lock
|
317
|
+
sleep 0.02
|
318
|
+
end
|
319
|
+
sleep 0.01
|
320
|
+
end
|
321
|
+
|
322
|
+
it "returns false" do
|
323
|
+
@mutex.try_lock.should be_false
|
324
|
+
end
|
325
|
+
|
326
|
+
it "is still locked" do
|
327
|
+
@mutex.try_lock
|
328
|
+
@mutex.should be_locked
|
329
|
+
end
|
330
|
+
|
87
331
|
end
|
88
|
-
sleep 0.1
|
89
|
-
@mutex.try_lock.should be_false
|
90
332
|
end
|
91
333
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
334
|
+
describe "#synchronize" do
|
335
|
+
|
336
|
+
context "synchronizing in current thread" do
|
337
|
+
|
338
|
+
it "returns a value returned by block" do
|
339
|
+
obj = Object.new
|
340
|
+
@mutex.synchronize {
|
341
|
+
obj
|
342
|
+
}.should === obj
|
343
|
+
end
|
344
|
+
|
345
|
+
context "processing block" do
|
346
|
+
it "is locked" do
|
347
|
+
@mutex.synchronize do
|
348
|
+
@mutex.should be_locked
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
context "after block" do
|
354
|
+
it "is unlocked" do
|
355
|
+
@mutex.synchronize{ :nop }
|
356
|
+
@mutex.should_not be_locked
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
context "raised in block" do
|
361
|
+
it "raised same error" do
|
362
|
+
proc{ @mutex.synchronize{ raise StandardError.new } }.should raise_error StandardError
|
363
|
+
end
|
364
|
+
|
365
|
+
it "is unlocked" do
|
366
|
+
begin
|
367
|
+
@mutex.synchronize{ raise StandardError.new }
|
368
|
+
rescue StandardError
|
369
|
+
end
|
370
|
+
@mutex.should_not be_locked
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
context "synchronizing in other process" do
|
376
|
+
|
377
|
+
context "processing block" do
|
378
|
+
it "is locked" do
|
379
|
+
fork do
|
380
|
+
@mutex.synchronize do
|
381
|
+
sleep 0.02
|
382
|
+
end
|
383
|
+
end
|
384
|
+
sleep 0.01
|
385
|
+
@mutex.should be_locked
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
context "after block" do
|
390
|
+
it "is unlocked" do
|
391
|
+
fork do
|
392
|
+
@mutex.synchronize{ :nop }
|
393
|
+
end
|
394
|
+
sleep 0.01
|
395
|
+
@mutex.should_not be_locked
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
end
|
400
|
+
|
401
|
+
context "forked on locking" do
|
402
|
+
|
403
|
+
context "forked process exits synchronize block before forking process" do
|
404
|
+
it "raises ProcessError at unlocking on forked process" do
|
405
|
+
pid = nil
|
406
|
+
begin
|
407
|
+
@mutex.synchronize do
|
408
|
+
pid = fork
|
409
|
+
sleep 0.01 if pid # for forked process exiting sychronize block before forking process
|
410
|
+
end
|
411
|
+
rescue MultiProcessing::ProcessError
|
412
|
+
if !pid
|
413
|
+
exit! true # OK
|
414
|
+
end
|
415
|
+
end
|
416
|
+
if !pid
|
417
|
+
exit! false # NG
|
418
|
+
end
|
419
|
+
@mutex.should_not be_locked
|
420
|
+
timeout(1){ Process.detach(pid).value }.should be_success
|
421
|
+
@mutex.should_not be_locked
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
context "forking process exits synchronize block before forked process" do
|
426
|
+
it "raises ProcessError at unlocking on forked process" do
|
427
|
+
pid = nil
|
428
|
+
begin
|
429
|
+
@mutex.synchronize do
|
430
|
+
pid = fork
|
431
|
+
sleep 0.01 if !pid # for forking process exiting sychronize block before forked process
|
432
|
+
end
|
433
|
+
rescue MultiProcessing::ProcessError
|
434
|
+
if !pid
|
435
|
+
exit! true # OK
|
436
|
+
end
|
437
|
+
end
|
438
|
+
if !pid
|
439
|
+
exit! false # NG
|
440
|
+
end
|
441
|
+
@mutex.should_not be_locked
|
442
|
+
timeout(1){ Process.detach(pid).value }.should be_success
|
443
|
+
@mutex.should_not be_locked
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
|
105
449
|
end
|
106
450
|
|
451
|
+
describe "#sleep" do
|
452
|
+
|
453
|
+
context "another thread waits the lock" do
|
454
|
+
|
455
|
+
before do
|
456
|
+
@pid = fork do
|
457
|
+
sleep 0.02
|
458
|
+
@mutex.lock
|
459
|
+
@mutex.unlock
|
460
|
+
end
|
461
|
+
@detached_thread = Process.detach(@pid)
|
462
|
+
sleep 0.01
|
463
|
+
end
|
464
|
+
|
465
|
+
it "unlocks before sleep" do
|
466
|
+
@mutex.synchronize do
|
467
|
+
@mutex.sleep 0.01
|
468
|
+
end
|
469
|
+
timeout(1){ @detached_thread.value }.should be_success
|
470
|
+
end
|
471
|
+
|
472
|
+
it "re-locks after sleep" do
|
473
|
+
@mutex.synchronize do
|
474
|
+
@mutex.sleep 0.01
|
475
|
+
@mutex.should be_locked
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
after do
|
480
|
+
begin
|
481
|
+
Process.kill :TERM, @pid
|
482
|
+
rescue Errno::ESRCH
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|
487
|
+
|
488
|
+
context "error occurs in sleep" do
|
489
|
+
it "re-locks" do
|
490
|
+
@mutex.synchronize do
|
491
|
+
begin
|
492
|
+
timeout(0.01){ @mutex.sleep }
|
493
|
+
rescue Timeout::Error
|
494
|
+
end
|
495
|
+
@mutex.should be_locked
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
end
|
107
501
|
|
108
502
|
end
|
109
503
|
|