concurrent-ruby 0.2.2 → 0.3.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 +45 -42
- data/lib/concurrent.rb +5 -6
- data/lib/concurrent/agent.rb +29 -33
- data/lib/concurrent/cached_thread_pool.rb +26 -105
- data/lib/concurrent/channel.rb +94 -0
- data/lib/concurrent/event.rb +8 -17
- data/lib/concurrent/executor.rb +68 -72
- data/lib/concurrent/fixed_thread_pool.rb +15 -83
- data/lib/concurrent/functions.rb +7 -22
- data/lib/concurrent/future.rb +29 -9
- data/lib/concurrent/null_thread_pool.rb +5 -2
- data/lib/concurrent/obligation.rb +6 -16
- data/lib/concurrent/promise.rb +9 -10
- data/lib/concurrent/runnable.rb +103 -0
- data/lib/concurrent/supervisor.rb +271 -44
- data/lib/concurrent/thread_pool.rb +112 -39
- data/lib/concurrent/version.rb +1 -1
- data/md/executor.md +9 -3
- data/md/goroutine.md +11 -9
- data/md/reactor.md +32 -0
- data/md/supervisor.md +43 -0
- data/spec/concurrent/agent_spec.rb +128 -51
- data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
- data/spec/concurrent/channel_spec.rb +446 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
- data/spec/concurrent/event_spec.rb +0 -19
- data/spec/concurrent/executor_spec.rb +167 -119
- data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
- data/spec/concurrent/functions_spec.rb +0 -20
- data/spec/concurrent/future_spec.rb +88 -0
- data/spec/concurrent/null_thread_pool_spec.rb +23 -2
- data/spec/concurrent/obligation_shared.rb +0 -5
- data/spec/concurrent/promise_spec.rb +9 -10
- data/spec/concurrent/runnable_shared.rb +62 -0
- data/spec/concurrent/runnable_spec.rb +233 -0
- data/spec/concurrent/supervisor_spec.rb +912 -47
- data/spec/concurrent/thread_pool_shared.rb +18 -31
- data/spec/spec_helper.rb +10 -3
- metadata +17 -23
- data/lib/concurrent/defer.rb +0 -65
- data/lib/concurrent/reactor.rb +0 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
- data/lib/concurrent/utilities.rb +0 -32
- data/md/defer.md +0 -174
- data/spec/concurrent/defer_spec.rb +0 -199
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
- data/spec/concurrent/reactor_spec.rb +0 -364
- data/spec/concurrent/utilities_spec.rb +0 -74
@@ -1,19 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'thread'
|
2
|
+
require 'functional'
|
2
3
|
|
3
4
|
require 'concurrent/event'
|
4
|
-
require 'concurrent/utilities'
|
5
|
-
|
6
|
-
behavior_info(:thread_pool,
|
7
|
-
running?: 0,
|
8
|
-
shutdown?: 0,
|
9
|
-
killed?: 0,
|
10
|
-
shutdown: 0,
|
11
|
-
kill: 0,
|
12
|
-
size: 0,
|
13
|
-
wait_for_termination: -1,
|
14
|
-
post: -1,
|
15
|
-
:<< => 1,
|
16
|
-
status: 0)
|
17
5
|
|
18
6
|
behavior_info(:global_thread_pool,
|
19
7
|
post: -1,
|
@@ -21,44 +9,49 @@ behavior_info(:global_thread_pool,
|
|
21
9
|
|
22
10
|
module Concurrent
|
23
11
|
|
24
|
-
class
|
12
|
+
class AbstractThreadPool
|
25
13
|
|
26
|
-
|
27
|
-
@status = :running
|
28
|
-
@queue = Queue.new
|
29
|
-
@termination = Event.new
|
30
|
-
@pool = []
|
31
|
-
end
|
14
|
+
WorkerContext = Struct.new(:status, :idletime, :thread)
|
32
15
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
16
|
+
MIN_POOL_SIZE = 1
|
17
|
+
MAX_POOL_SIZE = 256
|
36
18
|
|
37
|
-
|
38
|
-
|
19
|
+
attr_accessor :max_threads
|
20
|
+
|
21
|
+
def initialize(opts = {})
|
22
|
+
@max_threads = opts[:max_threads] || opts[:max] || MAX_POOL_SIZE
|
23
|
+
if @max_threads < MIN_POOL_SIZE || @max_threads > MAX_POOL_SIZE
|
24
|
+
raise ArgumentError.new("pool size must be from #{MIN_POOL_SIZE} to #{MAX_POOL_SIZE}")
|
25
|
+
end
|
26
|
+
|
27
|
+
@state = :running
|
28
|
+
@mutex ||= Mutex.new
|
29
|
+
@terminator ||= Event.new
|
30
|
+
@pool ||= []
|
31
|
+
@queue ||= Queue.new
|
32
|
+
@working = 0
|
39
33
|
end
|
40
34
|
|
41
|
-
def
|
42
|
-
return @
|
35
|
+
def running?
|
36
|
+
return @state == :running
|
43
37
|
end
|
44
38
|
|
45
39
|
def shutdown
|
46
|
-
mutex.synchronize do
|
40
|
+
@mutex.synchronize do
|
41
|
+
@collector.kill if @collector && @collector.status
|
47
42
|
if @pool.empty?
|
48
|
-
@
|
43
|
+
@state = :shutdown
|
44
|
+
@terminator.set
|
49
45
|
else
|
50
|
-
@
|
46
|
+
@state = :shuttingdown
|
51
47
|
@pool.size.times{ @queue << :stop }
|
52
48
|
end
|
53
49
|
end
|
50
|
+
Thread.pass
|
54
51
|
end
|
55
52
|
|
56
53
|
def wait_for_termination(timeout = nil)
|
57
|
-
|
58
|
-
return true
|
59
|
-
else
|
60
|
-
return @termination.wait(timeout)
|
61
|
-
end
|
54
|
+
return @terminator.wait(timeout)
|
62
55
|
end
|
63
56
|
|
64
57
|
def <<(block)
|
@@ -66,11 +59,91 @@ module Concurrent
|
|
66
59
|
return self
|
67
60
|
end
|
68
61
|
|
62
|
+
def kill
|
63
|
+
@mutex.synchronize do
|
64
|
+
@collector.kill if @collector && @collector.status
|
65
|
+
@state = :shuttingdown
|
66
|
+
@pool.each{|t| Thread.kill(t.thread) }
|
67
|
+
@terminator.set
|
68
|
+
end
|
69
|
+
Thread.pass
|
70
|
+
end
|
71
|
+
|
72
|
+
def size
|
73
|
+
return @mutex.synchronize do
|
74
|
+
@state == :running ? @pool.length : 0
|
75
|
+
end
|
76
|
+
end
|
77
|
+
alias_method :length, :size
|
78
|
+
|
79
|
+
def status
|
80
|
+
@mutex.synchronize do
|
81
|
+
@pool.collect do |worker|
|
82
|
+
[
|
83
|
+
worker.status,
|
84
|
+
worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
|
85
|
+
worker.thread.status
|
86
|
+
]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
69
91
|
protected
|
70
92
|
|
71
|
-
|
72
|
-
|
73
|
-
|
93
|
+
def timestamp
|
94
|
+
return Time.now.to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def create_worker_thread
|
100
|
+
context = WorkerContext.new(:idle, timestamp, nil)
|
101
|
+
|
102
|
+
context.thread = Thread.new do
|
103
|
+
Thread.current.abort_on_exception = false
|
104
|
+
loop do
|
105
|
+
task = @queue.pop
|
106
|
+
if task == :stop
|
107
|
+
@mutex.synchronize do
|
108
|
+
context.status = :stopping
|
109
|
+
end
|
110
|
+
break
|
111
|
+
else
|
112
|
+
@mutex.synchronize do
|
113
|
+
context.status = :working
|
114
|
+
@working += 1
|
115
|
+
end
|
116
|
+
task.last.call(*task.first)
|
117
|
+
@mutex.synchronize do
|
118
|
+
@working -= 1
|
119
|
+
context.status = :idle
|
120
|
+
context.idletime = timestamp
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
@mutex.synchronize do
|
125
|
+
@pool.delete(context)
|
126
|
+
if @pool.empty? && @state != :running
|
127
|
+
@terminator.set
|
128
|
+
@state = :shutdown
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
Thread.pass
|
134
|
+
run_garbage_collector unless @collector && @collector.alive?
|
135
|
+
return context
|
136
|
+
end
|
137
|
+
|
138
|
+
def run_garbage_collector
|
139
|
+
@collector = Thread.new do
|
140
|
+
Thread.current.abort_on_exception = false
|
141
|
+
loop do
|
142
|
+
sleep(1)
|
143
|
+
@mutex.synchronize { collect_garbage }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
Thread.pass
|
74
147
|
end
|
75
148
|
end
|
76
149
|
end
|
data/lib/concurrent/version.rb
CHANGED
data/md/executor.md
CHANGED
@@ -21,10 +21,16 @@ One other advantage of the `Executor` class is that it forces the bsiness logic
|
|
21
21
|
be completely decoupled from the threading logic. The business logic can be tested
|
22
22
|
separately then passed to the an executor for scheduling and running.
|
23
23
|
|
24
|
+
The `Executor` is the yin to to the
|
25
|
+
[Supervisor's](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md)
|
26
|
+
yang. Where the `Supervisor` is intended to manage long-running threads that operate
|
27
|
+
continuously, the `Executor` is intended to manage fairly short operations that
|
28
|
+
occur repeatedly at regular intervals.
|
29
|
+
|
24
30
|
Unlike some of the others concurrency objects in the library, executors do not
|
25
|
-
run on the global. In my experience the types of tasks that will benefit
|
26
|
-
the `Executor` class tend to also be long running. For this reason they get
|
27
|
-
own thread every time the task is executed.
|
31
|
+
run on the global thread pool. In my experience the types of tasks that will benefit
|
32
|
+
from the `Executor` class tend to also be long running. For this reason they get
|
33
|
+
their own thread every time the task is executed.
|
28
34
|
|
29
35
|
## ExecutionContext
|
30
36
|
|
data/md/goroutine.md
CHANGED
@@ -11,15 +11,17 @@ for processing.
|
|
11
11
|
```ruby
|
12
12
|
require 'concurrent'
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
go(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
go('foo'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Boom!\n" }
|
15
|
+
go('bar'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Pow!\n" }
|
16
|
+
go('baz'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Zap!\n" }
|
17
|
+
sleep(0.5)
|
18
|
+
|
19
|
+
#=> foo
|
20
|
+
#=> bar
|
21
|
+
#=> baz
|
22
|
+
#=> Boom!
|
23
|
+
#=> Pow!
|
24
|
+
#=> Zap!
|
23
25
|
```
|
24
26
|
|
25
27
|
## Copyright
|
data/md/reactor.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Event
|
2
|
+
|
3
|
+
TBD...
|
4
|
+
|
5
|
+
## Copyright
|
6
|
+
|
7
|
+
*Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
8
|
+
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
9
|
+
|
10
|
+
## License
|
11
|
+
|
12
|
+
Released under the MIT license.
|
13
|
+
|
14
|
+
http://www.opensource.org/licenses/mit-license.php
|
15
|
+
|
16
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
|
+
> of this software and associated documentation files (the "Software"), to deal
|
18
|
+
> in the Software without restriction, including without limitation the rights
|
19
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
20
|
+
> copies of the Software, and to permit persons to whom the Software is
|
21
|
+
> furnished to do so, subject to the following conditions:
|
22
|
+
>
|
23
|
+
> The above copyright notice and this permission notice shall be included in
|
24
|
+
> all copies or substantial portions of the Software.
|
25
|
+
>
|
26
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
27
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
28
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
29
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
30
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
31
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
32
|
+
> THE SOFTWARE.
|
data/md/supervisor.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# You don't need to get no supervisor! You the supervisor today!
|
2
|
+
|
3
|
+
TBD...
|
4
|
+
|
5
|
+
The `Sypervisor` is the yin to to the
|
6
|
+
[Executor's](https://github.com/jdantonio/concurrent-ruby/blob/master/md/executor.md)
|
7
|
+
yang. Where the `Supervisor` is intended to manage long-running threads that operate
|
8
|
+
continuously, the `Executor` is intended to manage fairly short operations that
|
9
|
+
occur repeatedly at regular intervals.
|
10
|
+
|
11
|
+
* [Supervisor Module](http://www.erlang.org/doc/man/supervisor.html)
|
12
|
+
* [Supervisor Behaviour](http://www.erlang.org/doc/design_principles/sup_princ.html)
|
13
|
+
* [Who Supervises The Supervisors?](http://learnyousomeerlang.com/supervisors)
|
14
|
+
* [OTP Design Principles](http://www.erlang.org/doc/design_principles/des_princ.html)
|
15
|
+
|
16
|
+
## Copyright
|
17
|
+
|
18
|
+
*Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
19
|
+
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
20
|
+
|
21
|
+
## License
|
22
|
+
|
23
|
+
Released under the MIT license.
|
24
|
+
|
25
|
+
http://www.opensource.org/licenses/mit-license.php
|
26
|
+
|
27
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
28
|
+
> of this software and associated documentation files (the "Software"), to deal
|
29
|
+
> in the Software without restriction, including without limitation the rights
|
30
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
31
|
+
> copies of the Software, and to permit persons to whom the Software is
|
32
|
+
> furnished to do so, subject to the following conditions:
|
33
|
+
>
|
34
|
+
> The above copyright notice and this permission notice shall be included in
|
35
|
+
> all copies or substantial portions of the Software.
|
36
|
+
>
|
37
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
38
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
39
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
40
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
41
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
42
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
43
|
+
> THE SOFTWARE.
|
@@ -30,21 +30,12 @@ module Concurrent
|
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'sets the timeout to the given value' do
|
33
|
-
Agent.new(0, 5).timeout.should eq 5
|
33
|
+
Agent.new(0, timeout: 5).timeout.should eq 5
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'sets the timeout to the default when nil' do
|
37
37
|
Agent.new(0).timeout.should eq Agent::TIMEOUT
|
38
38
|
end
|
39
|
-
|
40
|
-
it 'sets the length to zero' do
|
41
|
-
Agent.new(10).length.should eq 0
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'spawns the worker thread' do
|
45
|
-
Agent.thread_pool.should_receive(:post).once.with(any_args())
|
46
|
-
Agent.new(0)
|
47
|
-
end
|
48
39
|
end
|
49
40
|
|
50
41
|
context '#rescue' do
|
@@ -96,45 +87,19 @@ module Concurrent
|
|
96
87
|
context '#post' do
|
97
88
|
|
98
89
|
it 'adds the given block to the queue' do
|
90
|
+
Agent.thread_pool.should_receive(:post).with(no_args()).exactly(3).times
|
99
91
|
subject.post{ sleep(100) }
|
100
|
-
sleep(0.1)
|
101
|
-
before = subject.length
|
102
92
|
subject.post{ nil }
|
103
93
|
subject.post{ nil }
|
104
|
-
|
94
|
+
sleep(0.1)
|
105
95
|
end
|
106
96
|
|
107
97
|
it 'does not add to the queue when no block is given' do
|
98
|
+
Agent.thread_pool.should_receive(:post).with(no_args()).exactly(2).times
|
108
99
|
subject.post{ sleep(100) }
|
109
|
-
sleep(0.1)
|
110
|
-
before = subject.length
|
111
100
|
subject.post
|
112
101
|
subject.post{ nil }
|
113
|
-
subject.length.should eq before+1
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
context '#length' do
|
118
|
-
|
119
|
-
it 'should be zero for a new agent' do
|
120
|
-
subject.length.should eq 0
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'should increase by one for each #post' do
|
124
|
-
subject.post{ sleep(100) }
|
125
102
|
sleep(0.1)
|
126
|
-
subject.post{ sleep }
|
127
|
-
subject.post{ sleep }
|
128
|
-
subject.post{ sleep }
|
129
|
-
subject.length.should eq 3
|
130
|
-
end
|
131
|
-
|
132
|
-
it 'should decrease by one each time a handler is run' do
|
133
|
-
subject.post{ nil }
|
134
|
-
subject.post{ sleep }
|
135
|
-
subject.post{ sleep }
|
136
|
-
sleep(0.1)
|
137
|
-
subject.length.should eq 1
|
138
103
|
end
|
139
104
|
end
|
140
105
|
|
@@ -163,7 +128,7 @@ module Concurrent
|
|
163
128
|
end
|
164
129
|
|
165
130
|
it 'rejects the handler after timeout reached' do
|
166
|
-
agent = Agent.new(0, 0.1)
|
131
|
+
agent = Agent.new(0, timeout: 0.1)
|
167
132
|
agent.post{ sleep(1); 10 }
|
168
133
|
agent.value.should eq 0
|
169
134
|
end
|
@@ -238,7 +203,7 @@ module Concurrent
|
|
238
203
|
subject.
|
239
204
|
rescue(ArgumentError){|ex| @expected = 1 }.
|
240
205
|
rescue(LoadError){|ex| @expected = 2 }.
|
241
|
-
rescue(
|
206
|
+
rescue(StandardError){|ex| @expected = 3 }
|
242
207
|
subject.post{ raise ArgumentError }
|
243
208
|
sleep(0.1)
|
244
209
|
@expected.should eq 1
|
@@ -247,7 +212,7 @@ module Concurrent
|
|
247
212
|
subject.
|
248
213
|
rescue(ArgumentError){|ex| @expected = 1 }.
|
249
214
|
rescue(LoadError){|ex| @expected = 2 }.
|
250
|
-
rescue(
|
215
|
+
rescue(StandardError){|ex| @expected = 3 }
|
251
216
|
subject.post{ raise LoadError }
|
252
217
|
sleep(0.1)
|
253
218
|
@expected.should eq 2
|
@@ -256,7 +221,7 @@ module Concurrent
|
|
256
221
|
subject.
|
257
222
|
rescue(ArgumentError){|ex| @expected = 1 }.
|
258
223
|
rescue(LoadError){|ex| @expected = 2 }.
|
259
|
-
rescue(
|
224
|
+
rescue(StandardError){|ex| @expected = 3 }
|
260
225
|
subject.post{ raise StandardError }
|
261
226
|
sleep(0.1)
|
262
227
|
@expected.should eq 3
|
@@ -267,7 +232,7 @@ module Concurrent
|
|
267
232
|
subject.
|
268
233
|
rescue(ArgumentError){|ex| @expected = ex }.
|
269
234
|
rescue(LoadError){|ex| @expected = ex }.
|
270
|
-
rescue(
|
235
|
+
rescue(StandardError){|ex| @expected = ex }
|
271
236
|
subject.post{ raise StandardError }
|
272
237
|
sleep(0.1)
|
273
238
|
@expected.should be_a(StandardError)
|
@@ -275,11 +240,9 @@ module Concurrent
|
|
275
240
|
|
276
241
|
it 'ignores rescuers without a block' do
|
277
242
|
@expected = nil
|
278
|
-
|
243
|
+
Promise.new{ raise StandardError }.
|
279
244
|
rescue(StandardError).
|
280
|
-
rescue(StandardError){|ex| @expected = ex }
|
281
|
-
rescue(Exception){|ex| @expected = ex }
|
282
|
-
subject.post{ raise StandardError }
|
245
|
+
rescue(StandardError){|ex| @expected = ex }
|
283
246
|
sleep(0.1)
|
284
247
|
@expected.should be_a(StandardError)
|
285
248
|
end
|
@@ -288,8 +251,8 @@ module Concurrent
|
|
288
251
|
lambda {
|
289
252
|
subject.
|
290
253
|
rescue(ArgumentError){|ex| @expected = ex }.
|
291
|
-
rescue(
|
292
|
-
rescue(
|
254
|
+
rescue(NotImplementedError){|ex| @expected = ex }.
|
255
|
+
rescue(NoMethodError){|ex| @expected = ex }
|
293
256
|
subject.post{ raise StandardError }
|
294
257
|
sleep(0.1)
|
295
258
|
}.should_not raise_error
|
@@ -297,13 +260,109 @@ module Concurrent
|
|
297
260
|
|
298
261
|
it 'supresses exceptions thrown from rescue handlers' do
|
299
262
|
lambda {
|
300
|
-
subject.rescue(
|
263
|
+
subject.rescue(StandardError){ raise StandardError }
|
301
264
|
subject.post{ raise ArgumentError }
|
302
265
|
sleep(0.1)
|
303
266
|
}.should_not raise_error
|
304
267
|
end
|
305
268
|
end
|
306
269
|
|
270
|
+
context 'dereference' do
|
271
|
+
|
272
|
+
it 'defaults :dup_on_deref to false' do
|
273
|
+
value = 'value'
|
274
|
+
value.should_not_receive(:dup).with(any_args())
|
275
|
+
|
276
|
+
agent = Agent.new(value)
|
277
|
+
agent.value.should eq 'value'
|
278
|
+
|
279
|
+
agent = Agent.new(value, dup_on_deref: false)
|
280
|
+
agent.value.should eq 'value'
|
281
|
+
|
282
|
+
agent = Agent.new(value, dup: false)
|
283
|
+
agent.value.should eq 'value'
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'calls #dup when the :dup_on_deref option is true' do
|
287
|
+
value = 'value'
|
288
|
+
|
289
|
+
agent = Agent.new(value, dup_on_deref: true)
|
290
|
+
agent.value.object_id.should_not eq value.object_id
|
291
|
+
agent.value.should eq 'value'
|
292
|
+
|
293
|
+
agent = Agent.new(value, dup: true)
|
294
|
+
agent.value.object_id.should_not eq value.object_id
|
295
|
+
agent.value.should eq 'value'
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'defaults :freeze_on_deref to false' do
|
299
|
+
value = 'value'
|
300
|
+
value.should_not_receive(:freeze).with(any_args())
|
301
|
+
|
302
|
+
agent = Agent.new(value)
|
303
|
+
agent.value.should eq 'value'
|
304
|
+
|
305
|
+
agent = Agent.new(value, freeze_on_deref: false)
|
306
|
+
agent.value.should eq 'value'
|
307
|
+
|
308
|
+
agent = Agent.new(value, freeze: false)
|
309
|
+
agent.value.should eq 'value'
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'calls #freeze when the :freeze_on_deref option is true' do
|
313
|
+
value = 'value'
|
314
|
+
|
315
|
+
agent = Agent.new(value, freeze_on_deref: true)
|
316
|
+
agent.value.should be_frozen
|
317
|
+
agent.value.should eq 'value'
|
318
|
+
|
319
|
+
agent = Agent.new(value, freeze: true)
|
320
|
+
agent.value.should be_frozen
|
321
|
+
agent.value.should eq 'value'
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'defaults :copy_on_deref to nil' do
|
325
|
+
value = 'value'
|
326
|
+
|
327
|
+
agent = Agent.new(value)
|
328
|
+
agent.value.object_id.should == value.object_id
|
329
|
+
agent.value.should eq 'value'
|
330
|
+
|
331
|
+
agent = Agent.new(value, copy_on_deref: nil)
|
332
|
+
agent.value.object_id.should == value.object_id
|
333
|
+
agent.value.should eq 'value'
|
334
|
+
|
335
|
+
agent = Agent.new(value, copy: nil)
|
336
|
+
agent.value.object_id.should == value.object_id
|
337
|
+
agent.value.should eq 'value'
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'calls the block when the :copy_on_deref option is passed a proc' do
|
341
|
+
value = 'value'
|
342
|
+
copy = proc{|val| 'copy' }
|
343
|
+
|
344
|
+
agent = Agent.new(value, copy_on_deref: copy)
|
345
|
+
agent.value.object_id.should_not == value.object_id
|
346
|
+
|
347
|
+
agent = Agent.new(value, copy: copy)
|
348
|
+
agent.value.object_id.should_not == value.object_id
|
349
|
+
end
|
350
|
+
|
351
|
+
it 'calls the :copy block first followed by #dup followed by #freeze' do
|
352
|
+
value = 'value'
|
353
|
+
copied = 'copied'
|
354
|
+
dup = 'dup'
|
355
|
+
frozen = 'frozen'
|
356
|
+
copy = proc{|val| copied }
|
357
|
+
|
358
|
+
copied.should_receive(:dup).with(no_args()).and_return(dup)
|
359
|
+
dup.should_receive(:freeze).with(no_args()).and_return(frozen)
|
360
|
+
|
361
|
+
agent = Agent.new(value, dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copy)
|
362
|
+
agent.value.should eq frozen
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
307
366
|
context 'observation' do
|
308
367
|
|
309
368
|
it 'notifies all observers when the value changes' do
|
@@ -386,5 +445,23 @@ module Concurrent
|
|
386
445
|
observer.value.should eq 10
|
387
446
|
end
|
388
447
|
end
|
448
|
+
|
449
|
+
context 'stress test' do
|
450
|
+
|
451
|
+
before(:each) do
|
452
|
+
Agent.thread_pool = FixedThreadPool.new(5)
|
453
|
+
end
|
454
|
+
|
455
|
+
specify do
|
456
|
+
count = 10_000
|
457
|
+
counter = Concurrent::Agent.new(0)
|
458
|
+
|
459
|
+
count.times do |i|
|
460
|
+
counter.post{|value| value + 1 }
|
461
|
+
end
|
462
|
+
|
463
|
+
sleep(0.1) until counter.value == count
|
464
|
+
end
|
465
|
+
end
|
389
466
|
end
|
390
467
|
end
|