contender 0.1.1 → 0.2.0
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.
- data/lib/contender.rb +45 -1
- data/lib/contender/copy_on_write_array.rb +19 -0
- data/lib/contender/copy_on_write_set.rb +17 -0
- data/lib/contender/copy_on_write_structure.rb +91 -0
- data/lib/contender/countdown_latch.rb +7 -10
- data/lib/contender/counter.rb +30 -0
- data/lib/contender/direct_executor.rb +5 -2
- data/lib/contender/errors.rb +23 -4
- data/lib/contender/executor.rb +5 -4
- data/lib/contender/executor_service.rb +94 -0
- data/lib/contender/future.rb +37 -0
- data/lib/contender/future_task.rb +99 -0
- data/lib/contender/linked_queue.rb +338 -0
- data/lib/contender/pool.rb +3 -3
- data/lib/contender/pool/pool_executor.rb +697 -0
- data/lib/contender/pool/pool_worker.rb +19 -60
- data/lib/contender/pool/rejection_policy.rb +61 -0
- data/lib/contender/queue.rb +87 -0
- data/lib/contender/thread_factory.rb +37 -0
- data/lib/contender/version.rb +1 -1
- data/spec/copy_on_write_array_spec.rb +20 -0
- data/spec/copy_on_write_set_spec.rb +25 -0
- data/spec/countdown_latch_spec.rb +136 -0
- data/spec/counter_spec.rb +26 -0
- data/spec/direct_executor_spec.rb +21 -0
- data/spec/future_task_spec.rb +170 -0
- data/spec/linked_queue_spec.rb +25 -0
- data/spec/pool/executor_spec.rb +436 -0
- data/spec/pool/executor_stress_spec.rb +31 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/wait_helper.rb +14 -0
- metadata +149 -22
- data/lib/contender/pool/task.rb +0 -52
- data/lib/contender/pool/task_queue.rb +0 -80
- data/lib/contender/pool/thread_pool_executor.rb +0 -167
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Contender
|
4
|
+
|
5
|
+
describe Counter do
|
6
|
+
subject do
|
7
|
+
Counter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'increments the value' do
|
11
|
+
subject.increment
|
12
|
+
subject.increment
|
13
|
+
|
14
|
+
subject.value.should == 2
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'decrements the value' do
|
18
|
+
subject.increment
|
19
|
+
subject.increment
|
20
|
+
subject.decrement
|
21
|
+
|
22
|
+
subject.value.should == 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Contender
|
4
|
+
|
5
|
+
describe DirectExecutor do
|
6
|
+
subject do
|
7
|
+
DirectExecutor.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'executes blocks in the calling thread' do
|
11
|
+
calling = Thread.current
|
12
|
+
|
13
|
+
block = lambda do
|
14
|
+
calling.should be(Thread.current)
|
15
|
+
end
|
16
|
+
|
17
|
+
subject.execute &block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Contender
|
4
|
+
|
5
|
+
describe FutureTask do
|
6
|
+
it 'defers the result of executing a task' do
|
7
|
+
result = Object.new
|
8
|
+
block = proc do
|
9
|
+
result
|
10
|
+
end
|
11
|
+
|
12
|
+
task = FutureTask.new block
|
13
|
+
task.call
|
14
|
+
|
15
|
+
task.result.should be(result)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'defers the failure of executing a task' do
|
19
|
+
block = proc do
|
20
|
+
raise ArgumentError
|
21
|
+
end
|
22
|
+
|
23
|
+
task = FutureTask.new block
|
24
|
+
task.call
|
25
|
+
|
26
|
+
expect {
|
27
|
+
task.result
|
28
|
+
}.to raise_error(ExecutionError)
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when waiting on the result of the task' do
|
32
|
+
it 'raises an exception if the task is not done before the timeout is reached' do
|
33
|
+
block = Object.new
|
34
|
+
task = FutureTask.new block
|
35
|
+
|
36
|
+
expect {
|
37
|
+
task.result 0
|
38
|
+
}.to raise_error(TimeoutError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises an exception if the task was cancelled while waiting' do
|
42
|
+
start_latch = CountdownLatch.new 1
|
43
|
+
cancel_latch = CountdownLatch.new 1
|
44
|
+
|
45
|
+
block = Object.new
|
46
|
+
task = FutureTask.new block
|
47
|
+
|
48
|
+
Thread.new do
|
49
|
+
start_latch.countdown
|
50
|
+
begin
|
51
|
+
task.result
|
52
|
+
rescue CancellationError
|
53
|
+
cancel_latch.countdown
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
start_latch.await
|
58
|
+
|
59
|
+
task.cancel false
|
60
|
+
|
61
|
+
cancel_latch.await
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns the result of the task after it has ran' do
|
65
|
+
start_latch = CountdownLatch.new 1
|
66
|
+
|
67
|
+
result = Object.new
|
68
|
+
block = proc do
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
task = FutureTask.new block
|
73
|
+
|
74
|
+
Thread.new do
|
75
|
+
start_latch.countdown
|
76
|
+
task.call
|
77
|
+
end
|
78
|
+
|
79
|
+
start_latch.await
|
80
|
+
task.result.should be(result)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when cancelling the task' do
|
85
|
+
it 'prevents a task from running if it has not already' do
|
86
|
+
block = Object.new
|
87
|
+
|
88
|
+
task = FutureTask.new block
|
89
|
+
task.cancel false
|
90
|
+
|
91
|
+
expect {
|
92
|
+
task.result
|
93
|
+
}.to raise_error(CancellationError)
|
94
|
+
|
95
|
+
task.cancelled?.should be_true
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'will not cancel a task if it has already finished running' do
|
99
|
+
result = Object.new
|
100
|
+
block = proc do
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
task = FutureTask.new block
|
105
|
+
task.call
|
106
|
+
|
107
|
+
task.cancel(false).should be_false
|
108
|
+
task.result.should be(result)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'will not interrupt a task if it is running but should_interrupt is false' do
|
112
|
+
result = Object.new
|
113
|
+
|
114
|
+
start_latch = CountdownLatch.new 1
|
115
|
+
work_latch = CountdownLatch.new 1
|
116
|
+
|
117
|
+
block = proc do
|
118
|
+
start_latch.countdown
|
119
|
+
work_latch.await
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
task = FutureTask.new block
|
124
|
+
|
125
|
+
thread = Thread.new do
|
126
|
+
task.call
|
127
|
+
end
|
128
|
+
|
129
|
+
start_latch.await
|
130
|
+
|
131
|
+
task.cancel(false).should be_false
|
132
|
+
|
133
|
+
work_latch.countdown
|
134
|
+
|
135
|
+
task.result.should be(result)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'will interrupt a task if it is running and should_interrupt is true' do
|
139
|
+
start_latch = CountdownLatch.new 1
|
140
|
+
interrupt_latch = CountdownLatch.new 1
|
141
|
+
|
142
|
+
block = proc do
|
143
|
+
start_latch.countdown
|
144
|
+
begin
|
145
|
+
sleep
|
146
|
+
rescue InterruptError
|
147
|
+
interrupt_latch.countdown
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
task = FutureTask.new block
|
152
|
+
|
153
|
+
Thread.new do
|
154
|
+
task.call
|
155
|
+
end
|
156
|
+
|
157
|
+
start_latch.await
|
158
|
+
|
159
|
+
task.cancel(true).should be_true
|
160
|
+
|
161
|
+
interrupt_latch.await
|
162
|
+
|
163
|
+
expect {
|
164
|
+
task.result
|
165
|
+
}.to raise_error(CancellationError)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Contender
|
4
|
+
describe LinkedQueue do
|
5
|
+
it 'works' do
|
6
|
+
q = LinkedQueue.new
|
7
|
+
x = 10_000
|
8
|
+
|
9
|
+
t1 = Thread.new do
|
10
|
+
x.times do
|
11
|
+
q.offer Object.new, 5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
t2 = Thread.new do
|
16
|
+
x.times do
|
17
|
+
q.poll 5
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
t1.join
|
22
|
+
t2.join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,436 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Contender
|
4
|
+
module Pool
|
5
|
+
|
6
|
+
describe PoolExecutor do
|
7
|
+
include WaitHelper
|
8
|
+
|
9
|
+
context 'task submission' do
|
10
|
+
it 'raises an exception when no task is given' do
|
11
|
+
executor = Contender.single_pool
|
12
|
+
expect {
|
13
|
+
executor.execute
|
14
|
+
}.to raise_error(ArgumentError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raises an exception when the pool is shutdown' do
|
18
|
+
executor = Contender.single_pool
|
19
|
+
executor.shutdown
|
20
|
+
|
21
|
+
expect {
|
22
|
+
executor.execute {}
|
23
|
+
}.to raise_error(TaskRejectionError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises an exception when the pool state changes while waiting to enqueue a task' do
|
27
|
+
executor = Contender.single_pool
|
28
|
+
executor.prestart
|
29
|
+
|
30
|
+
queue = executor.queue
|
31
|
+
|
32
|
+
task = Object.new
|
33
|
+
|
34
|
+
enqueue_latch = CountdownLatch.new 1
|
35
|
+
start_latch = CountdownLatch.new 1
|
36
|
+
failure_latch = CountdownLatch.new 1
|
37
|
+
|
38
|
+
mock(queue).offer(task) do
|
39
|
+
start_latch.countdown
|
40
|
+
enqueue_latch.await
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
mock(queue).delete(task) do
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
exception = nil
|
50
|
+
thread = Thread.new do
|
51
|
+
begin
|
52
|
+
executor.execute task
|
53
|
+
rescue
|
54
|
+
exception = $!
|
55
|
+
end
|
56
|
+
|
57
|
+
failure_latch.countdown
|
58
|
+
end
|
59
|
+
|
60
|
+
start_latch.await
|
61
|
+
|
62
|
+
executor.shutdown
|
63
|
+
|
64
|
+
enqueue_latch.countdown
|
65
|
+
failure_latch.await
|
66
|
+
|
67
|
+
exception.should be_a(TaskRejectionError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'adds core workers and gives them tasks' do
|
71
|
+
executor = Contender.single_pool
|
72
|
+
queue = executor.queue
|
73
|
+
|
74
|
+
mock.proxy(queue).offer(anything).once
|
75
|
+
|
76
|
+
x = 2
|
77
|
+
|
78
|
+
latch = CountdownLatch.new x
|
79
|
+
x.times do
|
80
|
+
executor.execute do
|
81
|
+
latch.countdown
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
latch.await
|
86
|
+
executor.shutdown
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'adds a worker when the core worker size is zero' do
|
90
|
+
executor = PoolExecutor.new 0, 1, 60, LinkedQueue.new, SimpleThreadFactory.new
|
91
|
+
|
92
|
+
x = 2
|
93
|
+
|
94
|
+
latch = CountdownLatch.new x
|
95
|
+
x.times do
|
96
|
+
executor.execute do
|
97
|
+
latch.countdown
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
latch.await
|
102
|
+
executor.shutdown
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'creates additional workers when the queue is full for new tasks' do
|
106
|
+
executor = PoolExecutor.new 1, 2, 60, LinkedQueue.new, SimpleThreadFactory.new
|
107
|
+
executor.prestart
|
108
|
+
|
109
|
+
work_latch = CountdownLatch.new 1
|
110
|
+
task = proc do
|
111
|
+
work_latch.countdown
|
112
|
+
end
|
113
|
+
|
114
|
+
# Simulate the work queue being full
|
115
|
+
queue = executor.queue
|
116
|
+
stub(queue).offer(task) do
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
executor.execute task
|
121
|
+
|
122
|
+
work_latch.await
|
123
|
+
|
124
|
+
executor.current_size.should == 2
|
125
|
+
executor.shutdown
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'worker management' do
|
130
|
+
it 'restarts workers when a task raises an exception' do
|
131
|
+
executor = Contender.fixed_pool 1
|
132
|
+
|
133
|
+
latch = CountdownLatch.new 1
|
134
|
+
executor.execute do
|
135
|
+
latch.countdown
|
136
|
+
raise RuntimeError
|
137
|
+
end
|
138
|
+
|
139
|
+
latch.await
|
140
|
+
|
141
|
+
executor.current_size.should == 1
|
142
|
+
executor.shutdown
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'removes extra worker threads if they timeout before work arrives' do
|
146
|
+
executor = Contender.fixed_pool 2, true
|
147
|
+
# The pool scales up to a single thread, but is removed after work is performed
|
148
|
+
executor.work_timeout = 0.1
|
149
|
+
|
150
|
+
latch = CountdownLatch.new 1
|
151
|
+
executor.execute do
|
152
|
+
latch.countdown
|
153
|
+
end
|
154
|
+
|
155
|
+
latch.await
|
156
|
+
|
157
|
+
wait_until do
|
158
|
+
executor.current_size == 0
|
159
|
+
end
|
160
|
+
|
161
|
+
executor.largest_size.should == 1
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'removes core worker threads if core timeout is allowed and they timeout before work arrives' do
|
165
|
+
executor = Contender.single_pool
|
166
|
+
# The pool has a single core worker, but is removed unless work is present
|
167
|
+
executor.work_timeout = 0.1
|
168
|
+
executor.allow_core_timeout = true
|
169
|
+
|
170
|
+
executor.prestart.should be_true
|
171
|
+
|
172
|
+
wait_until do
|
173
|
+
executor.current_size == 0
|
174
|
+
end
|
175
|
+
|
176
|
+
executor.largest_size.should == 1
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'raises an exception if core timeout is enabled but work timeout is <= 0' do
|
180
|
+
executor = Contender.single_pool
|
181
|
+
|
182
|
+
expect {
|
183
|
+
executor.allow_core_timeout = true
|
184
|
+
}.to raise_error(ArgumentError)
|
185
|
+
|
186
|
+
executor.allow_core_timeout = false
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'raises an exception if work timeout is changed to a negative number' do
|
190
|
+
executor = Contender.single_pool
|
191
|
+
|
192
|
+
expect {
|
193
|
+
executor.work_timeout = -1
|
194
|
+
}.to raise_error(ArgumentError)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'raises an exception if core timeout is enabled and work timeout is changed to <= 0' do
|
198
|
+
executor = Contender.single_pool
|
199
|
+
|
200
|
+
executor.work_timeout = 1
|
201
|
+
executor.allow_core_timeout = true
|
202
|
+
|
203
|
+
expect {
|
204
|
+
executor.work_timeout = 0
|
205
|
+
}.to raise_error(ArgumentError)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'replaces workers when work timeout is changed' do
|
209
|
+
executor = Contender.fixed_pool 1, true
|
210
|
+
executor.prestart
|
211
|
+
|
212
|
+
executor.work_timeout = 30
|
213
|
+
|
214
|
+
wait_until do
|
215
|
+
executor.current_size == 1
|
216
|
+
end
|
217
|
+
|
218
|
+
executor.shutdown
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'does nothing when work timeout is increased' do
|
222
|
+
executor = Contender.fixed_pool 1, true
|
223
|
+
executor.prestart
|
224
|
+
|
225
|
+
executor.work_timeout = 120
|
226
|
+
|
227
|
+
executor.current_size.should == 1
|
228
|
+
|
229
|
+
executor.shutdown
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context 'pool size management' do
|
234
|
+
it 'does not create workers when increasing the core pool size when there is no backlog' do
|
235
|
+
executor = Contender.fixed_pool 2
|
236
|
+
|
237
|
+
executor.core_size.should == 2
|
238
|
+
executor.current_size.should == 0
|
239
|
+
|
240
|
+
executor.core_size = 4
|
241
|
+
|
242
|
+
executor.core_size.should == 4
|
243
|
+
executor.current_size.should == 0
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'increases the maximum size if it is less than the new core size' do
|
247
|
+
executor = Contender.fixed_pool 2
|
248
|
+
|
249
|
+
executor.core_size.should == 2
|
250
|
+
executor.maximum_size.should == 2
|
251
|
+
|
252
|
+
executor.core_size = 4
|
253
|
+
|
254
|
+
executor.core_size.should == 4
|
255
|
+
executor.maximum_size.should == 4
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'interrupts idle workers if the core pool size is decreased' do
|
259
|
+
executor = Contender.fixed_pool 2
|
260
|
+
executor.prestart
|
261
|
+
|
262
|
+
executor.core_size = 1
|
263
|
+
|
264
|
+
wait_until do
|
265
|
+
executor.current_size == 1
|
266
|
+
end
|
267
|
+
|
268
|
+
executor.shutdown
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'creates workers when increaing the core pool size when there is a backlog' do
|
272
|
+
executor = Contender.single_pool
|
273
|
+
|
274
|
+
block_latch = CountdownLatch.new 1
|
275
|
+
executor.execute do
|
276
|
+
block_latch.await
|
277
|
+
end
|
278
|
+
|
279
|
+
work_latch = CountdownLatch.new 1
|
280
|
+
executor.execute do
|
281
|
+
work_latch.countdown
|
282
|
+
end
|
283
|
+
|
284
|
+
executor.core_size = 3
|
285
|
+
|
286
|
+
work_latch.await
|
287
|
+
block_latch.countdown
|
288
|
+
|
289
|
+
executor.current_size.should == 2
|
290
|
+
executor.shutdown
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'raises an exception if core size size is set below 0' do
|
294
|
+
executor = Contender.single_pool
|
295
|
+
|
296
|
+
expect {
|
297
|
+
executor.core_size = -1
|
298
|
+
}.to raise_error(ArgumentError)
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'raises an exception if maximum size is less than 1' do
|
302
|
+
executor = Contender.single_pool
|
303
|
+
|
304
|
+
expect {
|
305
|
+
executor.maximum_size = 0
|
306
|
+
}.to raise_error(ArgumentError)
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'supports prestarting core workers' do
|
310
|
+
executor = Contender.fixed_pool 3
|
311
|
+
|
312
|
+
executor.current_size.should == 0
|
313
|
+
|
314
|
+
executor.prestart.should be_true
|
315
|
+
executor.current_size.should == 1
|
316
|
+
|
317
|
+
executor.prestart!.should == 2
|
318
|
+
executor.current_size.should == 3
|
319
|
+
|
320
|
+
executor.prestart.should be_false
|
321
|
+
executor.prestart!.should == 0
|
322
|
+
|
323
|
+
executor.current_size.should == 3
|
324
|
+
|
325
|
+
executor.shutdown
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context 'pool state management' do
|
330
|
+
it 'processes queued tasks after the pool has been shutdown' do
|
331
|
+
executor = Contender.single_pool
|
332
|
+
|
333
|
+
start_latch = CountdownLatch.new 1
|
334
|
+
block_latch = CountdownLatch.new 1
|
335
|
+
|
336
|
+
executor.execute do
|
337
|
+
start_latch.countdown
|
338
|
+
block_latch.await
|
339
|
+
end
|
340
|
+
|
341
|
+
work_latch = CountdownLatch.new 1
|
342
|
+
executor.execute do
|
343
|
+
work_latch.countdown
|
344
|
+
end
|
345
|
+
|
346
|
+
start_latch.await
|
347
|
+
|
348
|
+
executor.shutdown
|
349
|
+
|
350
|
+
executor.backlog.should == 1
|
351
|
+
executor.active_count.should == 1
|
352
|
+
|
353
|
+
block_latch.countdown
|
354
|
+
work_latch.await
|
355
|
+
end
|
356
|
+
|
357
|
+
it 'interrupts tasks when doing a forceful shutdown' do
|
358
|
+
executor = Contender.single_pool
|
359
|
+
|
360
|
+
start_latch = CountdownLatch.new 1
|
361
|
+
interrupt_latch = CountdownLatch.new 1
|
362
|
+
|
363
|
+
executor.execute do
|
364
|
+
start_latch.countdown
|
365
|
+
|
366
|
+
begin
|
367
|
+
sleep
|
368
|
+
rescue Interrupt
|
369
|
+
interrupt_latch.countdown
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
start_latch.await
|
374
|
+
executor.shutdown!
|
375
|
+
|
376
|
+
interrupt_latch.await
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'supports waiting until the pool is terminated' do
|
380
|
+
executor = Contender.single_pool
|
381
|
+
|
382
|
+
start_latch = CountdownLatch.new 1
|
383
|
+
block_latch = CountdownLatch.new 1
|
384
|
+
|
385
|
+
executor.execute do
|
386
|
+
start_latch.countdown
|
387
|
+
block_latch.await
|
388
|
+
end
|
389
|
+
|
390
|
+
start_latch.await
|
391
|
+
executor.shutdown
|
392
|
+
|
393
|
+
executor.await_termination(0).should be_false
|
394
|
+
|
395
|
+
terminate_latch = CountdownLatch.new 1
|
396
|
+
thread = Thread.new do
|
397
|
+
if executor.await_termination 5
|
398
|
+
terminate_latch.countdown
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
block_latch.countdown
|
403
|
+
terminate_latch.await
|
404
|
+
|
405
|
+
executor.await_termination(0).should be_true
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
context 'statistics' do
|
410
|
+
it 'tracks the number of completed tasks' do
|
411
|
+
executor = Contender.fixed_pool 2
|
412
|
+
executor.prestart
|
413
|
+
|
414
|
+
x = 10
|
415
|
+
latch = CountdownLatch.new x
|
416
|
+
|
417
|
+
x.times do
|
418
|
+
executor.execute do
|
419
|
+
latch.countdown
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
latch.await
|
424
|
+
|
425
|
+
executor.completed_tasks.should == x
|
426
|
+
|
427
|
+
executor.shutdown
|
428
|
+
executor.await_termination 5
|
429
|
+
|
430
|
+
executor.completed_tasks.should == x
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
end
|
436
|
+
end
|