dat-worker-pool 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/dat-worker-pool.gemspec +1 -1
- data/lib/dat-worker-pool.rb +45 -7
- data/lib/dat-worker-pool/queue.rb +17 -1
- data/lib/dat-worker-pool/version.rb +1 -1
- data/lib/dat-worker-pool/worker.rb +23 -9
- data/lib/dat-worker-pool/worker_pool_spy.rb +47 -4
- data/test/system/use_worker_pool_tests.rb +1 -0
- data/test/unit/dat-worker-pool_tests.rb +112 -2
- data/test/unit/queue_tests.rb +30 -1
- data/test/unit/worker_pool_spy_tests.rb +75 -5
- data/test/unit/worker_tests.rb +76 -35
- metadata +16 -15
data/dat-worker-pool.gemspec
CHANGED
data/lib/dat-worker-pool.rb
CHANGED
@@ -11,6 +11,10 @@ class DatWorkerPool
|
|
11
11
|
TimeoutError = Class.new(RuntimeError)
|
12
12
|
|
13
13
|
attr_reader :logger, :spawned
|
14
|
+
attr_reader :queue
|
15
|
+
attr_reader :on_worker_start_callbacks, :on_worker_shutdown_callbacks
|
16
|
+
attr_reader :on_worker_sleep_callbacks, :on_worker_wakeup_callbacks
|
17
|
+
attr_reader :before_work_callbacks, :after_work_callbacks
|
14
18
|
|
15
19
|
def initialize(min = 0, max = 1, debug = false, &do_work_proc)
|
16
20
|
@min_workers = min
|
@@ -26,7 +30,14 @@ class DatWorkerPool
|
|
26
30
|
@workers = []
|
27
31
|
@spawned = 0
|
28
32
|
|
29
|
-
@
|
33
|
+
@on_worker_start_callbacks = []
|
34
|
+
@on_worker_shutdown_callbacks = [proc{ |worker| despawn_worker(worker) }]
|
35
|
+
@on_worker_sleep_callbacks = [proc{ @workers_waiting.increment }]
|
36
|
+
@on_worker_wakeup_callbacks = [proc{ @workers_waiting.decrement }]
|
37
|
+
@before_work_callbacks = []
|
38
|
+
@after_work_callbacks = []
|
39
|
+
|
40
|
+
@started = false
|
30
41
|
end
|
31
42
|
|
32
43
|
def work_items
|
@@ -62,7 +73,13 @@ class DatWorkerPool
|
|
62
73
|
return if work_item.nil?
|
63
74
|
new_worker_needed = all_spawned_workers_are_busy?
|
64
75
|
@queue.push work_item
|
65
|
-
self.spawn_worker if new_worker_needed && !reached_max_workers?
|
76
|
+
self.spawn_worker if @started && new_worker_needed && !reached_max_workers?
|
77
|
+
end
|
78
|
+
|
79
|
+
def start
|
80
|
+
@started = true
|
81
|
+
@queue.start
|
82
|
+
@min_workers.times{ self.spawn_worker }
|
66
83
|
end
|
67
84
|
|
68
85
|
# Shutdown each worker and then the queue. Shutting down the queue will
|
@@ -72,8 +89,13 @@ class DatWorkerPool
|
|
72
89
|
# **NOTE** Any work that is left on the queue isn't processed. The controlling
|
73
90
|
# application for the worker pool should gracefully handle these items.
|
74
91
|
def shutdown(timeout = nil)
|
92
|
+
@started = false
|
75
93
|
begin
|
76
94
|
proc = OptionalTimeoutProc.new(timeout, true) do
|
95
|
+
# Workers need to be shutdown before the queue. This marks a flag that
|
96
|
+
# tells the workers to exit out of their loop once they wakeup. The
|
97
|
+
# queue shutdown signals the workers to wakeup, so the flag needs to be
|
98
|
+
# set before they wakeup.
|
77
99
|
@workers.each(&:shutdown)
|
78
100
|
@queue.shutdown
|
79
101
|
|
@@ -91,15 +113,32 @@ class DatWorkerPool
|
|
91
113
|
end
|
92
114
|
end
|
93
115
|
|
116
|
+
def on_queue_pop_callbacks; @queue.on_pop_callbacks; end
|
117
|
+
def on_queue_push_callbacks; @queue.on_push_callbacks; end
|
118
|
+
|
119
|
+
def on_queue_pop(&block); @queue.on_pop_callbacks << block; end
|
120
|
+
def on_queue_push(&block); @queue.on_push_callbacks << block; end
|
121
|
+
|
122
|
+
def on_worker_start(&block); @on_worker_start_callbacks << block; end
|
123
|
+
def on_worker_shutdown(&block); @on_worker_shutdown_callbacks << block; end
|
124
|
+
def on_worker_sleep(&block); @on_worker_sleep_callbacks << block; end
|
125
|
+
def on_worker_wakeup(&block); @on_worker_wakeup_callbacks << block; end
|
126
|
+
|
127
|
+
def before_work(&block); @before_work_callbacks << block; end
|
128
|
+
def after_work(&block); @after_work_callbacks << block; end
|
129
|
+
|
94
130
|
protected
|
95
131
|
|
96
132
|
def spawn_worker
|
97
133
|
@mutex.synchronize do
|
98
134
|
Worker.new(@queue).tap do |w|
|
99
|
-
w.on_work
|
100
|
-
w.
|
101
|
-
w.
|
102
|
-
w.
|
135
|
+
w.on_work = proc{ |worker, work_item| do_work(work_item) }
|
136
|
+
w.on_start_callbacks = @on_worker_start_callbacks
|
137
|
+
w.on_shutdown_callbacks = @on_worker_shutdown_callbacks
|
138
|
+
w.on_sleep_callbacks = @on_worker_sleep_callbacks
|
139
|
+
w.on_wakeup_callbacks = @on_worker_wakeup_callbacks
|
140
|
+
w.before_work_callbacks = @before_work_callbacks
|
141
|
+
w.after_work_callbacks = @after_work_callbacks
|
103
142
|
|
104
143
|
@workers << w
|
105
144
|
@spawned += 1
|
@@ -139,7 +178,6 @@ class DatWorkerPool
|
|
139
178
|
def decrement
|
140
179
|
@mutex.synchronize{ @count -= 1 }
|
141
180
|
end
|
142
|
-
|
143
181
|
end
|
144
182
|
|
145
183
|
class OptionalTimeoutProc
|
@@ -4,11 +4,16 @@ class DatWorkerPool
|
|
4
4
|
|
5
5
|
class Queue
|
6
6
|
|
7
|
+
attr_accessor :on_push_callbacks, :on_pop_callbacks
|
8
|
+
|
7
9
|
def initialize
|
8
10
|
@work_items = []
|
9
11
|
@shutdown = false
|
10
12
|
@mutex = Mutex.new
|
11
13
|
@condition_variable = ConditionVariable.new
|
14
|
+
|
15
|
+
@on_pop_callbacks = []
|
16
|
+
@on_push_callbacks = []
|
12
17
|
end
|
13
18
|
|
14
19
|
def work_items
|
@@ -23,26 +28,37 @@ class DatWorkerPool
|
|
23
28
|
@work_items << work_item
|
24
29
|
@condition_variable.signal
|
25
30
|
end
|
31
|
+
@on_push_callbacks.each(&:call)
|
26
32
|
end
|
27
33
|
|
28
34
|
def pop
|
29
35
|
return if @shutdown
|
30
|
-
@mutex.synchronize do
|
36
|
+
item = @mutex.synchronize do
|
31
37
|
@condition_variable.wait(@mutex) while !@shutdown && @work_items.empty?
|
32
38
|
@work_items.shift
|
33
39
|
end
|
40
|
+
@on_pop_callbacks.each(&:call)
|
41
|
+
item
|
34
42
|
end
|
35
43
|
|
36
44
|
def empty?
|
37
45
|
@mutex.synchronize{ @work_items.empty? }
|
38
46
|
end
|
39
47
|
|
48
|
+
def start
|
49
|
+
@shutdown = false
|
50
|
+
end
|
51
|
+
|
40
52
|
# wake up any workers who are idle (because of `wait_for_work_item`)
|
41
53
|
def shutdown
|
42
54
|
@shutdown = true
|
43
55
|
@mutex.synchronize{ @condition_variable.broadcast }
|
44
56
|
end
|
45
57
|
|
58
|
+
def shutdown?
|
59
|
+
@shutdown
|
60
|
+
end
|
61
|
+
|
46
62
|
end
|
47
63
|
|
48
64
|
end
|
@@ -3,14 +3,21 @@ require 'thread'
|
|
3
3
|
class DatWorkerPool
|
4
4
|
|
5
5
|
class Worker
|
6
|
-
|
6
|
+
|
7
|
+
attr_accessor :on_work
|
8
|
+
attr_accessor :on_start_callbacks, :on_shutdown_callbacks
|
9
|
+
attr_accessor :on_sleep_callbacks, :on_wakeup_callbacks
|
10
|
+
attr_accessor :before_work_callbacks, :after_work_callbacks
|
7
11
|
|
8
12
|
def initialize(queue)
|
9
13
|
@queue = queue
|
10
|
-
@on_work
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
14
|
+
@on_work = proc{ |worker, work_item| }
|
15
|
+
@on_start_callbacks = []
|
16
|
+
@on_shutdown_callbacks = []
|
17
|
+
@on_sleep_callbacks = []
|
18
|
+
@on_wakeup_callbacks = []
|
19
|
+
@before_work_callbacks = []
|
20
|
+
@after_work_callbacks = []
|
14
21
|
|
15
22
|
@shutdown = false
|
16
23
|
@thread = nil
|
@@ -35,18 +42,25 @@ class DatWorkerPool
|
|
35
42
|
protected
|
36
43
|
|
37
44
|
def work_loop
|
45
|
+
@on_start_callbacks.each{ |p| p.call(self) }
|
38
46
|
loop do
|
39
|
-
@
|
47
|
+
@on_sleep_callbacks.each{ |p| p.call(self) }
|
40
48
|
work_item = @queue.pop
|
41
|
-
@
|
49
|
+
@on_wakeup_callbacks.each{ |p| p.call(self) }
|
42
50
|
break if @shutdown
|
43
|
-
|
51
|
+
do_work(work_item) if work_item
|
44
52
|
end
|
45
53
|
ensure
|
46
|
-
@
|
54
|
+
@on_shutdown_callbacks.each{ |p| p.call(self) }
|
47
55
|
@thread = nil
|
48
56
|
end
|
49
57
|
|
58
|
+
def do_work(work_item)
|
59
|
+
@before_work_callbacks.each{ |p| p.call(self, work_item) }
|
60
|
+
@on_work.call(self, work_item)
|
61
|
+
@after_work_callbacks.each{ |p| p.call(self, work_item) }
|
62
|
+
end
|
63
|
+
|
50
64
|
end
|
51
65
|
|
52
66
|
end
|
@@ -1,15 +1,36 @@
|
|
1
1
|
class DatWorkerPool
|
2
2
|
|
3
3
|
class WorkerPoolSpy
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :min_workers, :max_workers, :debug
|
5
|
+
attr_reader :work_proc, :work_items
|
6
|
+
attr_reader :start_called
|
5
7
|
attr_reader :shutdown_called, :shutdown_timeout
|
8
|
+
attr_reader :on_queue_pop_callbacks, :on_queue_push_callbacks
|
9
|
+
attr_reader :on_worker_start_callbacks, :on_worker_shutdown_callbacks
|
10
|
+
attr_reader :on_worker_sleep_callbacks, :on_worker_wakeup_callbacks
|
11
|
+
attr_reader :before_work_callbacks, :after_work_callbacks
|
6
12
|
attr_accessor :worker_available
|
7
13
|
|
8
|
-
def initialize
|
14
|
+
def initialize(min = 0, max = 1, debug = false, &block)
|
15
|
+
@min_workers = min
|
16
|
+
@max_workers = max
|
17
|
+
@debug = debug
|
18
|
+
@work_proc = block
|
19
|
+
|
9
20
|
@worker_available = false
|
10
21
|
@work_items = []
|
22
|
+
@start_called = false
|
11
23
|
@shutdown_called = false
|
12
24
|
@shutdown_timeout = nil
|
25
|
+
|
26
|
+
@on_queue_pop_callbacks = []
|
27
|
+
@on_queue_push_callbacks = []
|
28
|
+
@on_worker_start_callbacks = []
|
29
|
+
@on_worker_shutdown_callbacks = []
|
30
|
+
@on_worker_sleep_callbacks = []
|
31
|
+
@on_worker_wakeup_callbacks = []
|
32
|
+
@before_work_callbacks = []
|
33
|
+
@after_work_callbacks = []
|
13
34
|
end
|
14
35
|
|
15
36
|
def worker_available?
|
@@ -21,13 +42,35 @@ class DatWorkerPool
|
|
21
42
|
end
|
22
43
|
|
23
44
|
def add_work(work)
|
24
|
-
|
45
|
+
return unless work
|
46
|
+
@work_items << work
|
47
|
+
@on_queue_push_callbacks.each(&:call)
|
48
|
+
end
|
49
|
+
|
50
|
+
def pop_work
|
51
|
+
work = @work_items.shift
|
52
|
+
@on_queue_pop_callbacks.each(&:call)
|
53
|
+
work
|
25
54
|
end
|
26
55
|
|
27
|
-
def
|
56
|
+
def start
|
57
|
+
@start_called = true
|
58
|
+
end
|
59
|
+
|
60
|
+
def shutdown(timeout = nil)
|
28
61
|
@shutdown_called = true
|
29
62
|
@shutdown_timeout = timeout
|
30
63
|
end
|
64
|
+
|
65
|
+
def on_queue_pop(&block); @on_queue_pop_callbacks << block; end
|
66
|
+
def on_queue_push(&block); @on_queue_push_callbacks << block; end
|
67
|
+
def on_worker_start(&block); @on_worker_start_callbacks << block; end
|
68
|
+
def on_worker_shutdown(&block); @on_worker_shutdown_callbacks << block; end
|
69
|
+
def on_worker_sleep(&block); @on_worker_sleep_callbacks << block; end
|
70
|
+
def on_worker_wakeup(&block); @on_worker_wakeup_callbacks << block; end
|
71
|
+
def before_work(&block); @before_work_callbacks << block; end
|
72
|
+
def after_work(&block); @after_work_callbacks << block; end
|
73
|
+
|
31
74
|
end
|
32
75
|
|
33
76
|
end
|
@@ -10,6 +10,7 @@ class UseWorkerPoolTests < Assert::Context
|
|
10
10
|
@work_pool = DatWorkerPool.new(1, 2, !!ENV['DEBUG']) do |work|
|
11
11
|
@mutex.synchronize{ @results << (work * 100) }
|
12
12
|
end
|
13
|
+
@work_pool.start
|
13
14
|
end
|
14
15
|
|
15
16
|
should "be able to add work, have it processed and stop the pool" do
|
@@ -10,12 +10,47 @@ class DatWorkerPool
|
|
10
10
|
end
|
11
11
|
subject{ @work_pool }
|
12
12
|
|
13
|
-
should have_readers :logger, :spawned
|
14
|
-
should
|
13
|
+
should have_readers :logger, :spawned, :queue
|
14
|
+
should have_readers :on_worker_start_callbacks, :on_worker_shutdown_callbacks
|
15
|
+
should have_readers :on_worker_sleep_callbacks, :on_worker_wakeup_callbacks
|
16
|
+
should have_readers :before_work_callbacks, :after_work_callbacks
|
17
|
+
should have_imeths :add_work, :start, :shutdown, :despawn_worker
|
15
18
|
should have_imeths :work_items, :waiting
|
16
19
|
should have_imeths :worker_available?, :all_spawned_workers_are_busy?
|
17
20
|
should have_imeths :reached_max_workers?
|
18
21
|
should have_imeths :queue_empty?
|
22
|
+
should have_imeths :on_queue_pop_callbacks, :on_queue_push_callbacks
|
23
|
+
should have_imeths :on_queue_pop, :on_queue_push
|
24
|
+
should have_imeths :on_worker_start, :on_worker_shutdown
|
25
|
+
should have_imeths :on_worker_sleep, :on_worker_wakeup
|
26
|
+
should have_imeths :before_work, :after_work
|
27
|
+
|
28
|
+
should "know its attributes" do
|
29
|
+
assert_instance_of ::Logger, subject.logger
|
30
|
+
assert_equal 0, subject.spawned
|
31
|
+
assert_instance_of Queue, subject.queue
|
32
|
+
end
|
33
|
+
|
34
|
+
should "default its worker callbacks" do
|
35
|
+
assert_equal [], subject.on_worker_start_callbacks
|
36
|
+
assert_equal 1, subject.on_worker_shutdown_callbacks.size
|
37
|
+
assert_instance_of Proc, subject.on_worker_shutdown_callbacks.first
|
38
|
+
assert_equal 1, subject.on_worker_sleep_callbacks.size
|
39
|
+
assert_instance_of Proc, subject.on_worker_sleep_callbacks.first
|
40
|
+
assert_equal 1, subject.on_worker_wakeup_callbacks.size
|
41
|
+
assert_instance_of Proc, subject.on_worker_wakeup_callbacks.first
|
42
|
+
assert_equal [], subject.before_work_callbacks
|
43
|
+
assert_equal [], subject.after_work_callbacks
|
44
|
+
end
|
45
|
+
|
46
|
+
should "demeter its queue's callbacks" do
|
47
|
+
callback = proc{ }
|
48
|
+
subject.on_queue_pop(&callback)
|
49
|
+
assert_equal [callback], subject.on_queue_pop_callbacks
|
50
|
+
callback = proc{ }
|
51
|
+
subject.on_queue_push(&callback)
|
52
|
+
assert_equal [callback], subject.on_queue_push_callbacks
|
53
|
+
end
|
19
54
|
|
20
55
|
end
|
21
56
|
|
@@ -23,6 +58,7 @@ class DatWorkerPool
|
|
23
58
|
desc "workers"
|
24
59
|
setup do
|
25
60
|
@work_pool = DatWorkerPool.new(1, 2, true){ |work| sleep(work) }
|
61
|
+
@work_pool.start
|
26
62
|
end
|
27
63
|
|
28
64
|
should "be created as needed and only go up to the maximum number allowed" do
|
@@ -74,6 +110,51 @@ class DatWorkerPool
|
|
74
110
|
|
75
111
|
end
|
76
112
|
|
113
|
+
class WorkerCallbackTests < UnitTests
|
114
|
+
desc "worker callbacks"
|
115
|
+
setup do
|
116
|
+
@start_called = false
|
117
|
+
@shutdown_called = false
|
118
|
+
@sleep_called = false
|
119
|
+
@wakeup_called = false
|
120
|
+
@before_work_called = false
|
121
|
+
@after_work_called = false
|
122
|
+
|
123
|
+
@work_pool = DatWorkerPool.new(1){ |work| }.tap do |p|
|
124
|
+
p.on_worker_start{ @start_called = true }
|
125
|
+
p.on_worker_shutdown{ @shutdown_called = true }
|
126
|
+
p.on_worker_sleep{ @sleep_called = true }
|
127
|
+
p.on_worker_wakeup{ @wakeup_called = true }
|
128
|
+
p.before_work{ @before_work_called = true }
|
129
|
+
p.after_work{ @after_work_called = true }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
subject{ @work_pool }
|
133
|
+
|
134
|
+
should "call the worker callbacks as workers wait or wakeup" do
|
135
|
+
assert_false @start_called
|
136
|
+
assert_false @sleep_called
|
137
|
+
subject.start
|
138
|
+
assert_true @start_called
|
139
|
+
assert_true @sleep_called
|
140
|
+
assert_false @wakeup_called
|
141
|
+
assert_false @before_work_called
|
142
|
+
assert_false @after_work_called
|
143
|
+
@sleep_called = false
|
144
|
+
subject.add_work 'work'
|
145
|
+
assert_true @wakeup_called
|
146
|
+
assert_true @before_work_called
|
147
|
+
assert_true @after_work_called
|
148
|
+
assert_true @sleep_called
|
149
|
+
@wakeup_called = false
|
150
|
+
assert_false @shutdown_called
|
151
|
+
subject.shutdown
|
152
|
+
assert_true @wakeup_called
|
153
|
+
assert_true @shutdown_called
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
77
158
|
class AddWorkWithNoWorkersTests < UnitTests
|
78
159
|
setup do
|
79
160
|
@work_pool = DatWorkerPool.new(0, 0){ |work| }
|
@@ -92,6 +173,7 @@ class DatWorkerPool
|
|
92
173
|
setup do
|
93
174
|
@result = nil
|
94
175
|
@work_pool = DatWorkerPool.new(1){|work| @result = (2 / work) }
|
176
|
+
@work_pool.start
|
95
177
|
end
|
96
178
|
|
97
179
|
should "have added the work and processed it by calling the passed block" do
|
@@ -112,6 +194,33 @@ class DatWorkerPool
|
|
112
194
|
|
113
195
|
end
|
114
196
|
|
197
|
+
class StartTests < UnitTests
|
198
|
+
desc "start"
|
199
|
+
setup do
|
200
|
+
@work_pool = DatWorkerPool.new(1, 2){ |work| sleep(work) }
|
201
|
+
end
|
202
|
+
subject{ @work_pool }
|
203
|
+
|
204
|
+
should "start its queue" do
|
205
|
+
assert_false subject.queue.shutdown?
|
206
|
+
subject.start
|
207
|
+
assert_false subject.queue.shutdown?
|
208
|
+
subject.shutdown
|
209
|
+
assert_true subject.queue.shutdown?
|
210
|
+
subject.start
|
211
|
+
assert_false subject.queue.shutdown?
|
212
|
+
end
|
213
|
+
|
214
|
+
should "keep workers from being spawned until its called" do
|
215
|
+
assert_equal 0, subject.spawned
|
216
|
+
subject.add_work 1
|
217
|
+
assert_equal 0, subject.spawned
|
218
|
+
subject.start
|
219
|
+
assert_equal 1, subject.spawned
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
115
224
|
class ShutdownTests < UnitTests
|
116
225
|
desc "shutdown"
|
117
226
|
setup do
|
@@ -121,6 +230,7 @@ class DatWorkerPool
|
|
121
230
|
sleep 1
|
122
231
|
@mutex.synchronize{ @finished << work }
|
123
232
|
end
|
233
|
+
@work_pool.start
|
124
234
|
@work_pool.add_work 'a'
|
125
235
|
@work_pool.add_work 'b'
|
126
236
|
@work_pool.add_work 'c'
|
data/test/unit/queue_tests.rb
CHANGED
@@ -10,14 +10,27 @@ class DatWorkerPool::Queue
|
|
10
10
|
end
|
11
11
|
subject{ @queue }
|
12
12
|
|
13
|
+
should have_accessors :on_push_callbacks, :on_pop_callbacks
|
13
14
|
should have_imeths :work_items, :push, :pop, :empty?
|
14
|
-
should have_imeths :shutdown
|
15
|
+
should have_imeths :start, :shutdown, :shutdown?
|
16
|
+
|
17
|
+
should "default its callbacks" do
|
18
|
+
assert_equal [], subject.on_push_callbacks
|
19
|
+
assert_equal [], subject.on_pop_callbacks
|
20
|
+
end
|
15
21
|
|
16
22
|
should "allow pushing work items onto the queue with #push" do
|
17
23
|
subject.push 'work'
|
18
24
|
assert_equal [ 'work' ], subject.work_items
|
19
25
|
end
|
20
26
|
|
27
|
+
should "call its on push callback when work is pushed" do
|
28
|
+
on_push_called = false
|
29
|
+
subject.on_push_callbacks << proc{ on_push_called = true }
|
30
|
+
subject.push 'work'
|
31
|
+
assert_true on_push_called
|
32
|
+
end
|
33
|
+
|
21
34
|
should "raise an exception if trying to push work when shutdown" do
|
22
35
|
subject.shutdown
|
23
36
|
assert_raises(RuntimeError){ subject.push('work') }
|
@@ -34,6 +47,14 @@ class DatWorkerPool::Queue
|
|
34
47
|
assert_equal 0, subject.work_items.size
|
35
48
|
end
|
36
49
|
|
50
|
+
should "call its on pop callback when work is popped" do
|
51
|
+
subject.push 'work'
|
52
|
+
on_pop_called = false
|
53
|
+
subject.on_pop_callbacks << proc{ on_pop_called = true }
|
54
|
+
subject.pop
|
55
|
+
assert_true on_pop_called
|
56
|
+
end
|
57
|
+
|
37
58
|
should "return nothing with pop when the queue has been shutdown" do
|
38
59
|
subject.push 'work1'
|
39
60
|
subject.shutdown
|
@@ -48,6 +69,14 @@ class DatWorkerPool::Queue
|
|
48
69
|
assert subject.empty?
|
49
70
|
end
|
50
71
|
|
72
|
+
should "reset its shutdown flag when started" do
|
73
|
+
assert_false subject.shutdown?
|
74
|
+
subject.shutdown
|
75
|
+
assert_true subject.shutdown?
|
76
|
+
subject.start
|
77
|
+
assert_false subject.shutdown?
|
78
|
+
end
|
79
|
+
|
51
80
|
end
|
52
81
|
|
53
82
|
class SignallingTests < UnitTests
|
@@ -6,15 +6,28 @@ class DatWorkerPool::WorkerPoolSpy
|
|
6
6
|
class UnitTests < Assert::Context
|
7
7
|
desc "DatWorkerPool::WorkerPoolSpy"
|
8
8
|
setup do
|
9
|
-
@
|
9
|
+
@work_proc = proc{ 'work' }
|
10
|
+
@worker_pool_spy = DatWorkerPool::WorkerPoolSpy.new(&@work_proc)
|
10
11
|
end
|
11
12
|
subject{ @worker_pool_spy }
|
12
13
|
|
13
|
-
should have_readers :work_items
|
14
|
-
should have_readers :shutdown_called, :shutdown_timeout
|
14
|
+
should have_readers :work_proc, :work_items
|
15
|
+
should have_readers :start_called, :shutdown_called, :shutdown_timeout
|
16
|
+
should have_readers :on_queue_pop_callbacks, :on_queue_push_callbacks
|
17
|
+
should have_readers :on_worker_start_callbacks, :on_worker_shutdown_callbacks
|
18
|
+
should have_readers :on_worker_sleep_callbacks, :on_worker_wakeup_callbacks
|
19
|
+
should have_readers :before_work_callbacks, :after_work_callbacks
|
15
20
|
should have_accessors :worker_available
|
16
21
|
should have_imeths :worker_available?, :queue_empty?
|
17
|
-
should have_imeths :add_work, :shutdown
|
22
|
+
should have_imeths :add_work, :start, :shutdown
|
23
|
+
should have_imeths :on_queue_pop, :on_queue_push
|
24
|
+
should have_imeths :on_worker_start, :on_worker_shutdown
|
25
|
+
should have_imeths :on_worker_sleep, :on_worker_wakeup
|
26
|
+
should have_imeths :before_work, :after_work
|
27
|
+
|
28
|
+
should "know its work proc" do
|
29
|
+
assert_equal @work_proc, subject.work_proc
|
30
|
+
end
|
18
31
|
|
19
32
|
should "have nothing in it's work items by default" do
|
20
33
|
assert subject.work_items.empty?
|
@@ -25,6 +38,10 @@ class DatWorkerPool::WorkerPoolSpy
|
|
25
38
|
assert_not subject.worker_available?
|
26
39
|
end
|
27
40
|
|
41
|
+
should "return false for start called by default" do
|
42
|
+
assert_equal false, subject.start_called
|
43
|
+
end
|
44
|
+
|
28
45
|
should "return false for shutdown called by default" do
|
29
46
|
assert_equal false, subject.shutdown_called
|
30
47
|
end
|
@@ -56,12 +73,65 @@ class DatWorkerPool::WorkerPoolSpy
|
|
56
73
|
assert_equal false, subject.queue_empty?
|
57
74
|
end
|
58
75
|
|
76
|
+
should "know when it's been started" do
|
77
|
+
subject.start
|
78
|
+
assert_true subject.start_called
|
79
|
+
end
|
80
|
+
|
59
81
|
should "know when it's been shutdown and with what timeout" do
|
60
82
|
subject.shutdown(10)
|
61
|
-
|
83
|
+
assert_true subject.shutdown_called
|
62
84
|
assert_equal 10, subject.shutdown_timeout
|
63
85
|
end
|
64
86
|
|
87
|
+
should "allow calling shutdown with no timeout" do
|
88
|
+
subject.shutdown
|
89
|
+
assert_true subject.shutdown_called
|
90
|
+
assert_nil subject.shutdown_timeout
|
91
|
+
end
|
92
|
+
|
93
|
+
should "know its queue and worker callbacks" do
|
94
|
+
assert_equal [], subject.on_queue_pop_callbacks
|
95
|
+
callback = proc{ }
|
96
|
+
subject.on_queue_pop(&callback)
|
97
|
+
assert_equal [callback], subject.on_queue_pop_callbacks
|
98
|
+
|
99
|
+
assert_equal [], subject.on_queue_push_callbacks
|
100
|
+
callback = proc{ }
|
101
|
+
subject.on_queue_push(&callback)
|
102
|
+
assert_equal [callback], subject.on_queue_push_callbacks
|
103
|
+
|
104
|
+
assert_equal [], subject.on_worker_start_callbacks
|
105
|
+
callback = proc{ }
|
106
|
+
subject.on_worker_start(&callback)
|
107
|
+
assert_equal [callback], subject.on_worker_start_callbacks
|
108
|
+
|
109
|
+
assert_equal [], subject.on_worker_shutdown_callbacks
|
110
|
+
callback = proc{ }
|
111
|
+
subject.on_worker_shutdown(&callback)
|
112
|
+
assert_equal [callback], subject.on_worker_shutdown_callbacks
|
113
|
+
|
114
|
+
assert_equal [], subject.on_worker_sleep_callbacks
|
115
|
+
callback = proc{ }
|
116
|
+
subject.on_worker_sleep(&callback)
|
117
|
+
assert_equal [callback], subject.on_worker_sleep_callbacks
|
118
|
+
|
119
|
+
assert_equal [], subject.on_worker_wakeup_callbacks
|
120
|
+
callback = proc{ }
|
121
|
+
subject.on_worker_wakeup(&callback)
|
122
|
+
assert_equal [callback], subject.on_worker_wakeup_callbacks
|
123
|
+
|
124
|
+
assert_equal [], subject.before_work_callbacks
|
125
|
+
callback = proc{ }
|
126
|
+
subject.before_work(&callback)
|
127
|
+
assert_equal [callback], subject.before_work_callbacks
|
128
|
+
|
129
|
+
assert_equal [], subject.after_work_callbacks
|
130
|
+
callback = proc{ }
|
131
|
+
subject.after_work(&callback)
|
132
|
+
assert_equal [callback], subject.after_work_callbacks
|
133
|
+
end
|
134
|
+
|
65
135
|
end
|
66
136
|
|
67
137
|
end
|
data/test/unit/worker_tests.rb
CHANGED
@@ -12,7 +12,7 @@ class DatWorkerPool::Worker
|
|
12
12
|
@queue = DatWorkerPool::Queue.new
|
13
13
|
@work_done = []
|
14
14
|
@worker = DatWorkerPool::Worker.new(@queue).tap do |w|
|
15
|
-
w.on_work = proc{ |work| @work_done << work }
|
15
|
+
w.on_work = proc{ |worker, work| @work_done << work }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
teardown do
|
@@ -22,9 +22,22 @@ class DatWorkerPool::Worker
|
|
22
22
|
end
|
23
23
|
subject{ @worker }
|
24
24
|
|
25
|
-
should
|
25
|
+
should have_accessors :on_work
|
26
|
+
should have_accessors :on_start_callbacks, :on_shutdown_callbacks
|
27
|
+
should have_accessors :on_sleep_callbacks, :on_wakeup_callbacks
|
28
|
+
should have_accessors :before_work_callbacks, :after_work_callbacks
|
26
29
|
should have_imeths :start, :shutdown, :join, :running?
|
27
30
|
|
31
|
+
should "default its callbacks" do
|
32
|
+
worker = DatWorkerPool::Worker.new(@queue)
|
33
|
+
assert_equal [], worker.on_start_callbacks
|
34
|
+
assert_equal [], worker.on_shutdown_callbacks
|
35
|
+
assert_equal [], worker.on_sleep_callbacks
|
36
|
+
assert_equal [], worker.on_wakeup_callbacks
|
37
|
+
assert_equal [], worker.before_work_callbacks
|
38
|
+
assert_equal [], worker.after_work_callbacks
|
39
|
+
end
|
40
|
+
|
28
41
|
should "start a thread with it's work loop with #start" do
|
29
42
|
thread = nil
|
30
43
|
assert_nothing_raised{ thread = subject.start }
|
@@ -60,54 +73,82 @@ class DatWorkerPool::Worker
|
|
60
73
|
class CallbacksTests < UnitTests
|
61
74
|
desc "callbacks"
|
62
75
|
setup do
|
76
|
+
@call_counter = 0
|
77
|
+
@on_start_called_with = nil
|
78
|
+
@on_start_call_count = nil
|
79
|
+
@on_shutdown_called_with = nil
|
80
|
+
@on_shutdown_call_count = nil
|
81
|
+
@on_sleep_called_with = nil
|
82
|
+
@on_sleep_call_count = nil
|
83
|
+
@on_wakeup_called_with = nil
|
84
|
+
@on_wakeup_call_count = nil
|
85
|
+
@before_work_called_with = nil
|
86
|
+
@before_work_called_at = nil
|
87
|
+
@after_work_called_with = nil
|
88
|
+
@after_work_called_at = nil
|
63
89
|
@worker = DatWorkerPool::Worker.new(@queue).tap do |w|
|
64
|
-
w.
|
90
|
+
w.on_start_callbacks << proc do |*args|
|
91
|
+
@on_start_called_with = args
|
92
|
+
@on_start_called_at = (@call_counter += 1)
|
93
|
+
end
|
94
|
+
w.on_shutdown_callbacks << proc do |*args|
|
95
|
+
@on_shutdown_called_with = args
|
96
|
+
@on_shutdown_called_at = (@call_counter += 1)
|
97
|
+
end
|
98
|
+
w.on_sleep_callbacks << proc do |*args|
|
99
|
+
@on_sleep_called_with = args
|
100
|
+
@on_sleep_called_at = (@call_counter += 1)
|
101
|
+
end
|
102
|
+
w.on_wakeup_callbacks << proc do |*args|
|
103
|
+
@on_wakeup_called_with = args
|
104
|
+
@on_wakeup_called_at = (@call_counter += 1)
|
105
|
+
end
|
106
|
+
w.before_work_callbacks << proc do |*args|
|
107
|
+
@before_work_called_with = args
|
108
|
+
@before_work_called_at = (@call_counter += 1)
|
109
|
+
end
|
110
|
+
w.after_work_callbacks << proc do |*args|
|
111
|
+
@after_work_called_with = args
|
112
|
+
@after_work_called_at = (@call_counter += 1)
|
113
|
+
end
|
65
114
|
end
|
66
115
|
end
|
67
116
|
|
68
|
-
should "
|
69
|
-
"it's waiting on work from the queue" do
|
70
|
-
waiting, yielded_worker = nil, nil
|
71
|
-
subject.on_waiting = proc do |worker|
|
72
|
-
waiting = true
|
73
|
-
yielded_worker = worker
|
74
|
-
end
|
117
|
+
should "pass its self to its start, shutdown, sleep and wakupe callbacks" do
|
75
118
|
subject.start
|
76
|
-
|
119
|
+
@queue.push('work')
|
120
|
+
subject.shutdown
|
121
|
+
@queue.shutdown
|
77
122
|
|
78
|
-
assert_equal
|
79
|
-
assert_equal subject,
|
123
|
+
assert_equal [subject], @on_start_called_with
|
124
|
+
assert_equal [subject], @on_shutdown_called_with
|
125
|
+
assert_equal [subject], @on_sleep_called_with
|
126
|
+
assert_equal [subject], @on_wakeup_called_with
|
80
127
|
end
|
81
128
|
|
82
|
-
should "
|
83
|
-
"it's done waiting for work from the queue" do
|
84
|
-
waiting, yielded_worker = nil, nil
|
85
|
-
subject.on_continuing = proc do |worker|
|
86
|
-
waiting = false
|
87
|
-
yielded_worker = worker
|
88
|
-
end
|
129
|
+
should "pass its self and work to its beofre and after work callbacks" do
|
89
130
|
subject.start
|
90
|
-
@queue.push
|
91
|
-
subject.
|
131
|
+
@queue.push('work')
|
132
|
+
subject.shutdown
|
133
|
+
@queue.shutdown
|
92
134
|
|
93
|
-
assert_equal
|
94
|
-
assert_equal subject,
|
135
|
+
assert_equal [subject, 'work'], @before_work_called_with
|
136
|
+
assert_equal [subject, 'work'], @after_work_called_with
|
95
137
|
end
|
96
138
|
|
97
|
-
should "call
|
98
|
-
"it's shutdown" do
|
99
|
-
shutdown, yielded_worker = nil, nil
|
100
|
-
subject.on_shutdown = proc do |worker|
|
101
|
-
shutdown = true
|
102
|
-
yielded_worker = worker
|
103
|
-
end
|
139
|
+
should "call its callbacks throughout its lifecycle" do
|
104
140
|
subject.start
|
141
|
+
assert_equal 1, @on_start_called_at
|
142
|
+
assert_equal 2, @on_sleep_called_at
|
143
|
+
@queue.push('work')
|
144
|
+
assert_equal 3, @on_wakeup_called_at
|
145
|
+
assert_equal 4, @before_work_called_at
|
146
|
+
assert_equal 5, @after_work_called_at
|
147
|
+
assert_equal 6, @on_sleep_called_at
|
105
148
|
subject.shutdown
|
106
149
|
@queue.shutdown
|
107
|
-
|
108
|
-
|
109
|
-
assert_equal true, shutdown
|
110
|
-
assert_equal subject, yielded_worker
|
150
|
+
assert_equal 7, @on_wakeup_called_at
|
151
|
+
assert_equal 8, @on_shutdown_called_at
|
111
152
|
end
|
112
153
|
|
113
154
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dat-worker-pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Collin Redding
|
@@ -16,10 +16,10 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date:
|
19
|
+
date: 2015-01-02 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
23
|
none: false
|
24
24
|
requirements:
|
25
25
|
- - ~>
|
@@ -29,24 +29,25 @@ dependencies:
|
|
29
29
|
- 1
|
30
30
|
- 2
|
31
31
|
version: "1.2"
|
32
|
+
version_requirements: *id001
|
32
33
|
type: :runtime
|
33
|
-
requirement: *id001
|
34
|
-
prerelease: false
|
35
34
|
name: SystemTimer
|
35
|
+
prerelease: false
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
38
|
none: false
|
39
39
|
requirements:
|
40
|
-
- -
|
40
|
+
- - ~>
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
hash:
|
42
|
+
hash: 27
|
43
43
|
segments:
|
44
|
-
-
|
45
|
-
|
44
|
+
- 2
|
45
|
+
- 12
|
46
|
+
version: "2.12"
|
47
|
+
version_requirements: *id002
|
46
48
|
type: :development
|
47
|
-
requirement: *id002
|
48
|
-
prerelease: false
|
49
49
|
name: assert
|
50
|
+
prerelease: false
|
50
51
|
description: A simple thread pool for processing generic 'work'
|
51
52
|
email:
|
52
53
|
- collin.redding@me.com
|
@@ -106,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
107
|
requirements: []
|
107
108
|
|
108
109
|
rubyforge_project:
|
109
|
-
rubygems_version: 1.8.
|
110
|
+
rubygems_version: 1.8.29
|
110
111
|
signing_key:
|
111
112
|
specification_version: 3
|
112
113
|
summary: A simple thread pool for processing generic 'work'
|