concurrent-ruby 0.5.0 → 0.6.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/concurrent/utilities.rb
CHANGED
data/lib/concurrent/version.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe ActorContext do
|
6
|
+
|
7
|
+
let(:described_class) do
|
8
|
+
Class.new do
|
9
|
+
include ActorContext
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'protects #initialize' do
|
14
|
+
expect {
|
15
|
+
described_class.new
|
16
|
+
}.to raise_error(NoMethodError)
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'callbacks' do
|
20
|
+
|
21
|
+
subject { described_class.send(:new) }
|
22
|
+
|
23
|
+
specify { subject.should respond_to :on_start }
|
24
|
+
|
25
|
+
specify { subject.should respond_to :on_reset }
|
26
|
+
|
27
|
+
specify { subject.should respond_to :on_shutdown }
|
28
|
+
end
|
29
|
+
|
30
|
+
context '#spawn' do
|
31
|
+
|
32
|
+
it 'returns an ActorRef' do
|
33
|
+
described_class.spawn.should be_a ActorRef
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def shared_actor_test_class
|
4
|
+
Class.new do
|
5
|
+
include Concurrent::ActorContext
|
6
|
+
|
7
|
+
attr_reader :argv
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
@argv = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def receive(*msg)
|
14
|
+
case msg.first
|
15
|
+
when :poison
|
16
|
+
raise StandardError
|
17
|
+
when :bullet
|
18
|
+
raise Exception
|
19
|
+
when :stop
|
20
|
+
shutdown
|
21
|
+
when :terminate
|
22
|
+
Thread.current.kill
|
23
|
+
when :sleep
|
24
|
+
sleep(msg.last)
|
25
|
+
when :check
|
26
|
+
msg[1].set(msg.last)
|
27
|
+
else
|
28
|
+
msg.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
share_examples_for :actor_ref do
|
35
|
+
|
36
|
+
it 'includes ActorRef' do
|
37
|
+
subject.should be_a Concurrent::ActorRef
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'running and shutdown' do
|
41
|
+
|
42
|
+
specify { subject.should respond_to :shutdown }
|
43
|
+
|
44
|
+
specify { subject.should be_running }
|
45
|
+
|
46
|
+
specify { subject.should_not be_shutdown }
|
47
|
+
|
48
|
+
specify do
|
49
|
+
subject.shutdown
|
50
|
+
sleep(0.1)
|
51
|
+
subject.should be_shutdown
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'defines a shutdown method on the actor(s)' do
|
55
|
+
subject << :foo
|
56
|
+
subject << :stop
|
57
|
+
sleep(0.1)
|
58
|
+
subject.should be_shutdown
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context '#post' do
|
63
|
+
|
64
|
+
it 'raises an exception when the message is empty' do
|
65
|
+
expect {
|
66
|
+
subject.post
|
67
|
+
}.to raise_error(ArgumentError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'returns an IVar' do
|
71
|
+
subject.post(:foo).should be_a Concurrent::IVar
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'fulfills the IVar when message is processed' do
|
75
|
+
ivar = subject.post(:foo)
|
76
|
+
sleep(0.1)
|
77
|
+
ivar.should be_fulfilled
|
78
|
+
ivar.value.should eq :foo
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'rejects the IVar when message processing fails' do
|
82
|
+
ivar = subject.post(:poison)
|
83
|
+
sleep(0.1)
|
84
|
+
ivar.should be_rejected
|
85
|
+
ivar.reason.should be_a StandardError
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context '#<<' do
|
90
|
+
|
91
|
+
it 'posts the message' do
|
92
|
+
ivar = Concurrent::IVar.new
|
93
|
+
subject << [:check, ivar, :foo]
|
94
|
+
ivar.value(0.1).should eq :foo
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns self' do
|
98
|
+
(subject << [1,2,3,4]).should eq subject
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context '#post with callback' do
|
103
|
+
|
104
|
+
specify 'on success calls the callback with time and value' do
|
105
|
+
expected_value = expected_reason = nil
|
106
|
+
subject.post(:foo) do |time, value, reason|
|
107
|
+
expected_value = value
|
108
|
+
expected_reason = reason
|
109
|
+
end
|
110
|
+
sleep(0.1)
|
111
|
+
|
112
|
+
expected_value.should eq :foo
|
113
|
+
expected_reason.should be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
specify 'on failure calls the callback with time and reason' do
|
117
|
+
expected_value = expected_reason = nil
|
118
|
+
subject.post(:poison) do |time, value, reason|
|
119
|
+
expected_value = value
|
120
|
+
expected_reason = reason
|
121
|
+
end
|
122
|
+
sleep(0.1)
|
123
|
+
|
124
|
+
expected_value.should be_nil
|
125
|
+
expected_reason.should be_a StandardError
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'supresses exceptions thrown by the callback' do
|
129
|
+
expected = nil
|
130
|
+
subject.post(:foo){|time, value, reason| raise StandardError }
|
131
|
+
sleep(0.1)
|
132
|
+
|
133
|
+
subject.post(:bar){|time, value, reason| expected = value }
|
134
|
+
sleep(0.1)
|
135
|
+
|
136
|
+
expected.should eq :bar
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context '#post!' do
|
141
|
+
|
142
|
+
it 'raises an exception when the message is empty' do
|
143
|
+
expect {
|
144
|
+
subject.post!(1)
|
145
|
+
}.to raise_error(ArgumentError)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'blocks for up to the given number of seconds' do
|
149
|
+
start = Time.now.to_f
|
150
|
+
begin
|
151
|
+
subject.post!(1, :sleep, 2)
|
152
|
+
rescue
|
153
|
+
end
|
154
|
+
delta = Time.now.to_f - start
|
155
|
+
delta.should >= 1
|
156
|
+
delta.should <= 2
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'blocks forever when the timeout is nil' do
|
160
|
+
start = Time.now.to_f
|
161
|
+
begin
|
162
|
+
subject.post!(nil, :sleep, 1)
|
163
|
+
rescue
|
164
|
+
end
|
165
|
+
delta = Time.now.to_f - start
|
166
|
+
delta.should > 1
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'raises a TimeoutError when timeout is zero' do
|
170
|
+
expect {
|
171
|
+
subject.post!(0, :foo)
|
172
|
+
}.to raise_error(Concurrent::TimeoutError)
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'raises a TimeoutError when the timeout is reached' do
|
176
|
+
expect {
|
177
|
+
subject.post!(1, :sleep, 10)
|
178
|
+
}.to raise_error(Concurrent::TimeoutError)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'returns the result of success processing' do
|
182
|
+
subject.post!(1, :foo).should eq :foo
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'bubbles exceptions thrown during processing' do
|
186
|
+
expect {
|
187
|
+
subject.post!(1, :poison)
|
188
|
+
}.to raise_error(StandardError)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context '#join' do
|
193
|
+
|
194
|
+
it 'blocks until shutdown when no limit is given' do
|
195
|
+
start = Time.now
|
196
|
+
subject << :foo # start the actor's thread
|
197
|
+
Thread.new{ sleep(1); subject.shutdown }
|
198
|
+
subject.join
|
199
|
+
stop = Time.now
|
200
|
+
|
201
|
+
subject.should be_shutdown
|
202
|
+
stop.should >= start + 1
|
203
|
+
stop.should <= start + 2
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'blocks for no more than the given number of seconds' do
|
207
|
+
start = Time.now
|
208
|
+
subject << :foo # start the actor's thread
|
209
|
+
Thread.new{ sleep(5); subject.shutdown }
|
210
|
+
subject.join(1)
|
211
|
+
stop = Time.now
|
212
|
+
|
213
|
+
stop.should >= start + 1
|
214
|
+
stop.should <= start + 2
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'returns true when shutdown has completed before timeout' do
|
218
|
+
subject << :foo # start the actor's thread
|
219
|
+
Thread.new{ sleep(1); subject.shutdown }
|
220
|
+
subject.join.should be_true
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'returns false on timeout' do
|
224
|
+
subject << :foo # start the actor's thread
|
225
|
+
Thread.new{ sleep(5); subject.shutdown }
|
226
|
+
subject.join(1).should be_false
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'returns immediately when already shutdown' do
|
230
|
+
start = Time.now
|
231
|
+
subject << :foo # start the actor's thread
|
232
|
+
sleep(0.1)
|
233
|
+
subject.shutdown
|
234
|
+
sleep(0.1)
|
235
|
+
|
236
|
+
start = Time.now
|
237
|
+
subject.join
|
238
|
+
Time.now.should >= start
|
239
|
+
Time.now.should <= start + 0.1
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context '#on_error' do
|
244
|
+
|
245
|
+
specify 'is not called on success' do
|
246
|
+
actor = subject.instance_variable_get(:@actor)
|
247
|
+
actor.should_not_receive(:on_error).with(any_args)
|
248
|
+
subject.post(:foo)
|
249
|
+
sleep(0.1)
|
250
|
+
end
|
251
|
+
|
252
|
+
specify 'is called when a message raises an exception' do
|
253
|
+
actor = subject.instance_variable_get(:@actor)
|
254
|
+
actor.should_receive(:on_error).
|
255
|
+
with(anything, [:poison], an_instance_of(StandardError))
|
256
|
+
subject.post(:poison)
|
257
|
+
sleep(0.1)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
context 'observation' do
|
262
|
+
|
263
|
+
let(:observer_class) do
|
264
|
+
Class.new do
|
265
|
+
attr_reader :time, :msg, :value, :reason
|
266
|
+
def update(time, msg, value, reason)
|
267
|
+
@msg = msg
|
268
|
+
@time = time
|
269
|
+
@value = value
|
270
|
+
@reason = reason
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'notifies observers' do
|
276
|
+
o1 = observer_class.new
|
277
|
+
o2 = observer_class.new
|
278
|
+
|
279
|
+
subject.add_observer(o1)
|
280
|
+
subject.add_observer(o2)
|
281
|
+
|
282
|
+
subject << :foo
|
283
|
+
sleep(0.1)
|
284
|
+
|
285
|
+
o1.value.should eq :foo
|
286
|
+
o1.reason.should be_nil
|
287
|
+
|
288
|
+
o2.value.should eq :foo
|
289
|
+
o2.reason.should be_nil
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'does not notify removed observers' do
|
293
|
+
o1 = observer_class.new
|
294
|
+
o2 = observer_class.new
|
295
|
+
|
296
|
+
subject.add_observer(o1)
|
297
|
+
subject.add_observer(o2)
|
298
|
+
|
299
|
+
subject << :foo
|
300
|
+
sleep(0.1)
|
301
|
+
|
302
|
+
subject.delete_observer(o1)
|
303
|
+
subject << :bar
|
304
|
+
sleep(0.1)
|
305
|
+
o1.value.should_not eq :bar
|
306
|
+
|
307
|
+
subject.delete_observers
|
308
|
+
subject << :baz
|
309
|
+
sleep(0.1)
|
310
|
+
o1.value.should_not eq :baz
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -6,6 +6,12 @@ module Concurrent
|
|
6
6
|
|
7
7
|
describe Actor do
|
8
8
|
|
9
|
+
before do
|
10
|
+
# suppress deprecation warnings.
|
11
|
+
Concurrent::Actor.any_instance.stub(:warn)
|
12
|
+
Concurrent::Actor.stub(:warn)
|
13
|
+
end
|
14
|
+
|
9
15
|
let(:actor_class) do
|
10
16
|
Class.new(Actor) do
|
11
17
|
attr_reader :last_message
|
@@ -127,6 +133,7 @@ module Concurrent
|
|
127
133
|
end
|
128
134
|
|
129
135
|
it 'notifies observers when a message is successfully handled' do
|
136
|
+
pending('intermittently failing; deprecated')
|
130
137
|
observer.should_receive(:update).exactly(10).times.with(any_args())
|
131
138
|
subject.add_observer(observer)
|
132
139
|
@thread = Thread.new{ subject.run }
|
@@ -196,7 +203,7 @@ module Concurrent
|
|
196
203
|
it 'passes a duplicate of the given block to each actor in the pool' do
|
197
204
|
block = proc{ nil }
|
198
205
|
block.should_receive(:dup).exactly(5).times.and_return(proc{ nil })
|
199
|
-
mailbox, pool =
|
206
|
+
mailbox, pool = clazz.pool(5, &block)
|
200
207
|
end
|
201
208
|
|
202
209
|
it 'gives all pool the same mailbox' do
|
@@ -237,6 +244,7 @@ module Concurrent
|
|
237
244
|
end
|
238
245
|
|
239
246
|
it 'posts to the mailbox with Poolbox#<<' do
|
247
|
+
pending('intermittently failing; deprecated')
|
240
248
|
@expected = false
|
241
249
|
mailbox, pool = clazz.pool(1)
|
242
250
|
@thread = Thread.new{ pool.first.run }
|
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative 'dereferenceable_shared'
|
3
|
-
require_relative 'uses_global_thread_pool_shared'
|
4
3
|
|
5
4
|
module Concurrent
|
6
5
|
|
7
6
|
describe Agent do
|
8
7
|
|
9
|
-
|
8
|
+
let(:executor) { PerThreadExecutor.new }
|
9
|
+
|
10
|
+
subject { Agent.new(0, executor: executor) }
|
10
11
|
|
11
12
|
let(:observer) do
|
12
13
|
Class.new do
|
@@ -17,30 +18,22 @@ module Concurrent
|
|
17
18
|
end.new
|
18
19
|
end
|
19
20
|
|
20
|
-
before(:each) do
|
21
|
-
Agent.thread_pool = FixedThreadPool.new(1)
|
22
|
-
end
|
23
|
-
|
24
21
|
context 'behavior' do
|
25
22
|
|
26
|
-
# uses_global_thread_pool
|
27
|
-
|
28
|
-
let!(:thread_pool_user) { Agent }
|
29
|
-
|
30
|
-
it_should_behave_like Concurrent::UsesGlobalThreadPool
|
31
|
-
|
32
23
|
# dereferenceable
|
33
24
|
|
34
25
|
def dereferenceable_subject(value, opts = {})
|
26
|
+
opts = opts.merge(executor: executor)
|
35
27
|
Agent.new(value, opts)
|
36
28
|
end
|
37
29
|
|
38
30
|
def dereferenceable_observable(opts = {})
|
31
|
+
opts = opts.merge(executor: executor)
|
39
32
|
Agent.new(0, opts)
|
40
33
|
end
|
41
34
|
|
42
35
|
def execute_dereferenceable(subject)
|
43
|
-
subject
|
36
|
+
subject.post{|value| 10 }
|
44
37
|
sleep(0.1)
|
45
38
|
end
|
46
39
|
|
@@ -49,6 +42,8 @@ module Concurrent
|
|
49
42
|
|
50
43
|
context '#initialize' do
|
51
44
|
|
45
|
+
let(:executor) { ImmediateExecutor.new }
|
46
|
+
|
52
47
|
it 'sets the value to the given initial state' do
|
53
48
|
Agent.new(10).value.should eq 10
|
54
49
|
end
|
@@ -60,6 +55,30 @@ module Concurrent
|
|
60
55
|
it 'sets the timeout to the default when nil' do
|
61
56
|
Agent.new(0).timeout.should eq Agent::TIMEOUT
|
62
57
|
end
|
58
|
+
|
59
|
+
it 'uses the executor given with the :executor option' do
|
60
|
+
executor.should_receive(:post).with(any_args).and_return(0)
|
61
|
+
agent = Agent.new(0, executor: executor)
|
62
|
+
agent.post{|value| 0 }
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'uses the global operation pool when :operation is true' do
|
66
|
+
Concurrent.configuration.should_receive(:global_operation_pool).and_return(executor)
|
67
|
+
agent = Agent.new(0, operation: true)
|
68
|
+
agent.post{|value| 0 }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'uses the global task pool when :task is true' do
|
72
|
+
Concurrent.configuration.should_receive(:global_task_pool).and_return(executor)
|
73
|
+
agent = Agent.new(0, task: true)
|
74
|
+
agent.post{|value| 0 }
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'uses the global task pool by default' do
|
78
|
+
Concurrent.configuration.should_receive(:global_task_pool).and_return(executor)
|
79
|
+
agent = Agent.new(0)
|
80
|
+
agent.post{|value| 0 }
|
81
|
+
end
|
63
82
|
end
|
64
83
|
|
65
84
|
context '#rescue' do
|
@@ -116,7 +135,7 @@ module Concurrent
|
|
116
135
|
context '#post' do
|
117
136
|
|
118
137
|
it 'adds the given block to the queue' do
|
119
|
-
|
138
|
+
executor.should_receive(:post).with(no_args).exactly(3).times
|
120
139
|
subject.post { sleep(100) }
|
121
140
|
subject.post { nil }
|
122
141
|
subject.post { nil }
|
@@ -124,7 +143,7 @@ module Concurrent
|
|
124
143
|
end
|
125
144
|
|
126
145
|
it 'does not add to the queue when no block is given' do
|
127
|
-
|
146
|
+
executor.should_receive(:post).with(no_args).exactly(2).times
|
128
147
|
subject.post { sleep(100) }
|
129
148
|
subject.post
|
130
149
|
subject.post { nil }
|
@@ -140,12 +159,12 @@ module Concurrent
|
|
140
159
|
subject.post { @expected << 2 }
|
141
160
|
subject.post { @expected << 3 }
|
142
161
|
sleep(0.1)
|
143
|
-
@expected.should eq [1, 2, 3]
|
162
|
+
@expected.sort.should eq [1, 2, 3]
|
144
163
|
end
|
145
164
|
|
146
165
|
it 'passes the current value to the handler' do
|
147
166
|
@expected = nil
|
148
|
-
Agent.new(10).post { |i| @expected = i }
|
167
|
+
Agent.new(10, executor: executor).post { |i| @expected = i }
|
149
168
|
sleep(0.1)
|
150
169
|
@expected.should eq 10
|
151
170
|
end
|
@@ -157,7 +176,7 @@ module Concurrent
|
|
157
176
|
end
|
158
177
|
|
159
178
|
it 'rejects the handler after timeout reached' do
|
160
|
-
agent = Agent.new(0, timeout: 0.1)
|
179
|
+
agent = Agent.new(0, timeout: 0.1, executor: executor)
|
161
180
|
agent.post { sleep(1); 10 }
|
162
181
|
agent.value.should eq 0
|
163
182
|
end
|
@@ -182,21 +201,21 @@ module Concurrent
|
|
182
201
|
end
|
183
202
|
|
184
203
|
it 'sets the new value when the validator returns true' do
|
185
|
-
agent = Agent.new(0).validate { true }
|
204
|
+
agent = Agent.new(0, executor: executor).validate { true }
|
186
205
|
agent.post { 10 }
|
187
206
|
sleep(0.1)
|
188
207
|
agent.value.should eq 10
|
189
208
|
end
|
190
209
|
|
191
210
|
it 'does not change the value when the validator returns false' do
|
192
|
-
agent = Agent.new(0).validate { false }
|
211
|
+
agent = Agent.new(0, executor: executor).validate { false }
|
193
212
|
agent.post { 10 }
|
194
213
|
sleep(0.1)
|
195
214
|
agent.value.should eq 0
|
196
215
|
end
|
197
216
|
|
198
217
|
it 'does not change the value when the validator raises an exception' do
|
199
|
-
agent = Agent.new(0).validate { raise StandardError }
|
218
|
+
agent = Agent.new(0, executor: executor).validate { raise StandardError }
|
200
219
|
agent.post { 10 }
|
201
220
|
sleep(0.1)
|
202
221
|
agent.value.should eq 0
|
@@ -208,85 +227,85 @@ module Concurrent
|
|
208
227
|
it 'calls the first exception block with a matching class' do
|
209
228
|
@expected = nil
|
210
229
|
subject.
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
230
|
+
rescue(StandardError) { |ex| @expected = 1 }.
|
231
|
+
rescue(StandardError) { |ex| @expected = 2 }.
|
232
|
+
rescue(StandardError) { |ex| @expected = 3 }
|
233
|
+
subject.post { raise StandardError }
|
234
|
+
sleep(0.1)
|
235
|
+
@expected.should eq 1
|
236
|
+
end
|
218
237
|
|
219
238
|
it 'matches all with a rescue with no class given' do
|
220
239
|
@expected = nil
|
221
240
|
subject.
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
241
|
+
rescue(LoadError) { |ex| @expected = 1 }.
|
242
|
+
rescue { |ex| @expected = 2 }.
|
243
|
+
rescue(StandardError) { |ex| @expected = 3 }
|
244
|
+
subject.post { raise NoMethodError }
|
245
|
+
sleep(0.1)
|
246
|
+
@expected.should eq 2
|
247
|
+
end
|
229
248
|
|
230
249
|
it 'searches associated rescue handlers in order' do
|
231
250
|
@expected = nil
|
232
251
|
subject.
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
252
|
+
rescue(ArgumentError) { |ex| @expected = 1 }.
|
253
|
+
rescue(LoadError) { |ex| @expected = 2 }.
|
254
|
+
rescue(StandardError) { |ex| @expected = 3 }
|
255
|
+
subject.post { raise ArgumentError }
|
256
|
+
sleep(0.1)
|
257
|
+
@expected.should eq 1
|
239
258
|
|
240
|
-
|
241
|
-
|
242
|
-
|
259
|
+
@expected = nil
|
260
|
+
subject.
|
261
|
+
rescue(ArgumentError) { |ex| @expected = 1 }.
|
243
262
|
rescue(LoadError) { |ex| @expected = 2 }.
|
244
263
|
rescue(StandardError) { |ex| @expected = 3 }
|
245
|
-
|
246
|
-
|
247
|
-
|
264
|
+
subject.post { raise LoadError }
|
265
|
+
sleep(0.1)
|
266
|
+
@expected.should eq 2
|
248
267
|
|
249
|
-
|
250
|
-
|
268
|
+
@expected = nil
|
269
|
+
subject.
|
251
270
|
rescue(ArgumentError) { |ex| @expected = 1 }.
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
271
|
+
rescue(LoadError) { |ex| @expected = 2 }.
|
272
|
+
rescue(StandardError) { |ex| @expected = 3 }
|
273
|
+
subject.post { raise StandardError }
|
274
|
+
sleep(0.1)
|
275
|
+
@expected.should eq 3
|
276
|
+
end
|
258
277
|
|
259
278
|
it 'passes the exception object to the matched block' do
|
260
279
|
@expected = nil
|
261
280
|
subject.
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
281
|
+
rescue(ArgumentError) { |ex| @expected = ex }.
|
282
|
+
rescue(LoadError) { |ex| @expected = ex }.
|
283
|
+
rescue(StandardError) { |ex| @expected = ex }
|
284
|
+
subject.post { raise StandardError }
|
285
|
+
sleep(0.1)
|
286
|
+
@expected.should be_a(StandardError)
|
287
|
+
end
|
269
288
|
|
270
289
|
it 'ignores rescuers without a block' do
|
271
290
|
@expected = nil
|
272
291
|
subject.
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
292
|
+
rescue(StandardError).
|
293
|
+
rescue(StandardError) { |ex| @expected = ex }
|
294
|
+
subject.post { raise StandardError }
|
295
|
+
sleep(0.1)
|
296
|
+
@expected.should be_a(StandardError)
|
297
|
+
end
|
279
298
|
|
280
299
|
it 'supresses the exception if no rescue matches' do
|
281
300
|
lambda {
|
282
301
|
subject.
|
283
|
-
|
284
|
-
|
285
|
-
|
302
|
+
rescue(ArgumentError) { |ex| @expected = ex }.
|
303
|
+
rescue(NotImplementedError) { |ex| @expected = ex }.
|
304
|
+
rescue(NoMethodError) { |ex| @expected = ex }
|
286
305
|
subject.post { raise StandardError }
|
287
306
|
sleep(0.1)
|
288
307
|
}.should_not raise_error
|
289
|
-
|
308
|
+
end
|
290
309
|
|
291
310
|
it 'suppresses exceptions thrown from rescue handlers' do
|
292
311
|
lambda {
|
@@ -300,7 +319,7 @@ module Concurrent
|
|
300
319
|
context 'observation' do
|
301
320
|
|
302
321
|
it 'notifies all observers when the value changes' do
|
303
|
-
agent = Agent.new(0)
|
322
|
+
agent = Agent.new(0, executor: executor)
|
304
323
|
agent.add_observer(observer)
|
305
324
|
agent.post { 10 }
|
306
325
|
sleep(0.1)
|
@@ -308,7 +327,7 @@ module Concurrent
|
|
308
327
|
end
|
309
328
|
|
310
329
|
it 'does not notify removed observers when the value changes' do
|
311
|
-
agent = Agent.new(0)
|
330
|
+
agent = Agent.new(0, executor: executor)
|
312
331
|
agent.add_observer(observer)
|
313
332
|
agent.delete_observer(observer)
|
314
333
|
agent.post { 10 }
|
@@ -317,7 +336,7 @@ module Concurrent
|
|
317
336
|
end
|
318
337
|
|
319
338
|
it 'does not notify observers when validation fails' do
|
320
|
-
agent = Agent.new(0)
|
339
|
+
agent = Agent.new(0, executor: executor)
|
321
340
|
agent.validate { false }
|
322
341
|
agent.add_observer(observer)
|
323
342
|
agent.post { 10 }
|
@@ -326,7 +345,7 @@ module Concurrent
|
|
326
345
|
end
|
327
346
|
|
328
347
|
it 'does not notify observers when the handler raises an exception' do
|
329
|
-
agent = Agent.new(0)
|
348
|
+
agent = Agent.new(0, executor: executor)
|
330
349
|
agent.add_observer(observer)
|
331
350
|
agent.post { raise StandardError }
|
332
351
|
sleep(0.1)
|
@@ -337,7 +356,7 @@ module Concurrent
|
|
337
356
|
context 'aliases' do
|
338
357
|
|
339
358
|
it 'aliases #deref for #value' do
|
340
|
-
Agent.new(10).deref.should eq 10
|
359
|
+
Agent.new(10, executor: executor).deref.should eq 10
|
341
360
|
end
|
342
361
|
|
343
362
|
it 'aliases #validates for :validate' do
|
@@ -381,30 +400,12 @@ module Concurrent
|
|
381
400
|
end
|
382
401
|
|
383
402
|
it 'aliases #add_watch for #add_observer' do
|
384
|
-
agent = Agent.new(0)
|
403
|
+
agent = Agent.new(0, executor: executor)
|
385
404
|
agent.add_watch(observer)
|
386
405
|
agent.post { 10 }
|
387
406
|
sleep(0.1)
|
388
407
|
observer.value.should eq 10
|
389
408
|
end
|
390
409
|
end
|
391
|
-
|
392
|
-
context 'stress test' do
|
393
|
-
|
394
|
-
before(:each) do
|
395
|
-
Agent.thread_pool = FixedThreadPool.new(5)
|
396
|
-
end
|
397
|
-
|
398
|
-
specify do
|
399
|
-
count = 10_000
|
400
|
-
counter = Concurrent::Agent.new(0)
|
401
|
-
|
402
|
-
count.times do |i|
|
403
|
-
counter.post { |value| value + 1 }
|
404
|
-
end
|
405
|
-
|
406
|
-
sleep(0.1) until counter.value == count
|
407
|
-
end
|
408
|
-
end
|
409
410
|
end
|
410
411
|
end
|