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