contender 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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