concurrent-ruby 0.2.2 → 0.3.0.pre.1
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/README.md +45 -42
- data/lib/concurrent.rb +5 -6
- data/lib/concurrent/agent.rb +29 -33
- data/lib/concurrent/cached_thread_pool.rb +26 -105
- data/lib/concurrent/channel.rb +94 -0
- data/lib/concurrent/event.rb +8 -17
- data/lib/concurrent/executor.rb +68 -72
- data/lib/concurrent/fixed_thread_pool.rb +15 -83
- data/lib/concurrent/functions.rb +7 -22
- data/lib/concurrent/future.rb +29 -9
- data/lib/concurrent/null_thread_pool.rb +5 -2
- data/lib/concurrent/obligation.rb +6 -16
- data/lib/concurrent/promise.rb +9 -10
- data/lib/concurrent/runnable.rb +103 -0
- data/lib/concurrent/supervisor.rb +271 -44
- data/lib/concurrent/thread_pool.rb +112 -39
- data/lib/concurrent/version.rb +1 -1
- data/md/executor.md +9 -3
- data/md/goroutine.md +11 -9
- data/md/reactor.md +32 -0
- data/md/supervisor.md +43 -0
- data/spec/concurrent/agent_spec.rb +128 -51
- data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
- data/spec/concurrent/channel_spec.rb +446 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
- data/spec/concurrent/event_spec.rb +0 -19
- data/spec/concurrent/executor_spec.rb +167 -119
- data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
- data/spec/concurrent/functions_spec.rb +0 -20
- data/spec/concurrent/future_spec.rb +88 -0
- data/spec/concurrent/null_thread_pool_spec.rb +23 -2
- data/spec/concurrent/obligation_shared.rb +0 -5
- data/spec/concurrent/promise_spec.rb +9 -10
- data/spec/concurrent/runnable_shared.rb +62 -0
- data/spec/concurrent/runnable_spec.rb +233 -0
- data/spec/concurrent/supervisor_spec.rb +912 -47
- data/spec/concurrent/thread_pool_shared.rb +18 -31
- data/spec/spec_helper.rb +10 -3
- metadata +17 -23
- data/lib/concurrent/defer.rb +0 -65
- data/lib/concurrent/reactor.rb +0 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
- data/lib/concurrent/utilities.rb +0 -32
- data/md/defer.md +0 -174
- data/spec/concurrent/defer_spec.rb +0 -199
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
- data/spec/concurrent/reactor_spec.rb +0 -364
- data/spec/concurrent/utilities_spec.rb +0 -74
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'timecop'
|
3
|
+
require_relative 'runnable_shared'
|
2
4
|
|
3
5
|
module Concurrent
|
4
6
|
|
@@ -7,18 +9,56 @@ module Concurrent
|
|
7
9
|
let(:worker_class) do
|
8
10
|
Class.new {
|
9
11
|
behavior(:runnable)
|
10
|
-
|
11
|
-
def
|
12
|
+
attr_reader :start_count, :stop_count
|
13
|
+
def run() @start_count ||= 0; @start_count += 1; return true; end
|
14
|
+
def stop() @stop_count ||= 0; @stop_count += 1; return true; end
|
12
15
|
def running?() return true; end
|
13
16
|
}
|
14
17
|
end
|
15
18
|
|
19
|
+
let(:sleeper_class) do
|
20
|
+
Class.new(worker_class) {
|
21
|
+
def run() super(); sleep; end
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:stopper_class) do
|
26
|
+
Class.new(worker_class) {
|
27
|
+
def initialize(sleep_time = 0.2) @sleep_time = sleep_time; end;
|
28
|
+
def run() super(); sleep(@sleep_time); end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:error_class) do
|
33
|
+
Class.new(worker_class) {
|
34
|
+
def run() super(); raise StandardError; end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:runner_class) do
|
39
|
+
Class.new(worker_class) {
|
40
|
+
attr_accessor :stopped
|
41
|
+
def run()
|
42
|
+
super()
|
43
|
+
stopped = false
|
44
|
+
loop do
|
45
|
+
break if stopped
|
46
|
+
Thread.pass
|
47
|
+
end
|
48
|
+
end
|
49
|
+
def stop() super(); stopped = true; end
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
16
53
|
let(:worker){ worker_class.new }
|
17
54
|
|
18
|
-
subject{ Supervisor.new }
|
55
|
+
subject{ Supervisor.new(strategy: :one_for_one, monitor_interval: 0.1) }
|
56
|
+
|
57
|
+
it_should_behave_like :runnable
|
19
58
|
|
20
59
|
after(:each) do
|
21
60
|
subject.stop
|
61
|
+
sleep(0.1)
|
22
62
|
end
|
23
63
|
|
24
64
|
context '#initialize' do
|
@@ -38,21 +78,98 @@ module Concurrent
|
|
38
78
|
supervisor.should_not be_running
|
39
79
|
end
|
40
80
|
|
41
|
-
it '
|
81
|
+
it 'uses the given monitor interval' do
|
42
82
|
supervisor = Supervisor.new
|
43
83
|
supervisor.monitor_interval.should == Supervisor::DEFAULT_MONITOR_INTERVAL
|
44
84
|
end
|
45
85
|
|
46
|
-
it '
|
86
|
+
it 'uses the default monitor interval when none given' do
|
47
87
|
supervisor = Supervisor.new(monitor_interval: 5)
|
48
88
|
supervisor.monitor_interval.should == 5
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'raises an exception when given an invalid monitor interval' do
|
92
|
+
lambda {
|
93
|
+
Supervisor.new(monitor_interval: -1)
|
94
|
+
}.should raise_error(ArgumentError)
|
95
|
+
|
96
|
+
lambda {
|
97
|
+
Supervisor.new(monitor_interval: 'bogus')
|
98
|
+
}.should raise_error(ArgumentError)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'uses the given restart strategy' do
|
102
|
+
supervisor = Supervisor.new(restart_strategy: :rest_for_one)
|
103
|
+
supervisor.restart_strategy.should eq :rest_for_one
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'uses :one_for_one when no restart strategy given' do
|
107
|
+
supervisor = Supervisor.new
|
108
|
+
supervisor.restart_strategy.should eq :one_for_one
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'raises an exception when given an invalid restart strategy' do
|
112
|
+
Supervisor::RESTART_STRATEGIES.each do |strategy|
|
113
|
+
lambda {
|
114
|
+
supervisor = Supervisor.new(strategy: strategy)
|
115
|
+
}.should_not raise_error
|
116
|
+
end
|
117
|
+
|
118
|
+
lambda {
|
119
|
+
supervisor = Supervisor.new(strategy: :bogus)
|
120
|
+
}.should raise_error(ArgumentError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'uses the given maximum restart value' do
|
124
|
+
supervisor = Supervisor.new(max_restart: 3)
|
125
|
+
supervisor.max_r.should == 3
|
126
|
+
|
127
|
+
supervisor = Supervisor.new(max_r: 3)
|
128
|
+
supervisor.max_restart.should == 3
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'uses the default maximum restart value when none given' do
|
132
|
+
supervisor = Supervisor.new
|
133
|
+
supervisor.max_restart.should == Supervisor::DEFAULT_MAX_RESTART
|
134
|
+
supervisor.max_r.should == Supervisor::DEFAULT_MAX_RESTART
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'raises an exception when given an invalid maximum restart value' do
|
138
|
+
lambda {
|
139
|
+
Supervisor.new(max_restart: -1)
|
140
|
+
}.should raise_error(ArgumentError)
|
141
|
+
|
142
|
+
lambda {
|
143
|
+
Supervisor.new(max_restart: 'bogus')
|
144
|
+
}.should raise_error(ArgumentError)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'uses the given maximum time value' do
|
148
|
+
supervisor = Supervisor.new(max_time: 3)
|
149
|
+
supervisor.max_t.should == 3
|
150
|
+
|
151
|
+
supervisor = Supervisor.new(max_t: 3)
|
152
|
+
supervisor.max_time.should == 3
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'uses the default maximum time value when none given' do
|
156
|
+
supervisor = Supervisor.new
|
157
|
+
supervisor.max_time.should == Supervisor::DEFAULT_MAX_TIME
|
158
|
+
supervisor.max_t.should == Supervisor::DEFAULT_MAX_TIME
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'raises an exception when given an invalid maximum time value' do
|
162
|
+
lambda {
|
163
|
+
Supervisor.new(max_time: -1)
|
164
|
+
}.should raise_error(ArgumentError)
|
49
165
|
|
50
|
-
|
51
|
-
|
166
|
+
lambda {
|
167
|
+
Supervisor.new(max_time: 'bogus')
|
168
|
+
}.should raise_error(ArgumentError)
|
52
169
|
end
|
53
170
|
end
|
54
171
|
|
55
|
-
context 'run' do
|
172
|
+
context '#run' do
|
56
173
|
|
57
174
|
it 'runs the monitor' do
|
58
175
|
subject.should_receive(:monitor).with(no_args()).at_least(1).times
|
@@ -84,8 +201,10 @@ module Concurrent
|
|
84
201
|
@thread = nil
|
85
202
|
subject.run!
|
86
203
|
lambda {
|
87
|
-
@thread = Thread.new
|
88
|
-
|
204
|
+
@thread = Thread.new do
|
205
|
+
Thread.current.abort_on_exception = true
|
206
|
+
subject.run
|
207
|
+
end
|
89
208
|
sleep(0.1)
|
90
209
|
}.should raise_error(StandardError)
|
91
210
|
subject.stop
|
@@ -99,6 +218,7 @@ module Concurrent
|
|
99
218
|
thread = Thread.new{ nil }
|
100
219
|
Thread.should_receive(:new).with(no_args()).and_return(thread)
|
101
220
|
subject.run!
|
221
|
+
sleep(0.1)
|
102
222
|
end
|
103
223
|
|
104
224
|
it 'calls #run on all workers' do
|
@@ -124,15 +244,35 @@ module Concurrent
|
|
124
244
|
|
125
245
|
context '#stop' do
|
126
246
|
|
127
|
-
|
128
|
-
|
247
|
+
def mock_thread(status = 'run')
|
248
|
+
thread = double('thread')
|
249
|
+
thread.should_receive(:status).with(no_args()).and_return(status)
|
250
|
+
thread.stub(:join).with(any_args()).and_return(thread)
|
251
|
+
Thread.stub(:new).with(no_args()).and_return(thread)
|
252
|
+
return thread
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'wakes the monitor thread if sleeping' do
|
256
|
+
thread = mock_thread('sleep')
|
257
|
+
thread.should_receive(:run).once.with(no_args())
|
258
|
+
|
259
|
+
subject.run!
|
260
|
+
sleep(0.1)
|
261
|
+
subject.stop
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'kills the monitor thread if it does not wake up' do
|
265
|
+
thread = mock_thread('run')
|
266
|
+
thread.should_receive(:join).with(any_args()).and_return(nil)
|
267
|
+
thread.should_receive(:kill).with(no_args())
|
268
|
+
|
129
269
|
subject.run!
|
130
270
|
sleep(0.1)
|
131
271
|
subject.stop
|
132
272
|
end
|
133
273
|
|
134
274
|
it 'calls #stop on all workers' do
|
135
|
-
workers = (1..3).collect{
|
275
|
+
workers = (1..3).collect{ runner_class.new }
|
136
276
|
workers.each{|worker| subject.add_worker(worker)}
|
137
277
|
# must stub AFTER adding or else #add_worker will reject
|
138
278
|
workers.each{|worker| worker.should_receive(:stop).with(no_args())}
|
@@ -152,7 +292,7 @@ module Concurrent
|
|
152
292
|
end
|
153
293
|
|
154
294
|
it 'unblocks a thread blocked by #run and exits normally' do
|
155
|
-
supervisor = Supervisor.new(
|
295
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
156
296
|
@thread = Thread.new{ sleep(0.5); supervisor.stop }
|
157
297
|
sleep(0.1)
|
158
298
|
lambda {
|
@@ -185,6 +325,150 @@ module Concurrent
|
|
185
325
|
end
|
186
326
|
end
|
187
327
|
|
328
|
+
context '#count' do
|
329
|
+
|
330
|
+
let(:busy_supervisor) do
|
331
|
+
supervisor = Supervisor.new(monitor_interval: 60)
|
332
|
+
3.times do
|
333
|
+
supervisor.add_worker(sleeper_class.new)
|
334
|
+
supervisor.add_worker(stopper_class.new)
|
335
|
+
supervisor.add_worker(error_class.new)
|
336
|
+
supervisor.add_worker(runner_class.new)
|
337
|
+
end
|
338
|
+
supervisor
|
339
|
+
end
|
340
|
+
|
341
|
+
let!(:total_count){ 12 }
|
342
|
+
let!(:active_count){ 6 }
|
343
|
+
let!(:sleeping_count){ 3 }
|
344
|
+
let!(:running_count){ 3 }
|
345
|
+
let!(:aborting_count){ 3 }
|
346
|
+
let!(:stopped_count){ 3 }
|
347
|
+
let!(:abend_count){ 3 }
|
348
|
+
|
349
|
+
after(:each) do
|
350
|
+
busy_supervisor.stop
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'returns an immutable WorkerCounts object' do
|
354
|
+
counts = subject.count
|
355
|
+
counts.should be_a(Supervisor::WorkerCounts)
|
356
|
+
|
357
|
+
lambda {
|
358
|
+
counts.specs += 1
|
359
|
+
}.should raise_error(RuntimeError)
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'returns the total worker count as #specs' do
|
363
|
+
subject.count.specs.should eq 0
|
364
|
+
|
365
|
+
3.times do
|
366
|
+
subject.add_worker(worker_class.new, type: :worker)
|
367
|
+
subject.add_worker(worker_class.new, type: :supervisor)
|
368
|
+
end
|
369
|
+
|
370
|
+
subject.count.specs.should eq 6
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'returns the count of all children marked as :supervisor as #supervisors' do
|
374
|
+
subject.count.supervisors.should eq 0
|
375
|
+
|
376
|
+
3.times do
|
377
|
+
subject.add_worker(worker_class.new, type: :worker)
|
378
|
+
subject.add_worker(worker_class.new, type: :supervisor)
|
379
|
+
end
|
380
|
+
|
381
|
+
subject.count.supervisors.should eq 3
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'returns the count of all children marked as :worker as #workers' do
|
385
|
+
subject.count.workers.should eq 0
|
386
|
+
|
387
|
+
3.times do
|
388
|
+
subject.add_worker(worker_class.new, type: :worker)
|
389
|
+
subject.add_worker(worker_class.new, type: :supervisor)
|
390
|
+
end
|
391
|
+
|
392
|
+
subject.count.workers.should eq 3
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'returns the count of all active workers as #active' do
|
396
|
+
busy_supervisor.count.active.should eq 0
|
397
|
+
busy_supervisor.run!
|
398
|
+
sleep(0.5)
|
399
|
+
|
400
|
+
busy_supervisor.count.active.should eq active_count
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'returns the count of all sleeping workers as #sleeping' do
|
404
|
+
busy_supervisor.count.sleeping.should eq 0
|
405
|
+
busy_supervisor.run!
|
406
|
+
sleep(0.5)
|
407
|
+
|
408
|
+
busy_supervisor.count.sleeping.should eq sleeping_count
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'returns the count of all running workers as #running' do
|
412
|
+
busy_supervisor.count.running.should eq 0
|
413
|
+
busy_supervisor.run!
|
414
|
+
sleep(0.5)
|
415
|
+
|
416
|
+
busy_supervisor.count.running.should eq running_count
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'returns the count of all aborting workers as #aborting' do
|
420
|
+
busy_supervisor.count.aborting.should eq 0
|
421
|
+
|
422
|
+
count = Supervisor::WorkerCounts.new(5, 0, 5)
|
423
|
+
count.status = %w[aborting run aborting false aborting]
|
424
|
+
count.aborting.should eq 3
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'returns the count of all stopped workers as #stopped' do
|
428
|
+
busy_supervisor.count.stopped.should eq total_count
|
429
|
+
busy_supervisor.run!
|
430
|
+
sleep(0.5)
|
431
|
+
|
432
|
+
busy_supervisor.count.stopped.should eq stopped_count
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'returns the count of all workers terminated by exception as #abend' do
|
436
|
+
busy_supervisor.count.abend.should eq 0
|
437
|
+
busy_supervisor.run!
|
438
|
+
sleep(0.5)
|
439
|
+
|
440
|
+
busy_supervisor.count.abend.should eq abend_count
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
context '#current_restart_count' do
|
445
|
+
|
446
|
+
it 'is zero for a new Supervisor' do
|
447
|
+
subject.current_restart_count.should eq 0
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'returns the number of worker restarts' do
|
451
|
+
worker = error_class.new
|
452
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
453
|
+
supervisor.add_worker(worker)
|
454
|
+
supervisor.run!
|
455
|
+
sleep(0.3)
|
456
|
+
supervisor.current_restart_count.should > 0
|
457
|
+
supervisor.stop
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'resets to zero on #stop' do
|
461
|
+
worker = error_class.new
|
462
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
463
|
+
supervisor.add_worker(worker)
|
464
|
+
supervisor.run!
|
465
|
+
sleep(0.3)
|
466
|
+
supervisor.stop
|
467
|
+
sleep(0.1)
|
468
|
+
supervisor.current_restart_count.should eq 0
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
188
472
|
context '#add_worker' do
|
189
473
|
|
190
474
|
it 'adds the worker when stopped' do
|
@@ -192,10 +476,14 @@ module Concurrent
|
|
192
476
|
subject.length.should == 1
|
193
477
|
end
|
194
478
|
|
195
|
-
it '
|
479
|
+
it 'runs the worker when the supervisor is running' do
|
480
|
+
worker = worker_class.new
|
481
|
+
worker.start_count.to_i.should eq 0
|
196
482
|
subject.run!
|
197
|
-
|
198
|
-
subject.
|
483
|
+
sleep(0.1)
|
484
|
+
subject.add_worker(worker).should be_true
|
485
|
+
sleep(0.1)
|
486
|
+
worker.start_count.should >= 1
|
199
487
|
end
|
200
488
|
|
201
489
|
it 'rejects a worker without the :runnable behavior' do
|
@@ -203,57 +491,633 @@ module Concurrent
|
|
203
491
|
subject.length.should == 0
|
204
492
|
end
|
205
493
|
|
206
|
-
it '
|
207
|
-
subject.add_worker(
|
494
|
+
it 'sets the restart type to the given value' do
|
495
|
+
subject.add_worker(worker_class.new, restart: :temporary)
|
496
|
+
worker = subject.instance_variable_get(:@workers).first
|
497
|
+
worker.restart.should eq :temporary
|
498
|
+
end
|
499
|
+
|
500
|
+
it 'sets the restart type to :permanent when none given' do
|
501
|
+
subject.add_worker(worker_class.new)
|
502
|
+
worker = subject.instance_variable_get(:@workers).first
|
503
|
+
worker.restart.should eq :permanent
|
504
|
+
end
|
505
|
+
|
506
|
+
it 'raises an exception when given an invalid restart type' do
|
507
|
+
lambda {
|
508
|
+
subject.add_worker(worker_class.new, restart: :bogus)
|
509
|
+
}.should raise_error(ArgumentError)
|
510
|
+
end
|
511
|
+
|
512
|
+
it 'sets the child type to the given value' do
|
513
|
+
subject.add_worker(worker_class.new, type: :supervisor)
|
514
|
+
worker = subject.instance_variable_get(:@workers).first
|
515
|
+
worker.type.should eq :supervisor
|
516
|
+
end
|
517
|
+
|
518
|
+
it 'sets the worker type to :worker when none given' do
|
519
|
+
subject.add_worker(worker_class.new)
|
520
|
+
worker = subject.instance_variable_get(:@workers).first
|
521
|
+
worker.type.should eq :worker
|
522
|
+
end
|
523
|
+
|
524
|
+
it 'sets the worker type to :supervisor when #is_a? Supervisor' do
|
525
|
+
subject.add_worker(Supervisor.new)
|
526
|
+
worker = subject.instance_variable_get(:@workers).first
|
527
|
+
worker.type.should eq :supervisor
|
528
|
+
end
|
529
|
+
|
530
|
+
it 'raises an exception when given an invalid restart type' do
|
531
|
+
lambda {
|
532
|
+
subject.add_worker(worker_class.new, type: :bogus)
|
533
|
+
}.should raise_error(ArgumentError)
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'returns an object id when a worker is accepted' do
|
537
|
+
worker_id = subject.add_worker(worker)
|
538
|
+
worker_id.should be_a(Integer)
|
539
|
+
first = subject.instance_variable_get(:@workers).first
|
540
|
+
worker_id.should eq first.object_id
|
208
541
|
end
|
209
542
|
|
210
|
-
it 'returns
|
211
|
-
subject.add_worker('bogus worker').should
|
543
|
+
it 'returns nil when a worker is not accepted' do
|
544
|
+
subject.add_worker('bogus worker').should be_nil
|
212
545
|
end
|
213
546
|
end
|
214
547
|
|
215
|
-
context '
|
548
|
+
context '#add_workers' do
|
216
549
|
|
217
|
-
it '
|
218
|
-
|
219
|
-
|
220
|
-
|
550
|
+
it 'calls #add_worker once for each worker' do
|
551
|
+
workers = 5.times.collect{ worker_class.new }
|
552
|
+
workers.each do |worker|
|
553
|
+
subject.should_receive(:add_worker).once.with(worker, anything())
|
554
|
+
end
|
555
|
+
subject.add_workers(workers)
|
556
|
+
end
|
221
557
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
558
|
+
it 'passes the options hash to each #add_worker call' do
|
559
|
+
options = {
|
560
|
+
restart: :permanent,
|
561
|
+
type: :worker
|
562
|
+
}
|
563
|
+
workers = 5.times.collect{ worker_class.new }
|
564
|
+
workers.each do |worker|
|
565
|
+
subject.should_receive(:add_worker).once.with(anything(), options)
|
566
|
+
end
|
567
|
+
subject.add_workers(workers, options)
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'returns an array of object identifiers' do
|
571
|
+
workers = 5.times.collect{ worker_class.new }
|
572
|
+
context = subject.add_workers(workers)
|
573
|
+
context.size.should == 5
|
574
|
+
context.each do |wc|
|
575
|
+
wc.should be_a(Fixnum)
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
context '#remove_worker' do
|
581
|
+
|
582
|
+
it 'returns false if the worker is running' do
|
583
|
+
id = subject.add_worker(sleeper_class.new)
|
584
|
+
subject.run!
|
585
|
+
sleep(0.1)
|
586
|
+
subject.remove_worker(id).should be_false
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'returns nil if the worker is not found' do
|
590
|
+
subject.run!
|
591
|
+
sleep(0.1)
|
592
|
+
subject.remove_worker(1234).should be_nil
|
593
|
+
end
|
594
|
+
|
595
|
+
it 'returns the worker on success' do
|
596
|
+
worker = error_class.new
|
597
|
+
supervisor = Supervisor.new(monitor_interval: 60)
|
598
|
+
id = supervisor.add_worker(worker)
|
599
|
+
supervisor.run!
|
600
|
+
sleep(0.1)
|
601
|
+
supervisor.remove_worker(id).should eq worker
|
602
|
+
supervisor.stop
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'removes the worker from the supervisor on success' do
|
606
|
+
worker = error_class.new
|
607
|
+
supervisor = Supervisor.new(monitor_interval: 60)
|
608
|
+
id = supervisor.add_worker(worker)
|
609
|
+
supervisor.length.should == 1
|
226
610
|
supervisor.run!
|
227
|
-
sleep(1)
|
611
|
+
sleep(0.1)
|
612
|
+
supervisor.remove_worker(id)
|
613
|
+
supervisor.length.should == 0
|
228
614
|
supervisor.stop
|
229
615
|
end
|
616
|
+
end
|
230
617
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
618
|
+
context '#stop_worker' do
|
619
|
+
|
620
|
+
it 'returns true if the supervisor is not running' do
|
621
|
+
worker = worker_class.new
|
622
|
+
id = subject.add_worker(worker)
|
623
|
+
subject.stop_worker(id).should be_true
|
624
|
+
end
|
625
|
+
|
626
|
+
it 'returns nil if the worker is not found' do
|
627
|
+
worker = sleeper_class.new
|
628
|
+
id = subject.add_worker(worker)
|
629
|
+
subject.run!
|
630
|
+
sleep(0.1)
|
631
|
+
subject.stop_worker(1234).should be_nil
|
632
|
+
end
|
633
|
+
|
634
|
+
it 'returns true on success' do
|
635
|
+
worker = sleeper_class.new
|
636
|
+
id = subject.add_worker(worker)
|
637
|
+
subject.run!
|
638
|
+
sleep(0.1)
|
639
|
+
worker.should_receive(:stop).at_least(1).times.and_return(true)
|
640
|
+
subject.stop_worker(id).should be_true
|
641
|
+
end
|
642
|
+
|
643
|
+
it 'deletes the worker if it is :temporary' do
|
644
|
+
worker = sleeper_class.new
|
645
|
+
id = subject.add_worker(worker, restart: :temporary)
|
646
|
+
subject.size.should eq 1
|
647
|
+
subject.run!
|
648
|
+
sleep(0.1)
|
649
|
+
subject.stop_worker(id).should be_true
|
650
|
+
subject.size.should eq 0
|
651
|
+
end
|
652
|
+
|
653
|
+
it 'does not implicitly restart the worker' do
|
654
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
655
|
+
worker = runner_class.new
|
656
|
+
id = supervisor.add_worker(worker, restart: :permanent)
|
657
|
+
supervisor.run!
|
658
|
+
sleep(0.1)
|
659
|
+
supervisor.stop_worker(id)
|
660
|
+
sleep(0.5)
|
661
|
+
supervisor.stop
|
662
|
+
worker.start_count.should eq 1
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
context '#start_worker' do
|
667
|
+
|
668
|
+
it 'returns false if the supervisor is not running' do
|
669
|
+
worker = worker_class.new
|
670
|
+
id = subject.add_worker(worker)
|
671
|
+
subject.start_worker(id).should be_false
|
672
|
+
end
|
673
|
+
|
674
|
+
it 'returns nil if the worker is not found' do
|
675
|
+
subject.run!
|
676
|
+
sleep(0.1)
|
677
|
+
subject.start_worker(1234).should be_nil
|
678
|
+
end
|
679
|
+
|
680
|
+
it 'starts the worker if not running' do
|
681
|
+
supervisor = Supervisor.new(monitor_interval: 60)
|
682
|
+
worker = error_class.new
|
683
|
+
id = supervisor.add_worker(worker)
|
684
|
+
supervisor.run!
|
685
|
+
sleep(0.1)
|
686
|
+
supervisor.start_worker(id)
|
687
|
+
sleep(0.1)
|
688
|
+
worker.start_count.should == 2
|
689
|
+
supervisor.stop
|
690
|
+
end
|
691
|
+
|
692
|
+
it 'returns true when the worker is successfully started' do
|
693
|
+
supervisor = Supervisor.new(monitor_interval: 60)
|
694
|
+
worker = error_class.new
|
695
|
+
id = supervisor.add_worker(worker)
|
696
|
+
supervisor.run!
|
697
|
+
sleep(0.1)
|
698
|
+
supervisor.start_worker(id).should be_true
|
699
|
+
supervisor.stop
|
700
|
+
end
|
701
|
+
|
702
|
+
it 'returns true if the worker was already running' do
|
703
|
+
supervisor = Supervisor.new(monitor_interval: 60)
|
704
|
+
worker = sleeper_class.new
|
705
|
+
id = supervisor.add_worker(worker)
|
706
|
+
supervisor.run!
|
707
|
+
sleep(0.1)
|
708
|
+
supervisor.start_worker(id).should be_true
|
709
|
+
worker.start_count.should == 1
|
710
|
+
supervisor.stop
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
context '#restart_worker' do
|
715
|
+
|
716
|
+
it 'returns false if the supervisor is not running' do
|
717
|
+
worker = worker_class.new
|
718
|
+
id = subject.add_worker(worker)
|
719
|
+
subject.restart_worker(id).should be_false
|
720
|
+
end
|
721
|
+
|
722
|
+
it 'returns nil if the worker is not found' do
|
723
|
+
subject.run!
|
724
|
+
sleep(0.1)
|
725
|
+
subject.restart_worker(1234).should be_nil
|
726
|
+
end
|
727
|
+
|
728
|
+
it 'returns false if the worker is :temporary' do
|
729
|
+
worker = worker_class.new
|
730
|
+
id = subject.add_worker(worker, restart: :temporary)
|
731
|
+
subject.run!
|
732
|
+
sleep(0.1)
|
733
|
+
subject.restart_worker(id).should be_false
|
734
|
+
end
|
735
|
+
|
736
|
+
it 'stops and then starts a worker that is running' do
|
737
|
+
worker = runner_class.new
|
738
|
+
id = subject.add_worker(worker)
|
739
|
+
subject.run!
|
740
|
+
sleep(0.1)
|
741
|
+
subject.restart_worker(id)
|
742
|
+
sleep(0.1)
|
743
|
+
worker.start_count.should == 2
|
744
|
+
worker.stop_count.should == 1
|
745
|
+
end
|
746
|
+
|
747
|
+
it 'returns true if the worker is running and is successfully restarted' do
|
748
|
+
worker = runner_class.new
|
749
|
+
id = subject.add_worker(worker)
|
750
|
+
subject.run!
|
751
|
+
sleep(0.1)
|
752
|
+
subject.restart_worker(id).should be_true
|
753
|
+
end
|
754
|
+
|
755
|
+
it 'starts a worker that is not running' do
|
756
|
+
worker = error_class.new
|
757
|
+
id = subject.add_worker(worker)
|
758
|
+
subject.run!
|
759
|
+
sleep(0.1)
|
760
|
+
subject.restart_worker(id)
|
761
|
+
sleep(0.1)
|
762
|
+
worker.start_count.should >= 2
|
763
|
+
end
|
764
|
+
|
765
|
+
it 'returns true if the worker is not running and is successfully started' do
|
766
|
+
worker = error_class.new
|
767
|
+
id = subject.add_worker(worker)
|
768
|
+
subject.run!
|
769
|
+
sleep(0.1)
|
770
|
+
subject.restart_worker(id).should be_true
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
774
|
+
context 'maximum restart frequency' do
|
775
|
+
|
776
|
+
context '#exceeded_max_restart_frequency?' do
|
777
|
+
|
778
|
+
# Normally I am very opposed to testing private methods
|
779
|
+
# but this functionality has proven extremely difficult to test.
|
780
|
+
# Geting the timing right is almost impossible. This is the
|
781
|
+
# best approach I could think of.
|
782
|
+
|
783
|
+
it 'increments the restart count on every call' do
|
784
|
+
subject.send(:exceeded_max_restart_frequency?)
|
785
|
+
subject.current_restart_count.should eq 1
|
786
|
+
|
787
|
+
subject.send(:exceeded_max_restart_frequency?)
|
788
|
+
subject.current_restart_count.should eq 2
|
789
|
+
|
790
|
+
subject.send(:exceeded_max_restart_frequency?)
|
791
|
+
subject.current_restart_count.should eq 3
|
792
|
+
end
|
793
|
+
|
794
|
+
it 'returns false when the restart count is lower than :max_restart' do
|
795
|
+
supervisor = Supervisor.new(max_restart: 5, max_time: 60)
|
796
|
+
|
797
|
+
Timecop.freeze do
|
798
|
+
4.times do
|
799
|
+
Timecop.travel(5)
|
800
|
+
supervisor.send(:exceeded_max_restart_frequency?).should be_false
|
801
|
+
end
|
802
|
+
|
803
|
+
Timecop.travel(5)
|
804
|
+
supervisor.send(:exceeded_max_restart_frequency?).should be_true
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
it 'returns false when the restart count is high but the time range is out of scope' do
|
809
|
+
supervisor = Supervisor.new(max_restart: 3, max_time: 8)
|
810
|
+
|
811
|
+
Timecop.freeze do
|
812
|
+
10.times do
|
813
|
+
Timecop.travel(5)
|
814
|
+
supervisor.send(:exceeded_max_restart_frequency?).should be_false
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
it 'returns true when the restart count is exceeded within the max time range' do
|
820
|
+
supervisor = Supervisor.new(max_restart: 2, max_time: 5)
|
821
|
+
Timecop.freeze do
|
822
|
+
supervisor.send(:exceeded_max_restart_frequency?).should be_false
|
823
|
+
Timecop.travel(1)
|
824
|
+
supervisor.send(:exceeded_max_restart_frequency?).should be_true
|
825
|
+
end
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
context 'restarts when true for strategy' do
|
830
|
+
|
831
|
+
specify ':one_for_one' do
|
832
|
+
supervisor = Supervisor.new(restart_strategy: :one_for_one,
|
833
|
+
monitor_interval: 0.1)
|
834
|
+
supervisor.add_worker(error_class.new)
|
835
|
+
supervisor.should_receive(:exceeded_max_restart_frequency?).once.and_return(true)
|
836
|
+
supervisor.run!
|
837
|
+
sleep(0.2)
|
838
|
+
supervisor.should_not be_running
|
839
|
+
end
|
840
|
+
|
841
|
+
specify ':one_for_all' do
|
842
|
+
supervisor = Supervisor.new(restart_strategy: :one_for_all,
|
843
|
+
monitor_interval: 0.1)
|
844
|
+
supervisor.add_worker(error_class.new)
|
845
|
+
supervisor.should_receive(:exceeded_max_restart_frequency?).once.and_return(true)
|
846
|
+
supervisor.run!
|
847
|
+
sleep(0.2)
|
848
|
+
supervisor.should_not be_running
|
849
|
+
end
|
850
|
+
|
851
|
+
specify ':rest_for_one' do
|
852
|
+
supervisor = Supervisor.new(restart_strategy: :rest_for_one,
|
853
|
+
monitor_interval: 0.1)
|
854
|
+
supervisor.add_worker(error_class.new)
|
855
|
+
supervisor.should_receive(:exceeded_max_restart_frequency?).once.and_return(true)
|
856
|
+
supervisor.run!
|
857
|
+
sleep(0.2)
|
858
|
+
supervisor.should_not be_running
|
859
|
+
end
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
context 'restart strategies' do
|
864
|
+
|
865
|
+
context ':one_for_one' do
|
866
|
+
|
867
|
+
it 'restarts any worker that stops' do
|
868
|
+
|
869
|
+
workers = [
|
870
|
+
sleeper_class.new,
|
871
|
+
stopper_class.new,
|
872
|
+
sleeper_class.new
|
873
|
+
]
|
874
|
+
|
875
|
+
supervisor = Supervisor.new(strategy: :one_for_one, monitor_interval: 0.1)
|
876
|
+
workers.each{|worker| supervisor.add_worker(worker) }
|
877
|
+
|
878
|
+
supervisor.run!
|
879
|
+
sleep(1)
|
880
|
+
|
881
|
+
workers[0].start_count.should == 1
|
882
|
+
workers[1].start_count.should >= 2
|
883
|
+
workers[2].start_count.should == 1
|
884
|
+
|
885
|
+
supervisor.stop
|
886
|
+
end
|
887
|
+
|
888
|
+
it 'restarts any dead threads' do
|
889
|
+
|
890
|
+
workers = [
|
891
|
+
sleeper_class.new,
|
892
|
+
error_class.new,
|
893
|
+
sleeper_class.new
|
894
|
+
]
|
895
|
+
|
896
|
+
supervisor = Supervisor.new(strategy: :one_for_one, monitor_interval: 0.1)
|
897
|
+
workers.each{|worker| supervisor.add_worker(worker) }
|
898
|
+
|
899
|
+
supervisor.run!
|
900
|
+
sleep(1)
|
901
|
+
|
902
|
+
workers[0].start_count.should == 1
|
903
|
+
workers[1].start_count.should >= 2
|
904
|
+
workers[2].start_count.should == 1
|
905
|
+
|
906
|
+
supervisor.stop
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
context ':one_for_all' do
|
911
|
+
|
912
|
+
it 'restarts all workers when one stops' do
|
913
|
+
|
914
|
+
workers = [
|
915
|
+
sleeper_class.new,
|
916
|
+
stopper_class.new,
|
917
|
+
sleeper_class.new
|
918
|
+
]
|
919
|
+
|
920
|
+
supervisor = Supervisor.new(strategy: :one_for_all, monitor_interval: 0.1)
|
921
|
+
workers.each{|worker| supervisor.add_worker(worker) }
|
922
|
+
|
923
|
+
workers[0].should_receive(:stop).once.with(no_args())
|
924
|
+
workers[2].should_receive(:stop).once.with(no_args())
|
925
|
+
|
926
|
+
supervisor.run!
|
927
|
+
sleep(1)
|
928
|
+
workers.each{|worker| worker.start_count.should >= 2 }
|
929
|
+
|
930
|
+
supervisor.stop
|
931
|
+
end
|
932
|
+
|
933
|
+
it 'restarts all workers when one thread dies' do
|
934
|
+
|
935
|
+
workers = [
|
936
|
+
sleeper_class.new,
|
937
|
+
error_class.new,
|
938
|
+
sleeper_class.new
|
939
|
+
]
|
940
|
+
|
941
|
+
supervisor = Supervisor.new(strategy: :one_for_all, monitor_interval: 0.1)
|
942
|
+
workers.each{|worker| supervisor.add_worker(worker) }
|
943
|
+
|
944
|
+
workers[0].should_receive(:stop).once.with(no_args())
|
945
|
+
workers[2].should_receive(:stop).once.with(no_args())
|
946
|
+
|
947
|
+
supervisor.run!
|
948
|
+
sleep(1)
|
949
|
+
workers.each{|worker| worker.start_count.should >= 2 }
|
950
|
+
|
951
|
+
supervisor.stop
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
context ':rest_for_one' do
|
956
|
+
|
957
|
+
it 'restarts a stopped worker and all workers added after it' do
|
958
|
+
|
959
|
+
workers = [
|
960
|
+
sleeper_class.new,
|
961
|
+
stopper_class.new,
|
962
|
+
sleeper_class.new
|
963
|
+
]
|
964
|
+
|
965
|
+
supervisor = Supervisor.new(strategy: :rest_for_one, monitor_interval: 0.1)
|
966
|
+
workers.each{|worker| supervisor.add_worker(worker) }
|
967
|
+
|
968
|
+
workers[0].should_not_receive(:stop)
|
969
|
+
workers[2].should_receive(:stop).once.with(no_args())
|
970
|
+
|
971
|
+
supervisor.run!
|
972
|
+
sleep(1)
|
973
|
+
|
974
|
+
workers[0].start_count.should == 1
|
975
|
+
workers[1].start_count.should >= 2
|
976
|
+
workers[2].start_count.should >= 2
|
977
|
+
|
978
|
+
supervisor.stop
|
979
|
+
end
|
980
|
+
|
981
|
+
it 'restarts a dead worker thread and all workers added after it' do
|
982
|
+
|
983
|
+
workers = [
|
984
|
+
sleeper_class.new,
|
985
|
+
error_class.new,
|
986
|
+
sleeper_class.new
|
987
|
+
]
|
988
|
+
|
989
|
+
supervisor = Supervisor.new(strategy: :rest_for_one, monitor_interval: 0.1)
|
990
|
+
workers.each{|worker| supervisor.add_worker(worker) }
|
991
|
+
|
992
|
+
workers[0].should_not_receive(:stop)
|
993
|
+
workers[2].should_receive(:stop).once.with(no_args())
|
994
|
+
|
995
|
+
supervisor.run!
|
996
|
+
sleep(1)
|
997
|
+
|
998
|
+
workers[0].start_count.should == 1
|
999
|
+
workers[1].start_count.should >= 2
|
1000
|
+
workers[2].start_count.should >= 2
|
1001
|
+
|
1002
|
+
supervisor.stop
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
context 'child restart options' do
|
1008
|
+
|
1009
|
+
def worker_status(supervisor)
|
1010
|
+
worker = supervisor.instance_variable_get(:@workers).first
|
1011
|
+
return worker.thread.status
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
specify ':permanent restarts on abend' do
|
1015
|
+
worker = error_class.new
|
1016
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1017
|
+
supervisor.add_worker(worker, restart: :permanent)
|
1018
|
+
|
1019
|
+
supervisor.run!
|
1020
|
+
sleep(0.5)
|
1021
|
+
supervisor.stop
|
1022
|
+
|
1023
|
+
worker.start_count.should >= 1
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
specify ':permanent restarts on normal stop' do
|
1027
|
+
worker = stopper_class.new(0.1)
|
1028
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1029
|
+
supervisor.add_worker(worker, restart: :permanent)
|
1030
|
+
|
1031
|
+
supervisor.run!
|
1032
|
+
sleep(0.5)
|
1033
|
+
supervisor.stop
|
1034
|
+
|
1035
|
+
worker.start_count.should >= 1
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
specify ':temporary does not restart on abend' do
|
1039
|
+
worker = error_class.new
|
1040
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1041
|
+
supervisor.add_worker(worker, restart: :temporary)
|
1042
|
+
|
1043
|
+
supervisor.run!
|
1044
|
+
sleep(0.5)
|
1045
|
+
supervisor.stop
|
1046
|
+
|
1047
|
+
worker.start_count.should eq 1
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
specify ':temporary does not restart on normal stop' do
|
1051
|
+
worker = stopper_class.new
|
1052
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1053
|
+
supervisor.add_worker(worker, restart: :temporary)
|
1054
|
+
|
1055
|
+
supervisor.run!
|
1056
|
+
sleep(0.5)
|
1057
|
+
supervisor.stop
|
1058
|
+
|
1059
|
+
worker.start_count.should eq 1
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
specify ':temporary is deleted on abend' do
|
1063
|
+
worker = error_class.new
|
1064
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1065
|
+
supervisor.add_worker(worker, restart: :temporary)
|
235
1066
|
|
236
|
-
supervisor = Supervisor.new(worker: worker, monitor: 0.1)
|
237
|
-
supervisor.add_worker(worker)
|
238
|
-
# must stub AFTER adding or else #add_worker will reject
|
239
|
-
worker.should_receive(:run).with(no_args()).at_least(2).times
|
240
1067
|
supervisor.run!
|
241
|
-
sleep(
|
1068
|
+
sleep(0.5)
|
242
1069
|
supervisor.stop
|
1070
|
+
|
1071
|
+
supervisor.size.should eq 0
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
specify ':temporary is deleted on normal stop' do
|
1075
|
+
worker = stopper_class.new
|
1076
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1077
|
+
supervisor.add_worker(worker, restart: :temporary)
|
1078
|
+
|
1079
|
+
supervisor.run!
|
1080
|
+
sleep(0.5)
|
1081
|
+
supervisor.stop
|
1082
|
+
|
1083
|
+
supervisor.size.should eq 0
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
specify ':transient restarts on abend' do
|
1087
|
+
worker = error_class.new
|
1088
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1089
|
+
supervisor.add_worker(worker, restart: :transient)
|
1090
|
+
|
1091
|
+
supervisor.run!
|
1092
|
+
sleep(0.5)
|
1093
|
+
supervisor.stop
|
1094
|
+
|
1095
|
+
worker.start_count.should >= 1
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
specify ':transient does not restart on normal stop' do
|
1099
|
+
worker = stopper_class.new
|
1100
|
+
supervisor = Supervisor.new(monitor_interval: 0.1)
|
1101
|
+
supervisor.add_worker(worker, restart: :transient)
|
1102
|
+
|
1103
|
+
supervisor.run!
|
1104
|
+
sleep(0.5)
|
1105
|
+
supervisor.stop
|
1106
|
+
|
1107
|
+
worker.start_count.should eq 1
|
243
1108
|
end
|
244
1109
|
end
|
245
1110
|
|
246
|
-
context '
|
1111
|
+
context 'supervision tree' do
|
247
1112
|
|
248
1113
|
specify do
|
249
|
-
s1 = Supervisor.new(
|
250
|
-
s2 = Supervisor.new(
|
251
|
-
s3 = Supervisor.new(
|
1114
|
+
s1 = Supervisor.new(monitor_interval: 0.1)
|
1115
|
+
s2 = Supervisor.new(monitor_interval: 0.1)
|
1116
|
+
s3 = Supervisor.new(monitor_interval: 0.1)
|
252
1117
|
|
253
|
-
workers = (1..3).collect{
|
1118
|
+
workers = (1..3).collect{ sleeper_class.new }
|
254
1119
|
workers.each{|worker| s3.add_worker(worker)}
|
255
|
-
|
256
|
-
workers.each{|worker| worker.should_receive(:run).at_least(1).times.with(no_args())}
|
1120
|
+
|
257
1121
|
workers.each{|worker| worker.should_receive(:stop).at_least(1).times.with(no_args())}
|
258
1122
|
|
259
1123
|
s1.add_worker(s2)
|
@@ -261,6 +1125,7 @@ module Concurrent
|
|
261
1125
|
|
262
1126
|
s1.run!
|
263
1127
|
sleep(0.2)
|
1128
|
+
|
264
1129
|
s1.stop
|
265
1130
|
sleep(0.2)
|
266
1131
|
end
|