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
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
|