concurrent-ruby 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,405 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe Agent do
6
+
7
+ subject { Agent.new(0) }
8
+
9
+ let(:observer) do
10
+ Class.new do
11
+ attr_reader :value
12
+ define_method(:update) do |time, value|
13
+ @value = value
14
+ end
15
+ end.new
16
+ end
17
+
18
+ before(:each) do
19
+ $GLOBAL_THREAD_POOL = CachedThreadPool.new
20
+ end
21
+
22
+ context '#initialize' do
23
+
24
+ it 'sets the value to the given initial state' do
25
+ Agent.new(10).value.should eq 10
26
+ end
27
+
28
+ it 'sets the timeout to the given value' do
29
+ Agent.new(0, 5).timeout.should eq 5
30
+ end
31
+
32
+ it 'sets the timeout to the default when nil' do
33
+ Agent.new(0).timeout.should eq Agent::TIMEOUT
34
+ end
35
+
36
+ it 'sets the length to zero' do
37
+ Agent.new(10).length.should eq 0
38
+ end
39
+
40
+ it 'spawns the worker thread' do
41
+ $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
42
+ Agent.new(0)
43
+ end
44
+ end
45
+
46
+ context '#rescue' do
47
+
48
+ it 'returns self when a block is given' do
49
+ a1 = subject
50
+ a2 = a1.rescue{}
51
+ a1.object_id.should eq a2.object_id
52
+ end
53
+
54
+ it 'returns self when no block is given' do
55
+ a1 = subject
56
+ a2 = a1.rescue
57
+ a1.object_id.should eq a2.object_id
58
+ end
59
+
60
+ it 'accepts an exception class as the first parameter' do
61
+ lambda {
62
+ subject.rescue(StandardError){}
63
+ }.should_not raise_error
64
+ end
65
+
66
+ it 'ignores rescuers without a block' do
67
+ subject.rescue
68
+ subject.instance_variable_get(:@rescuers).should be_empty
69
+ end
70
+ end
71
+
72
+ context '#validate' do
73
+
74
+ it 'returns self when a block is given' do
75
+ a1 = subject
76
+ a2 = a1.validate{}
77
+ a1.object_id.should eq a2.object_id
78
+ end
79
+
80
+ it 'returns self when no block is given' do
81
+ a1 = subject
82
+ a2 = a1.validate
83
+ a1.object_id.should eq a2.object_id
84
+ end
85
+
86
+ it 'ignores validators without a block' do
87
+ subject.validate
88
+ subject.instance_variable_get(:@validator).should be_nil
89
+ end
90
+ end
91
+
92
+ context '#post' do
93
+
94
+ it 'adds the given block to the queue' do
95
+ before = subject.length
96
+ subject.post{ nil }
97
+ subject.post{ nil }
98
+ subject.length.should eq before+2
99
+ end
100
+
101
+ it 'does not add to the queue when no block is given' do
102
+ before = subject.length
103
+ subject.post
104
+ subject.post{ nil }
105
+ subject.length.should eq before+1
106
+ end
107
+ end
108
+
109
+ context '#length' do
110
+
111
+ it 'should be zero for a new agent' do
112
+ subject.length.should eq 0
113
+ end
114
+
115
+ it 'should increase by one for each #post' do
116
+ subject.post{ sleep }
117
+ subject.post{ sleep }
118
+ subject.post{ sleep }
119
+ subject.length.should eq 3
120
+ end
121
+
122
+ it 'should decrease by one each time a handler is run' do
123
+ subject.post{ nil }
124
+ subject.post{ sleep }
125
+ subject.post{ sleep }
126
+ sleep(0.1)
127
+ subject.length.should eq 1
128
+ end
129
+ end
130
+
131
+ context 'fulfillment' do
132
+
133
+ it 'process each block in the queue' do
134
+ @expected = []
135
+ subject.post{ @expected << 1 }
136
+ subject.post{ @expected << 2 }
137
+ subject.post{ @expected << 3 }
138
+ sleep(0.1)
139
+ @expected.should eq [1,2,3]
140
+ end
141
+
142
+ it 'passes the current value to the handler' do
143
+ @expected = nil
144
+ Agent.new(10).post{|i| @expected = i }
145
+ sleep(0.1)
146
+ @expected.should eq 10
147
+ end
148
+
149
+ it 'sets the value to the handler return value on success' do
150
+ subject.post{ 100 }
151
+ sleep(0.1)
152
+ subject.value.should eq 100
153
+ end
154
+
155
+ it 'rejects the handler after timeout reached' do
156
+ agent = Agent.new(0, 0.1)
157
+ agent.post{ sleep(1); 10 }
158
+ agent.value.should eq 0
159
+ end
160
+ end
161
+
162
+ context 'validation' do
163
+
164
+ it 'processes the validator when present' do
165
+ @expected = nil
166
+ subject.validate{ @expected = 10; true }
167
+ subject.post{ nil }
168
+ sleep(0.1)
169
+ @expected.should eq 10
170
+ end
171
+
172
+ it 'passes the new value to the validator' do
173
+ @expected = nil
174
+ subject.validate{|v| @expected = v; true }
175
+ subject.post{ 10 }
176
+ sleep(0.1)
177
+ @expected.should eq 10
178
+ end
179
+
180
+ it 'sets the new value when the validator returns true' do
181
+ agent = Agent.new(0).validate{ true }
182
+ agent.post{ 10 }
183
+ sleep(0.1)
184
+ agent.value.should eq 10
185
+ end
186
+
187
+ it 'does not change the value when the validator returns false' do
188
+ agent = Agent.new(0).validate{ false }
189
+ agent.post{ 10 }
190
+ sleep(0.1)
191
+ agent.value.should eq 0
192
+ end
193
+
194
+ it 'does not change the value when the validator raises an exception' do
195
+ agent = Agent.new(0).validate{ raise StandardError }
196
+ agent.post{ 10 }
197
+ sleep(0.1)
198
+ agent.value.should eq 0
199
+ end
200
+ end
201
+
202
+ context 'rejection' do
203
+
204
+ it 'calls the first exception block with a matching class' do
205
+ @expected = nil
206
+ subject.
207
+ rescue(StandardError){|ex| @expected = 1 }.
208
+ rescue(StandardError){|ex| @expected = 2 }.
209
+ rescue(StandardError){|ex| @expected = 3 }
210
+ subject.post{ raise StandardError }
211
+ sleep(0.1)
212
+ @expected.should eq 1
213
+ end
214
+
215
+ it 'matches all with a rescue with no class given' do
216
+ @expected = nil
217
+ subject.
218
+ rescue(LoadError){|ex| @expected = 1 }.
219
+ rescue{|ex| @expected = 2 }.
220
+ rescue(StandardError){|ex| @expected = 3 }
221
+ subject.post{ raise NoMethodError }
222
+ sleep(0.1)
223
+ @expected.should eq 2
224
+ end
225
+
226
+ it 'searches associated rescue handlers in order' do
227
+ @expected = nil
228
+ subject.
229
+ rescue(ArgumentError){|ex| @expected = 1 }.
230
+ rescue(LoadError){|ex| @expected = 2 }.
231
+ rescue(Exception){|ex| @expected = 3 }
232
+ subject.post{ raise ArgumentError }
233
+ sleep(0.1)
234
+ @expected.should eq 1
235
+
236
+ @expected = nil
237
+ subject.
238
+ rescue(ArgumentError){|ex| @expected = 1 }.
239
+ rescue(LoadError){|ex| @expected = 2 }.
240
+ rescue(Exception){|ex| @expected = 3 }
241
+ subject.post{ raise LoadError }
242
+ sleep(0.1)
243
+ @expected.should eq 2
244
+
245
+ @expected = nil
246
+ subject.
247
+ rescue(ArgumentError){|ex| @expected = 1 }.
248
+ rescue(LoadError){|ex| @expected = 2 }.
249
+ rescue(Exception){|ex| @expected = 3 }
250
+ subject.post{ raise StandardError }
251
+ sleep(0.1)
252
+ @expected.should eq 3
253
+ end
254
+
255
+ it 'passes the exception object to the matched block' do
256
+ @expected = nil
257
+ subject.
258
+ rescue(ArgumentError){|ex| @expected = ex }.
259
+ rescue(LoadError){|ex| @expected = ex }.
260
+ rescue(Exception){|ex| @expected = ex }
261
+ subject.post{ raise StandardError }
262
+ sleep(0.1)
263
+ @expected.should be_a(StandardError)
264
+ end
265
+
266
+ it 'ignores rescuers without a block' do
267
+ @expected = nil
268
+ subject.
269
+ rescue(StandardError).
270
+ rescue(StandardError){|ex| @expected = ex }.
271
+ rescue(Exception){|ex| @expected = ex }
272
+ subject.post{ raise StandardError }
273
+ sleep(0.1)
274
+ @expected.should be_a(StandardError)
275
+ end
276
+
277
+ it 'supresses the exception if no rescue matches' do
278
+ lambda {
279
+ subject.
280
+ rescue(ArgumentError){|ex| @expected = ex }.
281
+ rescue(StandardError){|ex| @expected = ex }.
282
+ rescue(Exception){|ex| @expected = ex }
283
+ subject.post{ raise StandardError }
284
+ sleep(0.1)
285
+ }.should_not raise_error
286
+ end
287
+
288
+ it 'supresses exceptions thrown from rescue handlers' do
289
+ lambda {
290
+ subject.rescue(Exception){ raise StandardError }
291
+ subject.post{ raise ArgumentError }
292
+ sleep(0.1)
293
+ }.should_not raise_error
294
+ end
295
+ end
296
+
297
+ context 'observation' do
298
+
299
+ it 'notifies all observers when the value changes' do
300
+ agent = Agent.new(0)
301
+ agent.add_observer(observer)
302
+ agent.post{ 10 }
303
+ sleep(0.1)
304
+ observer.value.should eq 10
305
+ end
306
+
307
+ it 'does not notify observers when validation fails' do
308
+ agent = Agent.new(0)
309
+ agent.validate{ false }
310
+ agent.add_observer(observer)
311
+ agent.post{ 10 }
312
+ sleep(0.1)
313
+ observer.value.should be_nil
314
+ end
315
+
316
+ it 'does not notify observers when the handler raises an exception' do
317
+ agent = Agent.new(0)
318
+ agent.add_observer(observer)
319
+ agent.post{ raise StandardError }
320
+ sleep(0.1)
321
+ observer.value.should be_nil
322
+ end
323
+ end
324
+
325
+ context 'aliases' do
326
+
327
+ it 'aliases #deref for #value' do
328
+ Agent.new(10).deref.should eq 10
329
+ end
330
+
331
+ it 'aliases #validates for :validate' do
332
+ @expected = nil
333
+ subject.validates{|v| @expected = v }
334
+ subject.post{ 10 }
335
+ sleep(0.1)
336
+ @expected.should eq 10
337
+ end
338
+
339
+ it 'aliases #validate_with for :validate' do
340
+ @expected = nil
341
+ subject.validate_with{|v| @expected = v }
342
+ subject.post{ 10 }
343
+ sleep(0.1)
344
+ @expected.should eq 10
345
+ end
346
+
347
+ it 'aliases #validates_with for :validate' do
348
+ @expected = nil
349
+ subject.validates_with{|v| @expected = v }
350
+ subject.post{ 10 }
351
+ sleep(0.1)
352
+ @expected.should eq 10
353
+ end
354
+
355
+ it 'aliases #catch for #rescue' do
356
+ @expected = nil
357
+ subject.catch{ @expected = true }
358
+ subject.post{ raise StandardError }
359
+ sleep(0.1)
360
+ @expected.should be_true
361
+ end
362
+
363
+ it 'aliases #on_error for #rescue' do
364
+ @expected = nil
365
+ subject.on_error{ @expected = true }
366
+ subject.post{ raise StandardError }
367
+ sleep(0.1)
368
+ @expected.should be_true
369
+ end
370
+
371
+ it 'aliases #add_watch for #add_observer' do
372
+ agent = Agent.new(0)
373
+ agent.add_watch(observer)
374
+ agent.post{ 10 }
375
+ sleep(0.1)
376
+ observer.value.should eq 10
377
+ end
378
+
379
+ it 'aliases #<< for Agent#post' do
380
+ subject << proc{ 100 }
381
+ sleep(0.1)
382
+ subject.value.should eq 100
383
+
384
+ subject << lambda{ 100 }
385
+ sleep(0.1)
386
+ subject.value.should eq 100
387
+ end
388
+
389
+ it 'aliases Kernel#agent for Agent.new' do
390
+ agent(10).should be_a(Agent)
391
+ end
392
+
393
+ it 'aliases Kernel#deref for #deref' do
394
+ deref(Agent.new(10)).should eq 10
395
+ deref(Agent.new(10), 10).should eq 10
396
+ end
397
+
398
+ it 'aliases Kernel:post for Agent#post' do
399
+ post(subject){ 100 }
400
+ sleep(0.1)
401
+ subject.value.should eq 100
402
+ end
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+ require_relative 'thread_pool_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe CachedThreadPool do
7
+
8
+ subject { CachedThreadPool.new }
9
+
10
+ it_should_behave_like 'Thread Pool'
11
+
12
+ context '#initialize' do
13
+ it 'aliases Concurrent#new_cached_thread_pool' do
14
+ pool = Concurrent.new_cached_thread_pool
15
+ pool.should be_a(CachedThreadPool)
16
+ pool.size.should eq 0
17
+ end
18
+ end
19
+
20
+ context '#kill' do
21
+
22
+ it 'kills all threads' do
23
+ Thread.should_receive(:kill).exactly(5).times
24
+ pool = CachedThreadPool.new
25
+ 5.times{ sleep(0.1); pool << proc{ sleep(1) } }
26
+ sleep(1)
27
+ pool.kill
28
+ sleep(0.1)
29
+ end
30
+ end
31
+
32
+ context '#size' do
33
+
34
+ it 'returns zero for a new thread pool' do
35
+ subject.size.should eq 0
36
+ end
37
+
38
+ it 'returns the size of the subject when running' do
39
+ 5.times{ sleep(0.1); subject << proc{ sleep(1) } }
40
+ subject.size.should eq 5
41
+ end
42
+
43
+ it 'returns zero once shut down' do
44
+ subject.shutdown
45
+ subject.size.should eq 0
46
+ end
47
+ end
48
+
49
+ context 'worker creation and caching' do
50
+
51
+ it 'creates new workers when there are none available' do
52
+ subject.size.should eq 0
53
+ 5.times{ sleep(0.1); subject << proc{ sleep(1000) } }
54
+ sleep(1)
55
+ subject.size.should eq 5
56
+ end
57
+
58
+ it 'uses existing idle threads' do
59
+ 5.times{ sleep(0.05); subject << proc{ sleep(0.5) } }
60
+ sleep(1)
61
+ 3.times{ sleep(0.1); subject << proc{ sleep(0.5) } }
62
+ subject.size.should eq 5
63
+ end
64
+ end
65
+
66
+ context 'garbage collection' do
67
+
68
+ subject{ CachedThreadPool.new(gc_interval: 1, thread_idleime: 1) }
69
+
70
+ it 'starts when the first thread is added to the pool' do
71
+ subject.should_receive(:collect_garbage)
72
+ subject << proc{ nil }
73
+ sleep(0.1)
74
+ end
75
+
76
+ it 'removes from pool any thread that has been idle too long' do
77
+ subject << proc{ nil }
78
+ subject.size.should eq 1
79
+ sleep(1.5)
80
+ subject.size.should eq 0
81
+ end
82
+
83
+ it 'removed from pool any dead thread' do
84
+ subject << proc{ raise StandardError }
85
+ subject.size.should eq 1
86
+ sleep(1.5)
87
+ subject.size.should eq 0
88
+ end
89
+
90
+ it 'resets the working count appropriately' do
91
+ subject << proc{ sleep(1000) }
92
+ sleep(0.1)
93
+ subject << proc{ raise StandardError }
94
+ sleep(0.1)
95
+ subject << proc{ nil }
96
+
97
+ sleep(0.1)
98
+ subject.working.should eq 2
99
+
100
+ sleep(1.5)
101
+ subject.working.should eq 1
102
+ end
103
+
104
+ it 'stops collection when the pool size becomes zero' do
105
+ 3.times{ sleep(0.1); subject << proc{ sleep(0.5) } }
106
+ subject.instance_variable_get(:@collector).status.should eq 'sleep'
107
+ sleep(1.5)
108
+ subject.instance_variable_get(:@collector).status.should be_false
109
+ end
110
+ end
111
+ end
112
+ end