dat-worker-pool 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/bench/report.rb +130 -0
- data/bench/report.txt +7 -0
- data/dat-worker-pool.gemspec +1 -1
- data/lib/dat-worker-pool.rb +38 -187
- data/lib/dat-worker-pool/default_queue.rb +60 -0
- data/lib/dat-worker-pool/locked_object.rb +60 -0
- data/lib/dat-worker-pool/queue.rb +71 -48
- data/lib/dat-worker-pool/runner.rb +196 -0
- data/lib/dat-worker-pool/version.rb +1 -1
- data/lib/dat-worker-pool/worker.rb +251 -72
- data/lib/dat-worker-pool/worker_pool_spy.rb +39 -53
- data/test/helper.rb +13 -0
- data/test/support/factory.rb +15 -0
- data/test/support/thread_spies.rb +83 -0
- data/test/system/dat-worker-pool_tests.rb +399 -0
- data/test/unit/dat-worker-pool_tests.rb +132 -255
- data/test/unit/default_queue_tests.rb +217 -0
- data/test/unit/locked_object_tests.rb +260 -0
- data/test/unit/queue_tests.rb +95 -72
- data/test/unit/runner_tests.rb +365 -0
- data/test/unit/worker_pool_spy_tests.rb +95 -102
- data/test/unit/worker_tests.rb +819 -153
- metadata +27 -12
- data/test/system/use_worker_pool_tests.rb +0 -34
data/test/unit/queue_tests.rb
CHANGED
@@ -1,108 +1,131 @@
|
|
1
1
|
require 'assert'
|
2
2
|
require 'dat-worker-pool/queue'
|
3
3
|
|
4
|
-
|
4
|
+
module DatWorkerPool::Queue
|
5
5
|
|
6
6
|
class UnitTests < Assert::Context
|
7
7
|
desc "DatWorkerPool::Queue"
|
8
8
|
setup do
|
9
|
-
@
|
10
|
-
|
11
|
-
subject{ @queue }
|
9
|
+
@queue_class = Class.new do
|
10
|
+
include DatWorkerPool::Queue
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
attr_reader :start_called, :shutdown_called
|
13
|
+
attr_reader :push_called_with
|
14
|
+
attr_accessor :pop_result
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def initialize
|
17
|
+
@start_called = false
|
18
|
+
@shutdown_called = false
|
19
|
+
@push_called_with = nil
|
20
|
+
@pop_result = nil
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
assert_equal [ 'work' ], subject.work_items
|
25
|
-
end
|
23
|
+
def start!; @start_called = true; end
|
24
|
+
def shutdown!; @shutdown_called = true; end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
subject.push 'work'
|
31
|
-
assert_true on_push_called
|
26
|
+
def push!(*args); @push_called_with = args; end
|
27
|
+
def pop!; @pop_result; end
|
28
|
+
end
|
32
29
|
end
|
30
|
+
subject{ @queue_class }
|
33
31
|
|
34
|
-
should "raise
|
35
|
-
|
36
|
-
|
32
|
+
should "raise a not implemented error for `dwp_push` and `dwp_pop` by default" do
|
33
|
+
queue_class = Class.new{ include DatWorkerPool::Queue }
|
34
|
+
queue = queue_class.new.tap(&:dwp_start)
|
35
|
+
|
36
|
+
assert_raises(NotImplementedError){ queue.dwp_push(Factory.string) }
|
37
|
+
assert_raises(NotImplementedError){ queue.dwp_pop }
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
-
subject.push 'work1'
|
41
|
-
subject.push 'work2'
|
40
|
+
end
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
assert_equal 0, subject.work_items.size
|
42
|
+
class InitTests < UnitTests
|
43
|
+
desc "when init"
|
44
|
+
setup do
|
45
|
+
@queue = @queue_class.new
|
48
46
|
end
|
47
|
+
subject{ @queue }
|
48
|
+
|
49
|
+
should have_imeths :work_items
|
50
|
+
should have_imeths :dwp_start, :dwp_signal_shutdown, :dwp_shutdown
|
51
|
+
should have_imeths :running?, :shutdown?
|
52
|
+
should have_imeths :dwp_push, :dwp_pop
|
49
53
|
|
50
|
-
should "
|
51
|
-
subject.
|
52
|
-
on_pop_called = false
|
53
|
-
subject.on_pop_callbacks << proc{ on_pop_called = true }
|
54
|
-
subject.pop
|
55
|
-
assert_true on_pop_called
|
54
|
+
should "raise a not implemented error using `work_items`" do
|
55
|
+
assert_raises(NotImplementedError){ subject.work_items }
|
56
56
|
end
|
57
57
|
|
58
|
-
should "
|
59
|
-
subject.
|
60
|
-
subject.shutdown
|
61
|
-
|
58
|
+
should "set its flags using `dwp_start` and `dwp_shutdown`" do
|
59
|
+
assert_false subject.running?
|
60
|
+
assert_true subject.shutdown?
|
61
|
+
subject.dwp_start
|
62
|
+
assert_true subject.running?
|
63
|
+
assert_false subject.shutdown?
|
64
|
+
subject.dwp_shutdown
|
65
|
+
assert_false subject.running?
|
66
|
+
assert_true subject.shutdown?
|
62
67
|
end
|
63
68
|
|
64
|
-
should "
|
65
|
-
|
66
|
-
subject.
|
67
|
-
|
68
|
-
subject.pop
|
69
|
-
assert subject.empty?
|
69
|
+
should "call `start!` using `dwp_start`" do
|
70
|
+
assert_false subject.start_called
|
71
|
+
subject.dwp_start
|
72
|
+
assert_true subject.start_called
|
70
73
|
end
|
71
74
|
|
72
|
-
should "
|
73
|
-
assert_false subject.
|
74
|
-
subject.
|
75
|
-
|
76
|
-
subject.
|
77
|
-
assert_false subject.
|
75
|
+
should "set its shutdown flag using `dwp_signal_shutdown`" do
|
76
|
+
assert_false subject.running?
|
77
|
+
assert_false subject.shutdown_called
|
78
|
+
subject.dwp_start
|
79
|
+
assert_true subject.running?
|
80
|
+
assert_false subject.shutdown_called
|
81
|
+
subject.dwp_signal_shutdown
|
82
|
+
assert_false subject.running?
|
83
|
+
assert_false subject.shutdown_called
|
78
84
|
end
|
79
85
|
|
80
|
-
|
86
|
+
should "call `shutdown!` using `dwp_shutdown`" do
|
87
|
+
assert_false subject.shutdown_called
|
88
|
+
subject.dwp_shutdown
|
89
|
+
assert_true subject.shutdown_called
|
90
|
+
end
|
81
91
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
92
|
+
should "raise an error if `dwp_push` is called when the queue isn't running" do
|
93
|
+
assert_false subject.running?
|
94
|
+
assert_raise(RuntimeError){ subject.dwp_push(Factory.string) }
|
95
|
+
subject.dwp_start
|
96
|
+
assert_nothing_raised{ subject.dwp_push(Factory.string) }
|
97
|
+
subject.dwp_shutdown
|
98
|
+
assert_raise(RuntimeError){ subject.dwp_push(Factory.string) }
|
88
99
|
end
|
89
100
|
|
90
|
-
should "
|
91
|
-
|
101
|
+
should "call `push!` using `dwp_push`" do
|
102
|
+
subject.dwp_start
|
103
|
+
|
104
|
+
work_item = Factory.string
|
105
|
+
subject.dwp_push(work_item)
|
106
|
+
assert_equal [work_item], subject.push_called_with
|
107
|
+
|
108
|
+
args = Factory.integer(3).times.map{ Factory.string }
|
109
|
+
subject.dwp_push(*args)
|
110
|
+
assert_equal args, subject.push_called_with
|
92
111
|
end
|
93
112
|
|
94
|
-
should "
|
95
|
-
subject.
|
96
|
-
|
97
|
-
|
98
|
-
|
113
|
+
should "return nothing if `dwp_pop` is called when the queue isn't running" do
|
114
|
+
subject.pop_result = Factory.string
|
115
|
+
assert_false subject.running?
|
116
|
+
assert_nil subject.dwp_pop
|
117
|
+
subject.dwp_start
|
118
|
+
assert_not_nil subject.dwp_pop
|
119
|
+
subject.dwp_shutdown
|
120
|
+
assert_nil subject.dwp_pop
|
99
121
|
end
|
100
122
|
|
101
|
-
should "
|
102
|
-
subject.
|
103
|
-
|
104
|
-
|
105
|
-
|
123
|
+
should "call `pop!` using `dwp_pop`" do
|
124
|
+
subject.dwp_start
|
125
|
+
subject.pop_result = Factory.string
|
126
|
+
|
127
|
+
value = subject.dwp_pop
|
128
|
+
assert_equal subject.pop_result, value
|
106
129
|
end
|
107
130
|
|
108
131
|
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'dat-worker-pool/runner'
|
3
|
+
|
4
|
+
require 'dat-worker-pool/default_queue'
|
5
|
+
|
6
|
+
class DatWorkerPool::Runner
|
7
|
+
|
8
|
+
class UnitTests < Assert::Context
|
9
|
+
desc "DatWorkerPool::Runner"
|
10
|
+
setup do
|
11
|
+
@runner_class = DatWorkerPool::Runner
|
12
|
+
end
|
13
|
+
subject{ @runner_class }
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class InitTests < UnitTests
|
18
|
+
desc "when init"
|
19
|
+
setup do
|
20
|
+
# at least 2 workers, up to 4
|
21
|
+
@num_workers = Factory.integer(3) + 1
|
22
|
+
@logger = TEST_LOGGER || Logger.new("/dev/null")
|
23
|
+
@queue = DatWorkerPool::DefaultQueue.new
|
24
|
+
@worker_class = TestWorker
|
25
|
+
@worker_params = { Factory.string => Factory.string }
|
26
|
+
|
27
|
+
@workers = DatWorkerPool::LockedArray.new
|
28
|
+
Assert.stub(DatWorkerPool::LockedArray, :new){ @workers }
|
29
|
+
|
30
|
+
@available_workers_spy = DatWorkerPool::LockedSet.new
|
31
|
+
Assert.stub(DatWorkerPool::LockedSet, :new){ @available_workers_spy }
|
32
|
+
|
33
|
+
@options = {
|
34
|
+
:num_workers => @num_workers,
|
35
|
+
:logger => @logger,
|
36
|
+
:queue => @queue,
|
37
|
+
:worker_class => @worker_class,
|
38
|
+
:worker_params => @worker_params
|
39
|
+
}
|
40
|
+
@runner = @runner_class.new(@options)
|
41
|
+
end
|
42
|
+
teardown do
|
43
|
+
@runner.shutdown(0) rescue false
|
44
|
+
end
|
45
|
+
subject{ @runner }
|
46
|
+
|
47
|
+
should have_readers :num_workers, :worker_class, :worker_params
|
48
|
+
should have_readers :logger_proxy, :queue
|
49
|
+
should have_imeths :workers, :start, :shutdown
|
50
|
+
should have_imeths :available_worker_count, :worker_available?
|
51
|
+
should have_imeths :make_worker_available, :make_worker_unavailable
|
52
|
+
should have_imeths :worker_log
|
53
|
+
|
54
|
+
should "know its attributes" do
|
55
|
+
assert_equal @num_workers, subject.num_workers
|
56
|
+
assert_equal @worker_class, subject.worker_class
|
57
|
+
assert_equal @worker_params, subject.worker_params
|
58
|
+
assert_equal @queue, subject.queue
|
59
|
+
|
60
|
+
assert_instance_of LoggerProxy, subject.logger_proxy
|
61
|
+
assert_equal @logger, subject.logger_proxy.logger
|
62
|
+
end
|
63
|
+
|
64
|
+
should "default its logger" do
|
65
|
+
@options.delete(:logger)
|
66
|
+
runner = @runner_class.new(@options)
|
67
|
+
assert_instance_of NullLoggerProxy, runner.logger_proxy
|
68
|
+
end
|
69
|
+
|
70
|
+
should "know its workers" do
|
71
|
+
assert_equal @workers.values, subject.workers
|
72
|
+
@workers.push(Factory.string)
|
73
|
+
assert_equal @workers.values, subject.workers
|
74
|
+
end
|
75
|
+
|
76
|
+
should "start its queue when its started" do
|
77
|
+
assert_false @queue.running?
|
78
|
+
subject.start
|
79
|
+
assert_true @queue.running?
|
80
|
+
end
|
81
|
+
|
82
|
+
should "build and add workers when its started" do
|
83
|
+
subject.start
|
84
|
+
|
85
|
+
assert_equal @num_workers, subject.workers.size
|
86
|
+
subject.workers.each_with_index do |worker, n|
|
87
|
+
assert_equal subject, worker.dwp_runner
|
88
|
+
assert_equal @queue, worker.dwp_queue
|
89
|
+
assert_equal n + 1, worker.dwp_number
|
90
|
+
assert_true worker.dwp_running?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
should "allow making workers available/unavailable" do
|
95
|
+
worker = @worker_class.new(@runner, @queue, Factory.integer(10))
|
96
|
+
|
97
|
+
assert_not_includes worker.object_id, @available_workers_spy.values
|
98
|
+
assert_false subject.worker_available?
|
99
|
+
subject.make_worker_available(worker)
|
100
|
+
assert_includes worker.object_id, @available_workers_spy.values
|
101
|
+
assert_true subject.worker_available?
|
102
|
+
subject.make_worker_unavailable(worker)
|
103
|
+
assert_not_includes worker.object_id, @available_workers_spy.values
|
104
|
+
assert_false subject.worker_available?
|
105
|
+
end
|
106
|
+
|
107
|
+
should "know how many workers are available" do
|
108
|
+
worker = @worker_class.new(@runner, @queue, Factory.integer(10))
|
109
|
+
|
110
|
+
assert_equal 0, subject.available_worker_count
|
111
|
+
subject.make_worker_available(worker)
|
112
|
+
assert_equal 1, subject.available_worker_count
|
113
|
+
subject.make_worker_unavailable(worker)
|
114
|
+
assert_equal 0, subject.available_worker_count
|
115
|
+
end
|
116
|
+
|
117
|
+
should "allow logging messages using `log`" do
|
118
|
+
logged_message = nil
|
119
|
+
Assert.stub(subject.logger_proxy, :runner_log) do |&mb|
|
120
|
+
logged_message = mb.call
|
121
|
+
end
|
122
|
+
|
123
|
+
text = Factory.text
|
124
|
+
subject.log{ text }
|
125
|
+
assert_equal text, logged_message
|
126
|
+
end
|
127
|
+
|
128
|
+
should "allow workers to log messages using `worker_log`" do
|
129
|
+
passed_worker = nil
|
130
|
+
logged_message = nil
|
131
|
+
Assert.stub(subject.logger_proxy, :worker_log) do |w, &mb|
|
132
|
+
passed_worker = w
|
133
|
+
logged_message = mb.call
|
134
|
+
end
|
135
|
+
worker = @worker_class.new(@runner, @queue, Factory.integer(10))
|
136
|
+
|
137
|
+
text = Factory.text
|
138
|
+
subject.worker_log(worker){ text }
|
139
|
+
assert_same worker, passed_worker
|
140
|
+
assert_equal text, logged_message
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
class ShutdownSetupTests < InitTests
|
146
|
+
desc "and started and shutdown"
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
class ShutdownTests < ShutdownSetupTests
|
151
|
+
setup do
|
152
|
+
@timeout_seconds = nil
|
153
|
+
@optional_timeout_called = false
|
154
|
+
# this acts as a spy but also keeps the shutdown from ever timing out
|
155
|
+
Assert.stub(OptionalTimeout, :new) do |secs, &block|
|
156
|
+
@timeout_seconds = secs
|
157
|
+
@optional_timeout_called = true
|
158
|
+
block.call
|
159
|
+
end
|
160
|
+
|
161
|
+
@options[:worker_class] = ShutdownSpyWorker
|
162
|
+
@runner = @runner_class.new(@options)
|
163
|
+
@runner.start
|
164
|
+
# we need a reference to the workers, the runners workers will get removed
|
165
|
+
# as they shutdown
|
166
|
+
@running_workers = @runner.workers.dup
|
167
|
+
end
|
168
|
+
|
169
|
+
should "optionally timeout when shutdown" do
|
170
|
+
subject.shutdown
|
171
|
+
assert_nil @timeout_seconds
|
172
|
+
assert_true @optional_timeout_called
|
173
|
+
|
174
|
+
@optional_timeout_called = false
|
175
|
+
seconds = Factory.integer
|
176
|
+
subject.shutdown(seconds)
|
177
|
+
assert_equal seconds, @timeout_seconds
|
178
|
+
assert_true @optional_timeout_called
|
179
|
+
end
|
180
|
+
|
181
|
+
should "shutdown all of its workers" do
|
182
|
+
@running_workers.each do |worker|
|
183
|
+
assert_false worker.dwp_shutdown?
|
184
|
+
end
|
185
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
186
|
+
@running_workers.each do |worker|
|
187
|
+
assert_true worker.dwp_shutdown?
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
should "shutdown its queue" do
|
192
|
+
assert_false @queue.shutdown?
|
193
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
194
|
+
assert_true @queue.shutdown?
|
195
|
+
end
|
196
|
+
|
197
|
+
should "join its workers waiting for them to finish" do
|
198
|
+
@running_workers.each do |worker|
|
199
|
+
assert_false worker.join_called
|
200
|
+
end
|
201
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
202
|
+
@running_workers.each do |worker|
|
203
|
+
assert_true worker.join_called
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
should "join all workers even if one raises an error when joined" do
|
208
|
+
@running_workers.choice.join_error = Factory.exception
|
209
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
210
|
+
@running_workers.each do |worker|
|
211
|
+
assert_true worker.join_called
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
should "remove workers as they finish" do
|
216
|
+
assert_false subject.workers.empty?
|
217
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
218
|
+
assert_true subject.workers.empty?
|
219
|
+
end
|
220
|
+
|
221
|
+
should "remove workers and make them unavailable even if they error" do
|
222
|
+
@running_workers.each{ |w| w.join_error = Factory.exception }
|
223
|
+
|
224
|
+
assert_false subject.workers.empty?
|
225
|
+
assert_false @available_workers_spy.empty?
|
226
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
227
|
+
assert_true subject.workers.empty?
|
228
|
+
assert_true @available_workers_spy.empty?
|
229
|
+
end
|
230
|
+
|
231
|
+
should "force its workers to shutdown if a timeout error occurs" do
|
232
|
+
Assert.stub(OptionalTimeout, :new){ raise TimeoutInterruptError }
|
233
|
+
subject.shutdown(Factory.integer)
|
234
|
+
|
235
|
+
@running_workers.each do |worker|
|
236
|
+
assert_instance_of DatWorkerPool::ShutdownError, worker.raised_error
|
237
|
+
assert_true worker.join_called
|
238
|
+
end
|
239
|
+
assert_true subject.workers.empty?
|
240
|
+
assert_true @available_workers_spy.empty?
|
241
|
+
end
|
242
|
+
|
243
|
+
should "force its workers to shutdown if a non-timeout error occurs" do
|
244
|
+
queue_exception = Factory.exception
|
245
|
+
Assert.stub(@queue, :dwp_shutdown){ raise queue_exception }
|
246
|
+
|
247
|
+
caught_exception = nil
|
248
|
+
begin
|
249
|
+
subject.shutdown(Factory.integer)
|
250
|
+
rescue StandardError => caught_exception
|
251
|
+
end
|
252
|
+
assert_same queue_exception, caught_exception
|
253
|
+
|
254
|
+
@running_workers.each do |worker|
|
255
|
+
assert_instance_of DatWorkerPool::ShutdownError, worker.raised_error
|
256
|
+
assert_true worker.join_called
|
257
|
+
end
|
258
|
+
assert_true subject.workers.empty?
|
259
|
+
assert_true @available_workers_spy.empty?
|
260
|
+
end
|
261
|
+
|
262
|
+
should "force shutdown all of its workers even if one raises an error when joining" do
|
263
|
+
Assert.stub(OptionalTimeout, :new){ raise TimeoutInterruptError }
|
264
|
+
error_class = Factory.boolean ? DatWorkerPool::ShutdownError : RuntimeError
|
265
|
+
@running_workers.choice.join_error = Factory.exception(error_class)
|
266
|
+
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
267
|
+
|
268
|
+
@running_workers.each do |worker|
|
269
|
+
assert_instance_of DatWorkerPool::ShutdownError, worker.raised_error
|
270
|
+
assert_true worker.join_called
|
271
|
+
end
|
272
|
+
assert_true subject.workers.empty?
|
273
|
+
assert_true @available_workers_spy.empty?
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
class LoggerProxyTests < UnitTests
|
279
|
+
desc "LoggerProxy"
|
280
|
+
setup do
|
281
|
+
@stringio = StringIO.new
|
282
|
+
@logger = Logger.new(@stringio)
|
283
|
+
@logger_proxy = LoggerProxy.new(@logger)
|
284
|
+
end
|
285
|
+
subject{ @logger_proxy }
|
286
|
+
|
287
|
+
should have_readers :logger
|
288
|
+
should have_imeths :runner_log, :worker_log
|
289
|
+
|
290
|
+
should "know its logger" do
|
291
|
+
assert_equal @logger, subject.logger
|
292
|
+
end
|
293
|
+
|
294
|
+
should "log a message block for a runner using `runner_log`" do
|
295
|
+
text = Factory.text
|
296
|
+
subject.runner_log{ text }
|
297
|
+
assert_match "[DWP] #{text}", @stringio.string
|
298
|
+
end
|
299
|
+
|
300
|
+
should "log a message block for a worker using `worker_log`" do
|
301
|
+
worker = FakeWorker.new(Factory.integer(10))
|
302
|
+
text = Factory.text
|
303
|
+
subject.worker_log(worker){ text }
|
304
|
+
assert_match "[DWP-#{worker.dwp_number}] #{text}", @stringio.string
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
class NullLoggerProxyTests < UnitTests
|
310
|
+
desc "NullLoggerProxy"
|
311
|
+
setup do
|
312
|
+
@null_logger_proxy = NullLoggerProxy.new
|
313
|
+
end
|
314
|
+
subject{ @null_logger_proxy }
|
315
|
+
|
316
|
+
should have_imeths :runner_log, :worker_log
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
class TestWorker
|
321
|
+
include DatWorkerPool::Worker
|
322
|
+
|
323
|
+
# for testing what is passed to the worker
|
324
|
+
attr_reader :dwp_runner, :dwp_queue
|
325
|
+
end
|
326
|
+
|
327
|
+
FakeWorker = Struct.new(:dwp_number)
|
328
|
+
|
329
|
+
class ShutdownSpyWorker < TestWorker
|
330
|
+
attr_reader :join_called
|
331
|
+
attr_accessor :join_error, :raised_error
|
332
|
+
|
333
|
+
# this pauses the shutdown so we can test that join or raise are called
|
334
|
+
# depending if we are doing a standard or forced shutdown; otherwise the
|
335
|
+
# worker threads can exit before join or raise ever gets called on them and
|
336
|
+
# then there is nothing to test
|
337
|
+
on_shutdown{ wait_for_join_or_raise }
|
338
|
+
|
339
|
+
def initialize(*args)
|
340
|
+
super
|
341
|
+
@mutex = Mutex.new
|
342
|
+
@cond_var = ConditionVariable.new
|
343
|
+
@join_called = false
|
344
|
+
@join_error = nil
|
345
|
+
@raised_error = nil
|
346
|
+
end
|
347
|
+
|
348
|
+
def dwp_join(*args)
|
349
|
+
@join_called = true
|
350
|
+
raise @join_error if @join_error
|
351
|
+
@mutex.synchronize{ @cond_var.broadcast }
|
352
|
+
end
|
353
|
+
|
354
|
+
def dwp_raise(error)
|
355
|
+
@raised_error = error
|
356
|
+
end
|
357
|
+
|
358
|
+
private
|
359
|
+
|
360
|
+
def wait_for_join_or_raise
|
361
|
+
@mutex.synchronize{ @cond_var.wait(@mutex) }
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|