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.
@@ -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