concurrent-ruby 0.2.2 → 0.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -42
  3. data/lib/concurrent.rb +5 -6
  4. data/lib/concurrent/agent.rb +29 -33
  5. data/lib/concurrent/cached_thread_pool.rb +26 -105
  6. data/lib/concurrent/channel.rb +94 -0
  7. data/lib/concurrent/event.rb +8 -17
  8. data/lib/concurrent/executor.rb +68 -72
  9. data/lib/concurrent/fixed_thread_pool.rb +15 -83
  10. data/lib/concurrent/functions.rb +7 -22
  11. data/lib/concurrent/future.rb +29 -9
  12. data/lib/concurrent/null_thread_pool.rb +5 -2
  13. data/lib/concurrent/obligation.rb +6 -16
  14. data/lib/concurrent/promise.rb +9 -10
  15. data/lib/concurrent/runnable.rb +103 -0
  16. data/lib/concurrent/supervisor.rb +271 -44
  17. data/lib/concurrent/thread_pool.rb +112 -39
  18. data/lib/concurrent/version.rb +1 -1
  19. data/md/executor.md +9 -3
  20. data/md/goroutine.md +11 -9
  21. data/md/reactor.md +32 -0
  22. data/md/supervisor.md +43 -0
  23. data/spec/concurrent/agent_spec.rb +128 -51
  24. data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
  25. data/spec/concurrent/channel_spec.rb +446 -0
  26. data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
  27. data/spec/concurrent/event_spec.rb +0 -19
  28. data/spec/concurrent/executor_spec.rb +167 -119
  29. data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
  30. data/spec/concurrent/functions_spec.rb +0 -20
  31. data/spec/concurrent/future_spec.rb +88 -0
  32. data/spec/concurrent/null_thread_pool_spec.rb +23 -2
  33. data/spec/concurrent/obligation_shared.rb +0 -5
  34. data/spec/concurrent/promise_spec.rb +9 -10
  35. data/spec/concurrent/runnable_shared.rb +62 -0
  36. data/spec/concurrent/runnable_spec.rb +233 -0
  37. data/spec/concurrent/supervisor_spec.rb +912 -47
  38. data/spec/concurrent/thread_pool_shared.rb +18 -31
  39. data/spec/spec_helper.rb +10 -3
  40. metadata +17 -23
  41. data/lib/concurrent/defer.rb +0 -65
  42. data/lib/concurrent/reactor.rb +0 -166
  43. data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
  44. data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
  45. data/lib/concurrent/utilities.rb +0 -32
  46. data/md/defer.md +0 -174
  47. data/spec/concurrent/defer_spec.rb +0 -199
  48. data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
  49. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
  50. data/spec/concurrent/reactor_spec.rb +0 -364
  51. 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
- def run() return true; end
11
- def stop() return true; end
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 'sets the monitor interval when given' do
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 'sets the monitor interval to the default when not given' do
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
- supervisor = Supervisor.new(monitor: 10)
51
- supervisor.monitor_interval.should == 10
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{ subject.run }
88
- @thread.abort_on_exception = true
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
- it 'stops the monitor thread' do
128
- Thread.should_receive(:kill).with(anything())
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{ worker_class.new }
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(monitor: 0.1)
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 'rejects the worker when running' do
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
- subject.add_worker(worker)
198
- subject.length.should == 0
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 'returns true when a worker is accepted' do
207
- subject.add_worker(worker).should be_true
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 false when a worker is not accepted' do
211
- subject.add_worker('bogus worker').should be_false
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 'supervision' do
548
+ context '#add_workers' do
216
549
 
217
- it 'reruns any worker that stops' do
218
- worker = Class.new(worker_class){
219
- def run() sleep(0.2); end
220
- }.new
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
- supervisor = Supervisor.new(worker: worker, monitor: 0.1)
223
- supervisor.add_worker(worker)
224
- # must stub AFTER adding or else #add_worker will reject
225
- worker.should_receive(:run).with(no_args()).at_least(2).times
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
- it 'reruns any dead threads' do
232
- worker = Class.new(worker_class){
233
- def run() raise StandardError; end
234
- }.new
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(1)
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 'supervisor tree' do
1111
+ context 'supervision tree' do
247
1112
 
248
1113
  specify do
249
- s1 = Supervisor.new(monitor: 0.1)
250
- s2 = Supervisor.new(monitor: 0.1)
251
- s3 = Supervisor.new(monitor: 0.1)
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{ worker_class.new }
1118
+ workers = (1..3).collect{ sleeper_class.new }
254
1119
  workers.each{|worker| s3.add_worker(worker)}
255
- # must stub AFTER adding or else #add_worker will reject
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