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.
@@ -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
- n = 0
18
- begin
19
- loop do
20
- @count_pout.read_nonblock 1
21
- n += 1
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
- rescue Errno::EAGAIN
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
- @mutex.synchronize do
66
- @count_pin.syswrite 1
67
- #@count_pin.write 1
68
- #@count_pin.flush
69
- @cond.signal
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
@@ -1,3 +1,14 @@
1
1
  module MultiProcessing
2
- VERSION = "0.0.1"
2
+
3
+ ##
4
+ #
5
+ # A version of MultiProcessing library
6
+ #
7
+ # 0.0.1 (2012-02-13)
8
+ # : Initial release
9
+ #
10
+ # 0.0.2 (2012-07-14)
11
+ # : fixed many bugs
12
+ #
13
+ VERSION = "0.0.2"
3
14
  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
- it "#lock returns itself" do
11
- @mutex.lock.should === @mutex
12
- end
10
+ describe "#lock" do
13
11
 
14
- it "#unlock returns itself if locked" do
15
- @mutex.lock
16
- @mutex.unlock.should === @mutex
17
- end
12
+ it "returns itself" do
13
+ @mutex.lock.should === @mutex
14
+ end
18
15
 
19
- it "#unlock raises ProcessError if not locked" do
20
- proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
21
- end
16
+ context "when locked in same thread" do
22
17
 
23
- it "#locked? returns false if not locked" do
24
- @mutex.locked?.should be_false
25
- end
18
+ before do
19
+ @mutex.lock
20
+ end
26
21
 
27
- it "#locked? returns true if locked" do
28
- @mutex.lock
29
- @mutex.locked?.should be_true
30
- end
22
+ it "raises ProcessError" do
23
+ proc{ @mutex.lock }.should raise_error MultiProcessing::ProcessError
24
+ end
31
25
 
32
- it "#lock, #unlock can locks/unlocks other process" do
33
- process = MultiProcessing::Process.new do
34
- sleep 0.1
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
- it "#synchronize can synchronize with other process" do
46
- process = MultiProcessing::Process.new do
47
- sleep 0.1
48
- @mutex.synchronize do
49
- :nop
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
- @mutex.synchronize do
53
- sleep 0.2
54
- process.join(0).should be_nil
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
- proc{ timeout(1){ process.join } }.should_not raise_error Timeout::Error
63
+
57
64
  end
58
65
 
59
- it "#lock cannot lock twice" do
60
- @mutex.lock
61
- proc{
62
- @mutex.lock
63
- }.should raise_error MultiProcessing::ProcessError
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
- it "#unlock raises ProcessError and does not unlock when unlocking by other processes" do
67
- process = MultiProcessing::Process.new do
68
- @mutex.lock
69
- sleep 0.2
70
- @mutex.unlock
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
- sleep 0.1
73
- proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
74
- process.value.success?.should be_true
274
+
75
275
  end
76
276
 
77
- it "#try_lock returns true if succeed, and lock correctly" do
78
- @mutex.try_lock.should be_true
79
- @mutex.locked?.should be_true
80
- @mutex.unlock.should === @mutex
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
- it "#try_lock returns false if failed" do
84
- process = MultiProcessing::Process.new do
85
- @mutex.lock
86
- sleep 0.2
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
- it "s sleep method unlocks and sleeps and re-locks itself" do
93
- process = MultiProcessing::Process.new do
94
- @mutex.lock
95
- @mutex.sleep(0.2)
96
- sleep 0.2
97
- @mutex.unlock
98
- end
99
- sleep 0.1
100
- @mutex.try_lock.should be_true
101
- @mutex.unlock
102
- sleep 0.2
103
- @mutex.try_lock.should be_false
104
- timeout(1){ process.value }.success?.should be_true
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