dat-worker-pool 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +0 -1
- data/{LICENSE.txt → LICENSE} +0 -0
- data/dat-worker-pool.gemspec +3 -3
- data/lib/dat-worker-pool.rb +1 -1
- data/lib/dat-worker-pool/runner.rb +18 -41
- data/lib/dat-worker-pool/version.rb +1 -1
- data/test/helper.rb +8 -1
- data/test/support/signal_test_worker.rb +71 -0
- data/test/system/dat-worker-pool_tests.rb +19 -65
- data/test/unit/dat-worker-pool_tests.rb +5 -10
- data/test/unit/default_queue_tests.rb +4 -0
- data/test/unit/runner_tests.rb +101 -64
- data/test/unit/worker_tests.rb +21 -15
- metadata +20 -42
- data/Rakefile +0 -1
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA512:
|
3
|
+
data.tar.gz: c0a52d7d5c533e9d71623775137e77131dcc4eceae71e4e55085f0248983442518e394fbca0aeaf02be04f6bf7484dd139ad84f4c7e165c194057d662d7689f2
|
4
|
+
metadata.gz: 8c5250174e3f695c2ad78ab377e1c29973dd37295abe65cd94d3b2a266ce0171087ab1262316004591882de23497a0e24649f8c1b0c7d75ec3cbb31fdc589938
|
5
|
+
SHA1:
|
6
|
+
data.tar.gz: 4a978550e606a83e897ca45f4eaf5e8c8e945622
|
7
|
+
metadata.gz: 43f2dd81c2d52ce758a87a403381536899526c58
|
data/Gemfile
CHANGED
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/dat-worker-pool.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = DatWorkerPool::VERSION
|
9
9
|
gem.authors = ["Collin Redding", "Kelly Redding"]
|
10
10
|
gem.email = ["collin.redding@me.com", "kelly@kellyredding.com"]
|
11
|
-
gem.description = "A simple thread pool for processing generic 'work'"
|
12
11
|
gem.summary = "A simple thread pool for processing generic 'work'"
|
12
|
+
gem.description = "A simple thread pool for processing generic 'work'"
|
13
13
|
gem.homepage = "http://github.com/redding/dat-worker-pool"
|
14
14
|
gem.license = 'MIT'
|
15
15
|
|
@@ -18,8 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.
|
21
|
+
gem.add_development_dependency("assert", ["~> 2.16.1"])
|
22
22
|
|
23
|
-
gem.
|
23
|
+
gem.add_dependency("much-timeout", ["~> 0.1.0"])
|
24
24
|
|
25
25
|
end
|
data/lib/dat-worker-pool.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
require 'much-timeout'
|
1
2
|
require 'set'
|
2
|
-
require 'system_timer'
|
3
3
|
require 'dat-worker-pool'
|
4
4
|
require 'dat-worker-pool/locked_object'
|
5
5
|
|
@@ -46,7 +46,7 @@ class DatWorkerPool
|
|
46
46
|
# instead of letting ruby kill the threads when the process exits;
|
47
47
|
# non-timeout errors will be re-raised so they can be caught and handled (or
|
48
48
|
# shown when the process exits)
|
49
|
-
def shutdown(timeout = nil
|
49
|
+
def shutdown(timeout = nil)
|
50
50
|
log do
|
51
51
|
timeout_message = timeout ? "#{timeout} second(s)" : "none"
|
52
52
|
"Shutting down worker pool (timeout: #{timeout_message})"
|
@@ -54,15 +54,21 @@ class DatWorkerPool
|
|
54
54
|
begin
|
55
55
|
@workers.with_lock{ |m, ws| ws.each(&:dwp_signal_shutdown) }
|
56
56
|
@queue.dwp_signal_shutdown
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
MuchTimeout.just_optional_timeout(timeout, {
|
58
|
+
:do => proc{
|
59
|
+
@queue.dwp_shutdown
|
60
|
+
wait_for_workers_to_shutdown
|
61
|
+
},
|
62
|
+
:on_timeout => proc{
|
63
|
+
e = ShutdownError.new("Timed out shutting down (#{timeout} seconds).")
|
64
|
+
force_workers_to_shutdown(e, timeout)
|
65
|
+
}
|
66
|
+
}) do
|
60
67
|
end
|
61
|
-
rescue StandardError =>
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
force_workers_to_shutdown(exception, timeout, backtrace)
|
68
|
+
rescue StandardError => err
|
69
|
+
e = ShutdownError.new("Errored while shutting down: #{err.inspect}")
|
70
|
+
force_workers_to_shutdown(e, timeout)
|
71
|
+
raise err
|
66
72
|
end
|
67
73
|
log{ "Finished shutting down" }
|
68
74
|
end
|
@@ -116,16 +122,15 @@ class DatWorkerPool
|
|
116
122
|
end
|
117
123
|
end
|
118
124
|
|
119
|
-
# use
|
125
|
+
# use a while loop instead of each to join all the workers, while we are
|
120
126
|
# joining a worker a different worker can shutdown and remove itself from
|
121
127
|
# the `@workers` array; rescue when joining the workers, ruby will raise any
|
122
128
|
# exceptions that aren't handled by a thread when its joined, this ensures
|
123
129
|
# if the hard shutdown is raised and not rescued (for example, in the
|
124
130
|
# workers ensure), then it won't cause the forced shutdown to end
|
125
131
|
# prematurely
|
126
|
-
def force_workers_to_shutdown(
|
132
|
+
def force_workers_to_shutdown(error, timeout)
|
127
133
|
log{ "Forcing #{@workers.size} workers to shutdown" }
|
128
|
-
error = build_forced_shutdown_error(orig_exception, timeout, backtrace)
|
129
134
|
while !(worker = @workers.first).nil?
|
130
135
|
worker.dwp_raise(error)
|
131
136
|
begin
|
@@ -149,34 +154,6 @@ class DatWorkerPool
|
|
149
154
|
@workers.delete(worker)
|
150
155
|
end
|
151
156
|
|
152
|
-
def build_forced_shutdown_error(orig_exception, timeout, backtrace)
|
153
|
-
if orig_exception.kind_of?(TimeoutInterruptError)
|
154
|
-
ShutdownError.new("Timed out shutting down (#{timeout} seconds).").tap do |e|
|
155
|
-
e.set_backtrace(backtrace) if backtrace
|
156
|
-
end
|
157
|
-
else
|
158
|
-
ShutdownError.new("Errored while shutting down: #{orig_exception.inspect}").tap do |e|
|
159
|
-
e.set_backtrace(orig_exception.backtrace)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
# this needs to be an `Interrupt` to be sure we don't accidentally catch it
|
165
|
-
# when rescueing exceptions; in the shutdown methods we rescue any errors
|
166
|
-
# from `worker.join`, this will also rescue the timeout error if its a
|
167
|
-
# standard error and will keep it from doing a forced shutdown
|
168
|
-
TimeoutInterruptError = Class.new(Interrupt)
|
169
|
-
|
170
|
-
module OptionalTimeout
|
171
|
-
def self.new(seconds, &block)
|
172
|
-
if seconds
|
173
|
-
SystemTimer.timeout(seconds, TimeoutInterruptError, &block)
|
174
|
-
else
|
175
|
-
block.call
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
157
|
class LoggerProxy < Struct.new(:logger)
|
181
158
|
def runner_log(&message_block)
|
182
159
|
self.logger.debug("[DWP] #{message_block.call}")
|
data/test/helper.rb
CHANGED
@@ -20,4 +20,11 @@ JOIN_SECONDS = 0.001
|
|
20
20
|
|
21
21
|
require 'test/support/factory'
|
22
22
|
|
23
|
-
#
|
23
|
+
# 1.8.7 backfills
|
24
|
+
|
25
|
+
# Array#sample
|
26
|
+
if !(a = Array.new).respond_to?(:sample) && a.respond_to?(:choice)
|
27
|
+
class Array
|
28
|
+
alias_method :sample, :choice
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'much-timeout'
|
2
|
+
require 'dat-worker-pool/worker'
|
3
|
+
|
4
|
+
module SignalTestWorker
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval{ include DatWorkerPool::Worker }
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def signal_test_suite_thread
|
12
|
+
params[:signal_worker_mutex].synchronize do
|
13
|
+
params[:signal_worker_cond_var].signal
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module TestHelpers
|
18
|
+
|
19
|
+
def self.included(klass)
|
20
|
+
klass.class_eval do
|
21
|
+
setup do
|
22
|
+
# at least 2 workers, up to 4
|
23
|
+
@num_workers = Factory.integer(3) + 1
|
24
|
+
|
25
|
+
@signal_worker_mutex = Mutex.new
|
26
|
+
@signal_worker_cond_var = ConditionVariable.new
|
27
|
+
|
28
|
+
@worker_params = {
|
29
|
+
:signal_worker_mutex => @signal_worker_mutex,
|
30
|
+
:signal_worker_cond_var => @signal_worker_cond_var
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# this could loop forever so ensure it doesn't by using a timeout
|
39
|
+
def wait_for_workers(&block)
|
40
|
+
MuchTimeout.just_timeout(1, {
|
41
|
+
:do => proc{
|
42
|
+
while !block.call do
|
43
|
+
@signal_worker_mutex.synchronize do
|
44
|
+
@signal_worker_cond_var.wait(@signal_worker_mutex)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
},
|
48
|
+
:on_timeout => proc{
|
49
|
+
raise "timed out waiting for workers" unless block.call
|
50
|
+
}
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
def wait_for_workers_to_become_available
|
55
|
+
wait_for_workers{ subject.available_worker_count == @num_workers }
|
56
|
+
end
|
57
|
+
|
58
|
+
def wait_for_workers_to_become_unavailable
|
59
|
+
wait_for_workers{ subject.available_worker_count == 0 }
|
60
|
+
end
|
61
|
+
|
62
|
+
def wait_for_a_worker_to_become_available
|
63
|
+
wait_for_workers{ subject.available_worker_count != 0 }
|
64
|
+
end
|
65
|
+
|
66
|
+
def wait_for_a_worker_to_become_unavailable
|
67
|
+
wait_for_workers{ subject.available_worker_count != @num_workers }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -1,58 +1,24 @@
|
|
1
1
|
require 'assert'
|
2
2
|
require 'dat-worker-pool'
|
3
3
|
|
4
|
-
require 'timeout'
|
5
4
|
require 'dat-worker-pool/locked_object'
|
6
5
|
require 'dat-worker-pool/worker'
|
6
|
+
require 'test/support/signal_test_worker'
|
7
7
|
|
8
8
|
class DatWorkerPool
|
9
9
|
|
10
10
|
class SystemTests < Assert::Context
|
11
|
+
include SignalTestWorker::TestHelpers
|
12
|
+
|
11
13
|
desc "DatWorkerPool"
|
12
|
-
setup do
|
13
|
-
# at least 2 workers, up to 4
|
14
|
-
@num_workers = Factory.integer(3) + 1
|
15
|
-
@mutex = Mutex.new
|
16
|
-
@cond_var = ConditionVariable.new
|
17
|
-
@worker_params = {
|
18
|
-
:mutex => @mutex,
|
19
|
-
:cond_var => @cond_var
|
20
|
-
}
|
21
|
-
end
|
22
14
|
subject{ @worker_pool }
|
23
15
|
|
24
|
-
# this could loop forever so ensure it doesn't by using a timeout; use
|
25
|
-
# timeout instead of system timer because system timer is paranoid about a
|
26
|
-
# deadlock even though its intended to prevent the deadlock because it times
|
27
|
-
# out the block
|
28
|
-
def wait_for_workers(&block)
|
29
|
-
Timeout.timeout(1) do
|
30
|
-
@mutex.synchronize{ @cond_var.wait(@mutex) } while !block.call
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def wait_for_workers_to_become_available
|
35
|
-
wait_for_workers{ @worker_pool.available_worker_count == @num_workers }
|
36
|
-
end
|
37
|
-
|
38
|
-
def wait_for_workers_to_become_unavailable
|
39
|
-
wait_for_workers{ @worker_pool.available_worker_count == 0 }
|
40
|
-
end
|
41
|
-
|
42
|
-
def wait_for_a_worker_to_become_available
|
43
|
-
wait_for_workers{ @worker_pool.available_worker_count != 0 }
|
44
|
-
end
|
45
|
-
|
46
|
-
def wait_for_a_worker_to_become_unavailable
|
47
|
-
wait_for_workers{ @worker_pool.available_worker_count != @num_workers }
|
48
|
-
end
|
49
|
-
|
50
16
|
end
|
51
17
|
|
52
18
|
class StartAddProcessAndShutdownTests < SystemTests
|
53
19
|
setup do
|
54
20
|
@worker_class = Class.new do
|
55
|
-
include
|
21
|
+
include SignalTestWorker
|
56
22
|
def work!(number)
|
57
23
|
params[:results].push(number * 100)
|
58
24
|
signal_test_suite_thread
|
@@ -88,17 +54,20 @@ class DatWorkerPool
|
|
88
54
|
class WorkerAvailabilityTests < SystemTests
|
89
55
|
setup do
|
90
56
|
@worker_class = Class.new do
|
91
|
-
include
|
57
|
+
include SignalTestWorker
|
92
58
|
on_available{ signal_test_suite_thread }
|
93
59
|
on_unavailable{ signal_test_suite_thread }
|
94
60
|
|
95
61
|
# this allows controlling how many workers are available and unavailable
|
96
62
|
# the worker will be unavailable until we signal it
|
97
63
|
def work!(work_item)
|
64
|
+
params[:working].add(self)
|
98
65
|
mutex, cond_var = work_item
|
99
66
|
mutex.synchronize{ cond_var.wait(mutex) }
|
67
|
+
params[:working].remove(self)
|
100
68
|
end
|
101
69
|
end
|
70
|
+
@working = LockedSet.new
|
102
71
|
@work_mutex = Mutex.new
|
103
72
|
@work_cond_var = ConditionVariable.new
|
104
73
|
@work_item = [@work_mutex, @work_cond_var]
|
@@ -106,7 +75,7 @@ class DatWorkerPool
|
|
106
75
|
@worker_pool = DatWorkerPool.new(@worker_class, {
|
107
76
|
:num_workers => @num_workers,
|
108
77
|
:logger => TEST_LOGGER,
|
109
|
-
:worker_params => @worker_params
|
78
|
+
:worker_params => @worker_params.merge(:working => @working)
|
110
79
|
})
|
111
80
|
@worker_pool.start
|
112
81
|
end
|
@@ -131,7 +100,7 @@ class DatWorkerPool
|
|
131
100
|
# make the rest of the workers unavailable
|
132
101
|
(@num_workers - 1).times{ subject.add_work(@work_item) }
|
133
102
|
|
134
|
-
|
103
|
+
wait_for_workers{ @working.size == @num_workers }
|
135
104
|
assert_equal 0, subject.available_worker_count
|
136
105
|
assert_false subject.worker_available?
|
137
106
|
|
@@ -155,7 +124,7 @@ class DatWorkerPool
|
|
155
124
|
class WorkerCallbackTests < SystemTests
|
156
125
|
setup do
|
157
126
|
@worker_class = Class.new do
|
158
|
-
include
|
127
|
+
include SignalTestWorker
|
159
128
|
|
160
129
|
on_start{ params[:callbacks_called][:on_start] = true }
|
161
130
|
on_shutdown{ params[:callbacks_called][:on_shutdown] = true }
|
@@ -269,7 +238,7 @@ class DatWorkerPool
|
|
269
238
|
class ShutdownSystemTests < SystemTests
|
270
239
|
setup do
|
271
240
|
@worker_class = Class.new do
|
272
|
-
include
|
241
|
+
include SignalTestWorker
|
273
242
|
on_available{ signal_test_suite_thread }
|
274
243
|
on_unavailable{ signal_test_suite_thread }
|
275
244
|
|
@@ -280,12 +249,15 @@ class DatWorkerPool
|
|
280
249
|
# this allows controlling how long a worker takes to finish processing
|
281
250
|
# the work item
|
282
251
|
def work!(work_item)
|
252
|
+
params[:working].add(self)
|
283
253
|
params[:work_mutex].synchronize do
|
284
254
|
params[:work_cond_var].wait(params[:work_mutex])
|
285
255
|
end
|
286
256
|
params[:finished].push(work_item)
|
257
|
+
params[:working].remove(self)
|
287
258
|
end
|
288
259
|
end
|
260
|
+
@working = LockedSet.new
|
289
261
|
@work_mutex = Mutex.new
|
290
262
|
@work_cond_var = ConditionVariable.new
|
291
263
|
@finished = LockedArray.new
|
@@ -295,6 +267,7 @@ class DatWorkerPool
|
|
295
267
|
:num_workers => @num_workers,
|
296
268
|
:logger => TEST_LOGGER,
|
297
269
|
:worker_params => @worker_params.merge({
|
270
|
+
:working => @working,
|
298
271
|
:work_mutex => @work_mutex,
|
299
272
|
:work_cond_var => @work_cond_var,
|
300
273
|
:finished => @finished,
|
@@ -308,7 +281,7 @@ class DatWorkerPool
|
|
308
281
|
# add 1 more work item than we have workers to handle it
|
309
282
|
@work_items = (@num_workers + 1).times.map{ Factory.string }
|
310
283
|
@work_items.each{ |wi| @worker_pool.add_work(wi) }
|
311
|
-
|
284
|
+
wait_for_workers{ @working.size == @num_workers }
|
312
285
|
end
|
313
286
|
teardown do
|
314
287
|
# ensure we wakeup any workers still stuck in their `work!`
|
@@ -351,17 +324,12 @@ class DatWorkerPool
|
|
351
324
|
assert_false shutdown_thread.alive?
|
352
325
|
end
|
353
326
|
|
354
|
-
should "allow any work that has been picked up to finish processing " \
|
327
|
+
should "not allow any work that has been picked up to finish processing " \
|
355
328
|
"when forced to shutdown because it timed out" do
|
356
329
|
assert_true @finished.empty?
|
357
330
|
assert_true @errored.empty?
|
358
331
|
|
359
|
-
|
360
|
-
# finishes; this is required otherwise system timer will think we are
|
361
|
-
# triggering a deadlock (it's not a deadlock because of the timeout)
|
362
|
-
shutdown_thread = Thread.new{ subject.shutdown(0) }
|
363
|
-
shutdown_thread.join(JOIN_SECONDS)
|
364
|
-
assert_equal 'sleep', shutdown_thread.status
|
332
|
+
subject.shutdown(0)
|
365
333
|
|
366
334
|
# wait for the workers to get forced to exit
|
367
335
|
wait_for_workers{ @errored.size == @num_workers }
|
@@ -378,22 +346,8 @@ class DatWorkerPool
|
|
378
346
|
assert_instance_of ShutdownError, exception
|
379
347
|
assert_includes work_item, @work_items[0, @num_workers]
|
380
348
|
end
|
381
|
-
|
382
|
-
# ensure the shutdown exits
|
383
|
-
shutdown_thread.join
|
384
|
-
assert_false shutdown_thread.alive?
|
385
|
-
end
|
386
|
-
|
387
|
-
end
|
388
|
-
|
389
|
-
module SystemTestWorker
|
390
|
-
def self.included(klass)
|
391
|
-
klass.class_eval{ include DatWorkerPool::Worker }
|
392
349
|
end
|
393
350
|
|
394
|
-
def signal_test_suite_thread
|
395
|
-
params[:mutex].synchronize{ params[:cond_var].signal }
|
396
|
-
end
|
397
351
|
end
|
398
352
|
|
399
353
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'assert'
|
2
2
|
require 'dat-worker-pool'
|
3
3
|
|
4
|
-
require 'system_timer'
|
5
4
|
require 'dat-worker-pool/queue'
|
6
5
|
require 'dat-worker-pool/runner'
|
7
6
|
require 'dat-worker-pool/worker'
|
@@ -97,8 +96,6 @@ class DatWorkerPool
|
|
97
96
|
subject.shutdown
|
98
97
|
assert_true @runner_spy.shutdown_called
|
99
98
|
assert_nil @runner_spy.shutdown_timeout
|
100
|
-
exp = "test/unit/dat-worker-pool_tests.rb"
|
101
|
-
assert_match exp, @runner_spy.shutdown_backtrace.first
|
102
99
|
|
103
100
|
timeout = Factory.integer
|
104
101
|
subject.shutdown(timeout)
|
@@ -118,7 +115,7 @@ class DatWorkerPool
|
|
118
115
|
should "raise an argument error if given an invalid number of workers" do
|
119
116
|
assert_raises(ArgumentError) do
|
120
117
|
@worker_pool_class.new(@worker_class, {
|
121
|
-
:num_workers => [0, (Factory.integer * -1)].
|
118
|
+
:num_workers => [0, (Factory.integer * -1)].sample
|
122
119
|
})
|
123
120
|
end
|
124
121
|
end
|
@@ -172,14 +169,13 @@ class DatWorkerPool
|
|
172
169
|
class RunnerSpy < DatWorkerPool::Runner
|
173
170
|
attr_accessor :args
|
174
171
|
attr_reader :start_called, :shutdown_called
|
175
|
-
attr_reader :shutdown_timeout
|
172
|
+
attr_reader :shutdown_timeout
|
176
173
|
|
177
174
|
def initialize
|
178
175
|
super({})
|
179
176
|
@start_called = false
|
180
177
|
@shutdown_called = false
|
181
178
|
@shutdown_timeout = nil
|
182
|
-
@shutdown_backtrace = nil
|
183
179
|
end
|
184
180
|
|
185
181
|
def start
|
@@ -187,11 +183,10 @@ class DatWorkerPool
|
|
187
183
|
@start_called = true
|
188
184
|
end
|
189
185
|
|
190
|
-
def shutdown(timeout
|
186
|
+
def shutdown(timeout)
|
191
187
|
@args[:queue].dwp_shutdown
|
192
|
-
@shutdown_called
|
193
|
-
@shutdown_timeout
|
194
|
-
@shutdown_backtrace = backtrace
|
188
|
+
@shutdown_called = true
|
189
|
+
@shutdown_timeout = timeout
|
195
190
|
end
|
196
191
|
end
|
197
192
|
|
@@ -162,6 +162,7 @@ class DatWorkerPool::DefaultQueue
|
|
162
162
|
|
163
163
|
value = Factory.string
|
164
164
|
@queue.dwp_push(value)
|
165
|
+
subject.join(JOIN_SECONDS)
|
165
166
|
|
166
167
|
assert_not subject.alive?
|
167
168
|
assert_equal value, subject['popped_value']
|
@@ -176,6 +177,7 @@ class DatWorkerPool::DefaultQueue
|
|
176
177
|
# sleeps and grabs the lock and work item before the thread being woken
|
177
178
|
# up
|
178
179
|
@work_items.with_lock{ @cond_var_spy.signal }
|
180
|
+
subject.join(JOIN_SECONDS)
|
179
181
|
|
180
182
|
assert_equal 'sleep', subject.status
|
181
183
|
assert_equal 2, @cond_var_spy.wait_call_count
|
@@ -186,6 +188,7 @@ class DatWorkerPool::DefaultQueue
|
|
186
188
|
assert_equal 1, @cond_var_spy.wait_call_count
|
187
189
|
|
188
190
|
@queue.dwp_shutdown
|
191
|
+
subject.join(JOIN_SECONDS)
|
189
192
|
|
190
193
|
assert_not subject.alive?
|
191
194
|
assert_equal 1, @cond_var_spy.wait_call_count
|
@@ -207,6 +210,7 @@ class DatWorkerPool::DefaultQueue
|
|
207
210
|
# accesses the array directly and pushes an item on it
|
208
211
|
@work_items.push(Factory.string)
|
209
212
|
@queue.dwp_shutdown
|
213
|
+
subject.join(JOIN_SECONDS)
|
210
214
|
|
211
215
|
assert_not subject.alive?
|
212
216
|
assert_nil subject['popped_value']
|
data/test/unit/runner_tests.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
require 'assert'
|
2
2
|
require 'dat-worker-pool/runner'
|
3
3
|
|
4
|
+
require 'much-timeout'
|
4
5
|
require 'dat-worker-pool/default_queue'
|
6
|
+
require 'test/support/signal_test_worker'
|
5
7
|
|
6
8
|
class DatWorkerPool::Runner
|
7
9
|
|
8
10
|
class UnitTests < Assert::Context
|
11
|
+
include SignalTestWorker::TestHelpers
|
12
|
+
|
9
13
|
desc "DatWorkerPool::Runner"
|
10
14
|
setup do
|
11
15
|
@runner_class = DatWorkerPool::Runner
|
@@ -17,12 +21,13 @@ class DatWorkerPool::Runner
|
|
17
21
|
class InitTests < UnitTests
|
18
22
|
desc "when init"
|
19
23
|
setup do
|
20
|
-
|
21
|
-
@
|
22
|
-
@
|
23
|
-
|
24
|
-
@
|
25
|
-
|
24
|
+
@logger = TEST_LOGGER || Logger.new("/dev/null")
|
25
|
+
@queue = DatWorkerPool::DefaultQueue.new
|
26
|
+
@worker_class = TestWorker
|
27
|
+
|
28
|
+
@worker_params.merge!({
|
29
|
+
Factory.string => Factory.string
|
30
|
+
})
|
26
31
|
|
27
32
|
@workers = DatWorkerPool::LockedArray.new
|
28
33
|
Assert.stub(DatWorkerPool::LockedArray, :new){ @workers }
|
@@ -73,24 +78,6 @@ class DatWorkerPool::Runner
|
|
73
78
|
assert_equal @workers.values, subject.workers
|
74
79
|
end
|
75
80
|
|
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
81
|
should "allow making workers available/unavailable" do
|
95
82
|
worker = @worker_class.new(@runner, @queue, Factory.integer(10))
|
96
83
|
|
@@ -142,25 +129,45 @@ class DatWorkerPool::Runner
|
|
142
129
|
|
143
130
|
end
|
144
131
|
|
145
|
-
class
|
146
|
-
desc "and started
|
132
|
+
class StartTests < InitTests
|
133
|
+
desc "and started"
|
134
|
+
|
135
|
+
should "start its queue" do
|
136
|
+
assert_false @queue.running?
|
137
|
+
subject.start
|
138
|
+
assert_true @queue.running?
|
139
|
+
end
|
140
|
+
|
141
|
+
should "build and add workers" do
|
142
|
+
subject.start
|
143
|
+
wait_for_workers_to_become_available
|
144
|
+
|
145
|
+
assert_equal @num_workers, subject.workers.size
|
146
|
+
subject.workers.each_with_index do |worker, n|
|
147
|
+
assert_equal subject, worker.dwp_runner
|
148
|
+
assert_equal @queue, worker.dwp_queue
|
149
|
+
assert_equal n + 1, worker.dwp_number
|
150
|
+
assert_true worker.dwp_running?
|
151
|
+
end
|
152
|
+
end
|
147
153
|
|
148
154
|
end
|
149
155
|
|
150
|
-
class ShutdownTests <
|
156
|
+
class ShutdownTests < StartTests
|
157
|
+
desc "and shutdown"
|
151
158
|
setup do
|
152
159
|
@timeout_seconds = nil
|
153
160
|
@optional_timeout_called = false
|
154
161
|
# this acts as a spy but also keeps the shutdown from ever timing out
|
155
|
-
Assert.stub(
|
162
|
+
Assert.stub(MuchTimeout, :just_optional_timeout) do |secs, args|
|
156
163
|
@timeout_seconds = secs
|
157
164
|
@optional_timeout_called = true
|
158
|
-
|
165
|
+
args[:do].call
|
159
166
|
end
|
160
167
|
|
161
|
-
@options[:worker_class] = ShutdownSpyWorker
|
162
|
-
@runner = @runner_class.new(@options)
|
163
168
|
@runner.start
|
169
|
+
wait_for_workers_to_become_available
|
170
|
+
|
164
171
|
# we need a reference to the workers, the runners workers will get removed
|
165
172
|
# as they shutdown
|
166
173
|
@running_workers = @runner.workers.dup
|
@@ -182,7 +189,10 @@ class DatWorkerPool::Runner
|
|
182
189
|
@running_workers.each do |worker|
|
183
190
|
assert_false worker.dwp_shutdown?
|
184
191
|
end
|
192
|
+
|
185
193
|
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
194
|
+
wait_for_workers_to_become_unavailable
|
195
|
+
|
186
196
|
@running_workers.each do |worker|
|
187
197
|
assert_true worker.dwp_shutdown?
|
188
198
|
end
|
@@ -198,15 +208,21 @@ class DatWorkerPool::Runner
|
|
198
208
|
@running_workers.each do |worker|
|
199
209
|
assert_false worker.join_called
|
200
210
|
end
|
211
|
+
|
201
212
|
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
213
|
+
wait_for_workers_to_become_unavailable
|
214
|
+
|
202
215
|
@running_workers.each do |worker|
|
203
216
|
assert_true worker.join_called
|
204
217
|
end
|
205
218
|
end
|
206
219
|
|
207
220
|
should "join all workers even if one raises an error when joined" do
|
208
|
-
@running_workers.
|
221
|
+
@running_workers.sample.join_error = Factory.exception
|
222
|
+
|
209
223
|
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
224
|
+
wait_for_workers_to_become_unavailable
|
225
|
+
|
210
226
|
@running_workers.each do |worker|
|
211
227
|
assert_true worker.join_called
|
212
228
|
end
|
@@ -214,7 +230,10 @@ class DatWorkerPool::Runner
|
|
214
230
|
|
215
231
|
should "remove workers as they finish" do
|
216
232
|
assert_false subject.workers.empty?
|
233
|
+
|
217
234
|
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
235
|
+
wait_for_workers_to_become_unavailable
|
236
|
+
|
218
237
|
assert_true subject.workers.empty?
|
219
238
|
end
|
220
239
|
|
@@ -223,13 +242,21 @@ class DatWorkerPool::Runner
|
|
223
242
|
|
224
243
|
assert_false subject.workers.empty?
|
225
244
|
assert_false @available_workers_spy.empty?
|
245
|
+
|
226
246
|
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
247
|
+
wait_for_workers_to_become_unavailable
|
248
|
+
|
227
249
|
assert_true subject.workers.empty?
|
228
250
|
assert_true @available_workers_spy.empty?
|
229
251
|
end
|
230
252
|
|
231
253
|
should "force its workers to shutdown if a timeout error occurs" do
|
232
|
-
Assert.stub(
|
254
|
+
Assert.stub(MuchTimeout, :just_optional_timeout) do |secs, args|
|
255
|
+
args[:on_timeout].call # force an immediate timeout
|
256
|
+
end
|
257
|
+
|
258
|
+
@num_workers.times{ @queue.dwp_push(:hang) }
|
259
|
+
wait_for_workers_to_become_unavailable
|
233
260
|
subject.shutdown(Factory.integer)
|
234
261
|
|
235
262
|
@running_workers.each do |worker|
|
@@ -244,6 +271,9 @@ class DatWorkerPool::Runner
|
|
244
271
|
queue_exception = Factory.exception
|
245
272
|
Assert.stub(@queue, :dwp_shutdown){ raise queue_exception }
|
246
273
|
|
274
|
+
@num_workers.times{ @queue.dwp_push(:hang) }
|
275
|
+
wait_for_workers_to_become_unavailable
|
276
|
+
|
247
277
|
caught_exception = nil
|
248
278
|
begin
|
249
279
|
subject.shutdown(Factory.integer)
|
@@ -260,15 +290,24 @@ class DatWorkerPool::Runner
|
|
260
290
|
end
|
261
291
|
|
262
292
|
should "force shutdown all of its workers even if one raises an error when joining" do
|
263
|
-
Assert.stub(
|
264
|
-
|
265
|
-
|
293
|
+
Assert.stub(MuchTimeout, :just_optional_timeout) do |secs, args|
|
294
|
+
args[:on_timeout].call # force an immediate timeout
|
295
|
+
end
|
296
|
+
error_class = Factory.boolean ? DatWorkerPool::ShutdownError : RuntimeError
|
297
|
+
error_worker = @running_workers.sample
|
298
|
+
error_worker.join_error = Factory.exception(error_class)
|
299
|
+
|
300
|
+
@num_workers.times{ @queue.dwp_push(:hang) }
|
301
|
+
wait_for_workers_to_become_unavailable
|
266
302
|
subject.shutdown(Factory.boolean ? Factory.integer : nil)
|
267
303
|
|
268
|
-
@running_workers.each do |worker|
|
304
|
+
(@running_workers - [error_worker]).each do |worker|
|
269
305
|
assert_instance_of DatWorkerPool::ShutdownError, worker.raised_error
|
270
306
|
assert_true worker.join_called
|
271
307
|
end
|
308
|
+
assert_instance_of error_class, error_worker.raised_error
|
309
|
+
assert_true error_worker.join_called
|
310
|
+
|
272
311
|
assert_true subject.workers.empty?
|
273
312
|
assert_true @available_workers_spy.empty?
|
274
313
|
end
|
@@ -318,48 +357,46 @@ class DatWorkerPool::Runner
|
|
318
357
|
end
|
319
358
|
|
320
359
|
class TestWorker
|
321
|
-
include
|
360
|
+
include SignalTestWorker
|
361
|
+
|
362
|
+
attr_reader :join_called
|
363
|
+
attr_accessor :join_error, :raised_error
|
322
364
|
|
323
365
|
# for testing what is passed to the worker
|
324
366
|
attr_reader :dwp_runner, :dwp_queue
|
325
|
-
end
|
326
367
|
|
327
|
-
|
368
|
+
on_available{ signal_test_suite_thread }
|
369
|
+
on_unavailable{ signal_test_suite_thread }
|
328
370
|
|
329
|
-
|
330
|
-
attr_reader :join_called
|
331
|
-
attr_accessor :join_error, :raised_error
|
371
|
+
on_shutdown{ raise self.join_error if self.join_error }
|
332
372
|
|
333
|
-
|
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 }
|
373
|
+
on_error{ |e, wi| self.raised_error = e }
|
338
374
|
|
339
375
|
def initialize(*args)
|
340
376
|
super
|
341
|
-
@
|
342
|
-
@
|
343
|
-
@join_called = false
|
344
|
-
@join_error = nil
|
345
|
-
@raised_error = nil
|
346
|
-
end
|
377
|
+
@join_called = false
|
378
|
+
@join_error = nil
|
347
379
|
|
348
|
-
|
349
|
-
@
|
350
|
-
raise @join_error if @join_error
|
351
|
-
@mutex.synchronize{ @cond_var.broadcast }
|
380
|
+
@hang_mutex = Mutex.new
|
381
|
+
@hang_cond_var = ConditionVariable.new
|
352
382
|
end
|
353
383
|
|
354
|
-
def
|
355
|
-
|
384
|
+
def work!(work_item)
|
385
|
+
case(work_item)
|
386
|
+
when :hang
|
387
|
+
MuchTimeout.timeout(1) do
|
388
|
+
@hang_mutex.synchronize{ @hang_cond_var.wait(@hang_mutex) }
|
389
|
+
end
|
390
|
+
end
|
356
391
|
end
|
357
392
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
@mutex.synchronize{ @cond_var.wait(@mutex) }
|
393
|
+
def dwp_join(*args)
|
394
|
+
@join_called = true
|
395
|
+
super
|
362
396
|
end
|
397
|
+
|
363
398
|
end
|
364
399
|
|
400
|
+
FakeWorker = Struct.new(:dwp_number)
|
401
|
+
|
365
402
|
end
|
data/test/unit/worker_tests.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'assert'
|
2
2
|
require 'dat-worker-pool/worker'
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'much-timeout'
|
5
5
|
require 'dat-worker-pool/default_queue'
|
6
6
|
require 'dat-worker-pool/runner'
|
7
7
|
require 'test/support/thread_spies'
|
@@ -125,9 +125,18 @@ module DatWorkerPool::Worker
|
|
125
125
|
})
|
126
126
|
@number = Factory.integer(10)
|
127
127
|
|
128
|
-
|
128
|
+
# we only want to stub the first `Thread.new` call, not all calls;
|
129
|
+
# stubbing all threads can generate unexpected behavior and specifically
|
130
|
+
# causes `MuchTimeout` to not work correctly
|
131
|
+
@worker_thread_built = false
|
132
|
+
@thread_spy = ThreadSpy.new
|
129
133
|
Assert.stub(Thread, :new) do |&block|
|
130
|
-
|
134
|
+
if !@worker_thread_built
|
135
|
+
@worker_thread_built = true
|
136
|
+
@thread_spy.tap{ |s| s.block = block }
|
137
|
+
else
|
138
|
+
Assert.stub_send(Thread, :new, &block)
|
139
|
+
end
|
131
140
|
end
|
132
141
|
|
133
142
|
@available_worker = nil
|
@@ -245,7 +254,7 @@ module DatWorkerPool::Worker
|
|
245
254
|
|
246
255
|
should "make itself available/unavailable and run callbacks if it errors" do
|
247
256
|
# these are the only errors that could interfere with it
|
248
|
-
error_method = [:on_unavailable_error, :work_error].
|
257
|
+
error_method = [:on_unavailable_error, :work_error].sample
|
249
258
|
exception = Factory.exception
|
250
259
|
subject.send("#{error_method}=", exception)
|
251
260
|
|
@@ -295,7 +304,7 @@ module DatWorkerPool::Worker
|
|
295
304
|
|
296
305
|
should "run its on-error callbacks if it errors while starting" do
|
297
306
|
exception = Factory.exception
|
298
|
-
error_method = [:on_start_error, :on_available_error].
|
307
|
+
error_method = [:on_start_error, :on_available_error].sample
|
299
308
|
subject.send("#{error_method}=", exception)
|
300
309
|
|
301
310
|
subject.dwp_start
|
@@ -307,7 +316,7 @@ module DatWorkerPool::Worker
|
|
307
316
|
|
308
317
|
should "stop its thread if it errors while starting" do
|
309
318
|
exception = Factory.exception
|
310
|
-
error_method = [:on_start_error, :on_available_error].
|
319
|
+
error_method = [:on_start_error, :on_available_error].sample
|
311
320
|
subject.send("#{error_method}=", exception)
|
312
321
|
|
313
322
|
subject.dwp_start
|
@@ -319,7 +328,7 @@ module DatWorkerPool::Worker
|
|
319
328
|
|
320
329
|
should "run its on-error callbacks if it errors while shutting down" do
|
321
330
|
exception = Factory.exception
|
322
|
-
error_method = [:on_shutdown_error, :on_unavailable_error].
|
331
|
+
error_method = [:on_shutdown_error, :on_unavailable_error].sample
|
323
332
|
subject.send("#{error_method}=", exception)
|
324
333
|
|
325
334
|
subject.dwp_start
|
@@ -401,7 +410,7 @@ module DatWorkerPool::Worker
|
|
401
410
|
subject.dwp_start
|
402
411
|
wait_for_worker_to_be_available
|
403
412
|
|
404
|
-
error_method = ERROR_METHODS.reject{ |e| e == :on_available_error }.
|
413
|
+
error_method = ERROR_METHODS.reject{ |e| e == :on_available_error }.sample
|
405
414
|
subject.send("#{error_method}=", exception)
|
406
415
|
work_item = Factory.string
|
407
416
|
@queue.dwp_push(work_item)
|
@@ -544,17 +553,14 @@ module DatWorkerPool::Worker
|
|
544
553
|
:after_work_error
|
545
554
|
].freeze
|
546
555
|
def setup_work_loop_to_raise_exception(exception)
|
547
|
-
error_method = ERROR_METHODS.
|
556
|
+
error_method = ERROR_METHODS.sample
|
548
557
|
@worker.send("#{error_method}=", exception)
|
549
558
|
error_method
|
550
559
|
end
|
551
560
|
|
552
|
-
# this could loop forever so ensure it doesn't by using a timeout
|
553
|
-
# timeout instead of system timer because system timer is paranoid about a
|
554
|
-
# deadlock even though its intended to prevent the deadlock because it times
|
555
|
-
# out the block
|
561
|
+
# this could loop forever so ensure it doesn't by using a timeout
|
556
562
|
def wait_for_worker(&block)
|
557
|
-
|
563
|
+
MuchTimeout.timeout(1) do
|
558
564
|
@mutex.synchronize{ @cond_var.wait(@mutex) } while !block.call
|
559
565
|
end
|
560
566
|
end
|
@@ -587,7 +593,7 @@ module DatWorkerPool::Worker
|
|
587
593
|
|
588
594
|
def wait_for_worker_thread_to_stop
|
589
595
|
return unless @worker.dwp_thread_alive?
|
590
|
-
|
596
|
+
MuchTimeout.timeout(1){ @worker.dwp_join }
|
591
597
|
end
|
592
598
|
|
593
599
|
# this is needed because errors will be re-raised when the thread is joined
|
metadata
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dat-worker-pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 6
|
9
|
-
- 0
|
10
|
-
version: 0.6.0
|
4
|
+
version: 0.6.1
|
11
5
|
platform: ruby
|
12
6
|
authors:
|
13
7
|
- Collin Redding
|
@@ -16,38 +10,28 @@ autorequire:
|
|
16
10
|
bindir: bin
|
17
11
|
cert_chain: []
|
18
12
|
|
19
|
-
date:
|
13
|
+
date: 2016-06-09 00:00:00 Z
|
20
14
|
dependencies:
|
21
15
|
- !ruby/object:Gem::Dependency
|
16
|
+
name: assert
|
17
|
+
prerelease: false
|
22
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
-
none: false
|
24
19
|
requirements:
|
25
20
|
- - ~>
|
26
21
|
- !ruby/object:Gem::Version
|
27
|
-
|
28
|
-
|
29
|
-
- 1
|
30
|
-
- 2
|
31
|
-
version: "1.2"
|
32
|
-
type: :runtime
|
33
|
-
name: SystemTimer
|
22
|
+
version: 2.16.1
|
23
|
+
type: :development
|
34
24
|
version_requirements: *id001
|
35
|
-
prerelease: false
|
36
25
|
- !ruby/object:Gem::Dependency
|
26
|
+
name: much-timeout
|
27
|
+
prerelease: false
|
37
28
|
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
-
none: false
|
39
29
|
requirements:
|
40
30
|
- - ~>
|
41
31
|
- !ruby/object:Gem::Version
|
42
|
-
|
43
|
-
|
44
|
-
- 2
|
45
|
-
- 15
|
46
|
-
version: "2.15"
|
47
|
-
type: :development
|
48
|
-
name: assert
|
32
|
+
version: 0.1.0
|
33
|
+
type: :runtime
|
49
34
|
version_requirements: *id002
|
50
|
-
prerelease: false
|
51
35
|
description: A simple thread pool for processing generic 'work'
|
52
36
|
email:
|
53
37
|
- collin.redding@me.com
|
@@ -61,9 +45,8 @@ extra_rdoc_files: []
|
|
61
45
|
files:
|
62
46
|
- .gitignore
|
63
47
|
- Gemfile
|
64
|
-
- LICENSE
|
48
|
+
- LICENSE
|
65
49
|
- README.md
|
66
|
-
- Rakefile
|
67
50
|
- bench/report.rb
|
68
51
|
- bench/report.txt
|
69
52
|
- dat-worker-pool.gemspec
|
@@ -78,6 +61,7 @@ files:
|
|
78
61
|
- log/.gitkeep
|
79
62
|
- test/helper.rb
|
80
63
|
- test/support/factory.rb
|
64
|
+
- test/support/signal_test_worker.rb
|
81
65
|
- test/support/thread_spies.rb
|
82
66
|
- test/system/dat-worker-pool_tests.rb
|
83
67
|
- test/unit/dat-worker-pool_tests.rb
|
@@ -91,39 +75,33 @@ files:
|
|
91
75
|
homepage: http://github.com/redding/dat-worker-pool
|
92
76
|
licenses:
|
93
77
|
- MIT
|
78
|
+
metadata: {}
|
79
|
+
|
94
80
|
post_install_message:
|
95
81
|
rdoc_options: []
|
96
82
|
|
97
83
|
require_paths:
|
98
84
|
- lib
|
99
85
|
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
-
none: false
|
101
86
|
requirements:
|
102
|
-
-
|
87
|
+
- &id003
|
88
|
+
- ">="
|
103
89
|
- !ruby/object:Gem::Version
|
104
|
-
hash: 3
|
105
|
-
segments:
|
106
|
-
- 0
|
107
90
|
version: "0"
|
108
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
-
none: false
|
110
92
|
requirements:
|
111
|
-
-
|
112
|
-
- !ruby/object:Gem::Version
|
113
|
-
hash: 3
|
114
|
-
segments:
|
115
|
-
- 0
|
116
|
-
version: "0"
|
93
|
+
- *id003
|
117
94
|
requirements: []
|
118
95
|
|
119
96
|
rubyforge_project:
|
120
|
-
rubygems_version:
|
97
|
+
rubygems_version: 2.6.4
|
121
98
|
signing_key:
|
122
|
-
specification_version:
|
99
|
+
specification_version: 4
|
123
100
|
summary: A simple thread pool for processing generic 'work'
|
124
101
|
test_files:
|
125
102
|
- test/helper.rb
|
126
103
|
- test/support/factory.rb
|
104
|
+
- test/support/signal_test_worker.rb
|
127
105
|
- test/support/thread_spies.rb
|
128
106
|
- test/system/dat-worker-pool_tests.rb
|
129
107
|
- test/unit/dat-worker-pool_tests.rb
|
data/Rakefile
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require "bundler/gem_tasks"
|