concurrent-ruby 0.2.2 → 0.3.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 +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
|