concurrent-ruby 0.5.0 → 0.6.0.pre.1
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.
- checksums.yaml +4 -4
- data/README.md +88 -77
- data/lib/concurrent.rb +17 -2
- data/lib/concurrent/actor.rb +17 -0
- data/lib/concurrent/actor_context.rb +31 -0
- data/lib/concurrent/actor_ref.rb +39 -0
- data/lib/concurrent/agent.rb +12 -3
- data/lib/concurrent/async.rb +290 -0
- data/lib/concurrent/atomic.rb +5 -9
- data/lib/concurrent/cached_thread_pool.rb +39 -137
- data/lib/concurrent/channel/blocking_ring_buffer.rb +60 -0
- data/lib/concurrent/channel/buffered_channel.rb +83 -0
- data/lib/concurrent/channel/channel.rb +11 -0
- data/lib/concurrent/channel/probe.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +54 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
- data/lib/concurrent/channel/waitable_list.rb +38 -0
- data/lib/concurrent/configuration.rb +92 -0
- data/lib/concurrent/dataflow.rb +9 -3
- data/lib/concurrent/delay.rb +88 -0
- data/lib/concurrent/exchanger.rb +31 -0
- data/lib/concurrent/fixed_thread_pool.rb +28 -122
- data/lib/concurrent/future.rb +10 -5
- data/lib/concurrent/immediate_executor.rb +3 -2
- data/lib/concurrent/ivar.rb +2 -1
- data/lib/concurrent/java_cached_thread_pool.rb +45 -0
- data/lib/concurrent/java_fixed_thread_pool.rb +37 -0
- data/lib/concurrent/java_thread_pool_executor.rb +194 -0
- data/lib/concurrent/per_thread_executor.rb +23 -0
- data/lib/concurrent/postable.rb +2 -0
- data/lib/concurrent/processor_count.rb +125 -0
- data/lib/concurrent/promise.rb +42 -18
- data/lib/concurrent/ruby_cached_thread_pool.rb +37 -0
- data/lib/concurrent/ruby_fixed_thread_pool.rb +31 -0
- data/lib/concurrent/ruby_thread_pool_executor.rb +268 -0
- data/lib/concurrent/ruby_thread_pool_worker.rb +69 -0
- data/lib/concurrent/simple_actor_ref.rb +124 -0
- data/lib/concurrent/thread_local_var.rb +1 -1
- data/lib/concurrent/thread_pool_executor.rb +30 -0
- data/lib/concurrent/timer_task.rb +13 -10
- data/lib/concurrent/tvar.rb +212 -0
- data/lib/concurrent/utilities.rb +1 -0
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/actor_context_spec.rb +37 -0
- data/spec/concurrent/actor_ref_shared.rb +313 -0
- data/spec/concurrent/actor_spec.rb +9 -1
- data/spec/concurrent/agent_spec.rb +97 -96
- data/spec/concurrent/async_spec.rb +320 -0
- data/spec/concurrent/cached_thread_pool_shared.rb +137 -0
- data/spec/concurrent/channel/blocking_ring_buffer_spec.rb +149 -0
- data/spec/concurrent/channel/buffered_channel_spec.rb +151 -0
- data/spec/concurrent/channel/channel_spec.rb +37 -0
- data/spec/concurrent/channel/probe_spec.rb +49 -0
- data/spec/concurrent/channel/ring_buffer_spec.rb +126 -0
- data/spec/concurrent/channel/unbuffered_channel_spec.rb +132 -0
- data/spec/concurrent/configuration_spec.rb +134 -0
- data/spec/concurrent/dataflow_spec.rb +109 -27
- data/spec/concurrent/delay_spec.rb +77 -0
- data/spec/concurrent/exchanger_spec.rb +66 -0
- data/spec/concurrent/fixed_thread_pool_shared.rb +136 -0
- data/spec/concurrent/future_spec.rb +60 -51
- data/spec/concurrent/global_thread_pool_shared.rb +33 -0
- data/spec/concurrent/immediate_executor_spec.rb +4 -25
- data/spec/concurrent/ivar_spec.rb +36 -23
- data/spec/concurrent/java_cached_thread_pool_spec.rb +64 -0
- data/spec/concurrent/java_fixed_thread_pool_spec.rb +64 -0
- data/spec/concurrent/java_thread_pool_executor_spec.rb +71 -0
- data/spec/concurrent/obligation_shared.rb +32 -20
- data/spec/concurrent/{global_thread_pool_spec.rb → per_thread_executor_spec.rb} +9 -13
- data/spec/concurrent/processor_count_spec.rb +20 -0
- data/spec/concurrent/promise_spec.rb +29 -41
- data/spec/concurrent/ruby_cached_thread_pool_spec.rb +69 -0
- data/spec/concurrent/ruby_fixed_thread_pool_spec.rb +39 -0
- data/spec/concurrent/ruby_thread_pool_executor_spec.rb +183 -0
- data/spec/concurrent/simple_actor_ref_spec.rb +219 -0
- data/spec/concurrent/thread_pool_class_cast_spec.rb +40 -0
- data/spec/concurrent/thread_pool_executor_shared.rb +155 -0
- data/spec/concurrent/thread_pool_shared.rb +98 -36
- data/spec/concurrent/tvar_spec.rb +137 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/functions.rb +4 -0
- metadata +85 -20
- data/lib/concurrent/cached_thread_pool/worker.rb +0 -91
- data/lib/concurrent/channel.rb +0 -63
- data/lib/concurrent/fixed_thread_pool/worker.rb +0 -54
- data/lib/concurrent/global_thread_pool.rb +0 -42
- data/spec/concurrent/cached_thread_pool_spec.rb +0 -101
- data/spec/concurrent/channel_spec.rb +0 -86
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -92
- data/spec/concurrent/uses_global_thread_pool_shared.rb +0 -64
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe Async do
|
6
|
+
|
7
|
+
let(:executor) { PerThreadExecutor.new }
|
8
|
+
|
9
|
+
let(:async_class) do
|
10
|
+
Class.new do
|
11
|
+
include Concurrent::Async
|
12
|
+
attr_accessor :accessor
|
13
|
+
def echo(msg)
|
14
|
+
msg
|
15
|
+
end
|
16
|
+
def gather(first, second = nil)
|
17
|
+
return first, second
|
18
|
+
end
|
19
|
+
def boom(ex = StandardError.new)
|
20
|
+
raise ex
|
21
|
+
end
|
22
|
+
def wait(seconds)
|
23
|
+
sleep(seconds)
|
24
|
+
end
|
25
|
+
def with_block
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
subject do
|
32
|
+
obj = async_class.new
|
33
|
+
obj.executor = executor
|
34
|
+
obj
|
35
|
+
end
|
36
|
+
|
37
|
+
context '#validate_argc' do
|
38
|
+
|
39
|
+
subject do
|
40
|
+
Class.new {
|
41
|
+
def zero() nil; end
|
42
|
+
def three(a, b, c, &block) nil; end
|
43
|
+
def two_plus_two(a, b, c=nil, d=nil, &block) nil; end
|
44
|
+
def many(*args, &block) nil; end
|
45
|
+
}.new
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises an exception when the method is not defined' do
|
49
|
+
expect {
|
50
|
+
Async::validate_argc(subject, :bogus)
|
51
|
+
}.to raise_error(StandardError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'raises an exception for too many args on a zero arity method' do
|
55
|
+
expect {
|
56
|
+
Async::validate_argc(subject, :zero, 1, 2, 3)
|
57
|
+
}.to raise_error(ArgumentError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not raise an exception for correct zero arity' do
|
61
|
+
expect {
|
62
|
+
Async::validate_argc(subject, :zero)
|
63
|
+
}.not_to raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'raises an exception for too many args on a method with positive arity' do
|
67
|
+
expect {
|
68
|
+
Async::validate_argc(subject, :three, 1, 2, 3, 4)
|
69
|
+
}.to raise_error(ArgumentError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'raises an exception for too few args on a method with positive arity' do
|
73
|
+
expect {
|
74
|
+
Async::validate_argc(subject, :three, 1, 2)
|
75
|
+
}.to raise_error(ArgumentError)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'does not raise an exception for correct positive arity' do
|
79
|
+
expect {
|
80
|
+
Async::validate_argc(subject, :three, 1, 2, 3)
|
81
|
+
}.not_to raise_error
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'raises an exception for too few args on a method with negative arity' do
|
85
|
+
expect {
|
86
|
+
Async::validate_argc(subject, :two_plus_two, 1)
|
87
|
+
}.to raise_error(ArgumentError)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'does not raise an exception for correct negative arity' do
|
91
|
+
expect {
|
92
|
+
Async::validate_argc(subject, :two_plus_two, 1, 2)
|
93
|
+
Async::validate_argc(subject, :two_plus_two, 1, 2, 3, 4)
|
94
|
+
Async::validate_argc(subject, :two_plus_two, 1, 2, 3, 4, 5, 6)
|
95
|
+
|
96
|
+
Async::validate_argc(subject, :many)
|
97
|
+
Async::validate_argc(subject, :many, 1, 2)
|
98
|
+
Async::validate_argc(subject, :many, 1, 2, 3, 4)
|
99
|
+
}.not_to raise_error
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'executor' do
|
104
|
+
|
105
|
+
it 'returns the default executor when #executor= has never been called' do
|
106
|
+
Concurrent.configuration.should_receive(:global_task_pool).
|
107
|
+
and_return(ImmediateExecutor.new)
|
108
|
+
subject = async_class.new
|
109
|
+
subject.async.echo(:foo)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns the memo after #executor= has been called' do
|
113
|
+
executor = ImmediateExecutor.new
|
114
|
+
executor.should_receive(:post)
|
115
|
+
subject = async_class.new
|
116
|
+
subject.executor = executor
|
117
|
+
subject.async.echo(:foo)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'raises an exception if #executor= is called multiple times' do
|
121
|
+
executor = ImmediateExecutor.new
|
122
|
+
subject = async_class.new
|
123
|
+
subject.executor = executor
|
124
|
+
expect {
|
125
|
+
subject.executor = executor
|
126
|
+
}.to raise_error(ArgumentError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context '#async' do
|
131
|
+
|
132
|
+
it 'raises an error when calling a method that does not exist' do
|
133
|
+
expect {
|
134
|
+
subject.async.bogus
|
135
|
+
}.to raise_error(StandardError)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'raises an error when passing too few arguments' do
|
139
|
+
expect {
|
140
|
+
subject.async.gather
|
141
|
+
}.to raise_error(ArgumentError)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'raises an error when pasing too many arguments (arity >= 0)' do
|
145
|
+
expect {
|
146
|
+
subject.async.echo(1, 2, 3, 4, 5)
|
147
|
+
}.to raise_error(StandardError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'returns a :pending Future' do
|
151
|
+
val = subject.async.wait(5)
|
152
|
+
val.should be_a Concurrent::Future
|
153
|
+
val.should be_pending
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'runs the Future on the memoized executor' do
|
157
|
+
executor = ImmediateExecutor.new
|
158
|
+
executor.should_receive(:post).with(any_args)
|
159
|
+
subject = async_class.new
|
160
|
+
subject.executor = executor
|
161
|
+
subject.async.echo(:foo)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'sets the value on success' do
|
165
|
+
val = subject.async.echo(:foo)
|
166
|
+
val.value.should eq :foo
|
167
|
+
val.should be_fulfilled
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'sets the reason on failure' do
|
171
|
+
ex = ArgumentError.new
|
172
|
+
val = subject.async.boom(ex)
|
173
|
+
sleep(0.1)
|
174
|
+
val.reason.should eq ex
|
175
|
+
val.should be_rejected
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'sets the reason when giving too many optional arguments' do
|
179
|
+
val = subject.async.gather(1, 2, 3, 4, 5)
|
180
|
+
sleep(0.1)
|
181
|
+
val.reason.should be_a StandardError
|
182
|
+
val.should be_rejected
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'supports attribute accessors' do
|
186
|
+
subject.async.accessor = :foo
|
187
|
+
sleep(0.1)
|
188
|
+
val = subject.async.accessor
|
189
|
+
sleep(0.1)
|
190
|
+
val.value.should eq :foo
|
191
|
+
subject.accessor.should eq :foo
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'supports methods with blocks' do
|
195
|
+
val = subject.async.with_block{ :foo }
|
196
|
+
sleep(0.1)
|
197
|
+
val.value.should eq :foo
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'is aliased as #future' do
|
201
|
+
val = subject.future.wait(5)
|
202
|
+
val.should be_a Concurrent::Future
|
203
|
+
end
|
204
|
+
|
205
|
+
context '#method_missing' do
|
206
|
+
|
207
|
+
it 'defines the method after the first call' do
|
208
|
+
expect { subject.async.method(:echo) }.to raise_error(NameError)
|
209
|
+
subject.async.echo(:foo)
|
210
|
+
sleep(0.1)
|
211
|
+
expect { subject.async.method(:echo) }.not_to raise_error
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'does not define the method on name/arity exception' do
|
215
|
+
expect { subject.async.method(:bogus) }.to raise_error(NameError)
|
216
|
+
expect { subject.async.bogus }.to raise_error(NameError)
|
217
|
+
expect { subject.async.method(:bogus) }.to raise_error(NameError)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context '#await' do
|
223
|
+
|
224
|
+
it 'raises an error when calling a method that does not exist' do
|
225
|
+
expect {
|
226
|
+
subject.await.bogus
|
227
|
+
}.to raise_error(StandardError)
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'raises an error when passing too few arguments' do
|
231
|
+
expect {
|
232
|
+
subject.await.gather
|
233
|
+
}.to raise_error(ArgumentError)
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'raises an error when pasing too many arguments (arity >= 0)' do
|
237
|
+
expect {
|
238
|
+
subject.await.echo(1, 2, 3, 4, 5)
|
239
|
+
}.to raise_error(StandardError)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'returns a :fulfilled IVar' do
|
243
|
+
val = subject.await.echo(5)
|
244
|
+
val.should be_a Concurrent::IVar
|
245
|
+
val.should be_fulfilled
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'sets the value on success' do
|
249
|
+
val = subject.await.echo(:foo)
|
250
|
+
val.value.should eq :foo
|
251
|
+
val.should be_fulfilled
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'sets the reason on failure' do
|
255
|
+
ex = ArgumentError.new
|
256
|
+
val = subject.await.boom(ex)
|
257
|
+
val.reason.should eq ex
|
258
|
+
val.should be_rejected
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'sets the reason when giving too many optional arguments' do
|
262
|
+
val = subject.await.gather(1, 2, 3, 4, 5)
|
263
|
+
val.reason.should be_a StandardError
|
264
|
+
val.should be_rejected
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'supports attribute accessors' do
|
268
|
+
subject.await.accessor = :foo
|
269
|
+
val = subject.await.accessor
|
270
|
+
val.value.should eq :foo
|
271
|
+
subject.accessor.should eq :foo
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'supports methods with blocks' do
|
275
|
+
val = subject.await.with_block{ :foo }
|
276
|
+
val.value.should eq :foo
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'is aliased as #delay' do
|
280
|
+
val = subject.delay.echo(5)
|
281
|
+
val.should be_a Concurrent::IVar
|
282
|
+
end
|
283
|
+
|
284
|
+
context '#method_missing' do
|
285
|
+
|
286
|
+
it 'defines the method after the first call' do
|
287
|
+
expect { subject.await.method(:echo) }.to raise_error(NameError)
|
288
|
+
subject.await.echo(:foo)
|
289
|
+
expect { subject.await.method(:echo) }.not_to raise_error
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'does not define the method on name/arity exception' do
|
293
|
+
expect { subject.await.method(:bogus) }.to raise_error(NameError)
|
294
|
+
expect { subject.await.bogus }.to raise_error(NameError)
|
295
|
+
expect { subject.await.method(:bogus) }.to raise_error(NameError)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context 'locking' do
|
301
|
+
|
302
|
+
it 'uses the same mutex for both #async and #await' do
|
303
|
+
object = Class.new {
|
304
|
+
include Concurrent::Async
|
305
|
+
attr_reader :bucket
|
306
|
+
def gather(seconds, first, *rest)
|
307
|
+
sleep(seconds)
|
308
|
+
(@bucket ||= []).concat([first])
|
309
|
+
@bucket.concat(rest)
|
310
|
+
end
|
311
|
+
}.new
|
312
|
+
|
313
|
+
object.async.gather(0.5, :a, :b)
|
314
|
+
sleep(0.1)
|
315
|
+
object.await.gather(0, :c, :d)
|
316
|
+
object.bucket.should eq [:a, :b, :c, :d]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'thread_pool_shared'
|
3
|
+
|
4
|
+
share_examples_for :cached_thread_pool do
|
5
|
+
|
6
|
+
let!(:max_threads){ 5 }
|
7
|
+
subject do
|
8
|
+
described_class.new(
|
9
|
+
max_threads: max_threads,
|
10
|
+
overflow_policy: :discard
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:each) do
|
15
|
+
subject.kill
|
16
|
+
sleep(0.1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it_should_behave_like :thread_pool
|
20
|
+
|
21
|
+
context '#initialize' do
|
22
|
+
|
23
|
+
it 'raises an exception when the pool idletime is less than one' do
|
24
|
+
lambda {
|
25
|
+
described_class.new(idletime: 0)
|
26
|
+
}.should raise_error(ArgumentError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises an exception when the pool size is less than one' do
|
30
|
+
lambda {
|
31
|
+
described_class.new(max_threads: 0)
|
32
|
+
}.should raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'sets :max_length to DEFAULT_MAX_POOL_SIZE when not given' do
|
36
|
+
described_class.new.max_length.should eq described_class::DEFAULT_MAX_POOL_SIZE
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#min_length' do
|
41
|
+
|
42
|
+
it 'returns zero on creation' do
|
43
|
+
subject.min_length.should eq 0
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns zero while running' do
|
47
|
+
10.times{ subject.post{ nil } }
|
48
|
+
sleep(0.1)
|
49
|
+
subject.min_length.should eq 0
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns zero once shutdown' do
|
53
|
+
10.times{ subject.post{ nil } }
|
54
|
+
sleep(0.1)
|
55
|
+
subject.shutdown
|
56
|
+
subject.wait_for_termination(1)
|
57
|
+
subject.min_length.should eq 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context '#max_length' do
|
62
|
+
|
63
|
+
it 'returns :max_length on creation' do
|
64
|
+
subject.max_length.should eq max_threads
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns :max_length while running' do
|
68
|
+
10.times{ subject.post{ nil } }
|
69
|
+
sleep(0.1)
|
70
|
+
subject.max_length.should eq max_threads
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns :max_length once shutdown' do
|
74
|
+
10.times{ subject.post{ nil } }
|
75
|
+
sleep(0.1)
|
76
|
+
subject.shutdown
|
77
|
+
subject.wait_for_termination(1)
|
78
|
+
subject.max_length.should eq max_threads
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context '#largest_length' do
|
83
|
+
|
84
|
+
it 'returns zero on creation' do
|
85
|
+
subject.largest_length.should eq 0
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns a non-zero number once tasks have been received' do
|
89
|
+
10.times{ subject.post{ sleep(0.1) } }
|
90
|
+
sleep(0.1)
|
91
|
+
subject.largest_length.should > 0
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns a non-zero number after shutdown if tasks have been received' do
|
95
|
+
10.times{ subject.post{ sleep(0.1) } }
|
96
|
+
sleep(0.1)
|
97
|
+
subject.shutdown
|
98
|
+
subject.wait_for_termination(1)
|
99
|
+
subject.largest_length.should > 0
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context '#status' do
|
104
|
+
|
105
|
+
it 'returns an array' do
|
106
|
+
subject.stub(:warn)
|
107
|
+
subject.status.should be_kind_of(Array)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context '#idletime' do
|
112
|
+
|
113
|
+
subject{ described_class.new(idletime: 42) }
|
114
|
+
|
115
|
+
it 'returns the thread idletime' do
|
116
|
+
subject.idletime.should eq 42
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'worker creation and caching' do
|
121
|
+
|
122
|
+
subject do
|
123
|
+
described_class.new(
|
124
|
+
idletime: 1,
|
125
|
+
max_threads: 5,
|
126
|
+
overflow_policy: :discard
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'never creates more than :max_threads threads' do
|
131
|
+
100.times{ subject << proc{ sleep(1) } }
|
132
|
+
sleep(0.1)
|
133
|
+
subject.length.should eq 5
|
134
|
+
subject.kill
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|