concurrent-ruby 0.0.1 → 0.1.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,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