concurrent-ruby 0.6.1 → 0.7.0.rc0

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