concurrent-ruby 0.2.0 → 0.2.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.
Files changed (57) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +275 -275
  3. data/lib/concurrent.rb +28 -28
  4. data/lib/concurrent/agent.rb +114 -114
  5. data/lib/concurrent/cached_thread_pool.rb +131 -129
  6. data/lib/concurrent/defer.rb +65 -65
  7. data/lib/concurrent/event.rb +60 -60
  8. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  9. data/lib/concurrent/executor.rb +96 -95
  10. data/lib/concurrent/fixed_thread_pool.rb +99 -95
  11. data/lib/concurrent/functions.rb +120 -120
  12. data/lib/concurrent/future.rb +42 -42
  13. data/lib/concurrent/global_thread_pool.rb +16 -16
  14. data/lib/concurrent/goroutine.rb +29 -29
  15. data/lib/concurrent/null_thread_pool.rb +22 -22
  16. data/lib/concurrent/obligation.rb +67 -67
  17. data/lib/concurrent/promise.rb +174 -174
  18. data/lib/concurrent/reactor.rb +166 -166
  19. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  20. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  21. data/lib/concurrent/supervisor.rb +105 -100
  22. data/lib/concurrent/thread_pool.rb +76 -76
  23. data/lib/concurrent/utilities.rb +32 -32
  24. data/lib/concurrent/version.rb +3 -3
  25. data/lib/concurrent_ruby.rb +1 -1
  26. data/md/agent.md +123 -123
  27. data/md/defer.md +174 -174
  28. data/md/event.md +32 -32
  29. data/md/executor.md +187 -187
  30. data/md/future.md +83 -83
  31. data/md/goroutine.md +52 -52
  32. data/md/obligation.md +32 -32
  33. data/md/promise.md +227 -227
  34. data/md/thread_pool.md +224 -224
  35. data/spec/concurrent/agent_spec.rb +386 -386
  36. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  37. data/spec/concurrent/defer_spec.rb +195 -195
  38. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +200 -200
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  42. data/spec/concurrent/functions_spec.rb +217 -217
  43. data/spec/concurrent/future_spec.rb +108 -108
  44. data/spec/concurrent/global_thread_pool_spec.rb +38 -38
  45. data/spec/concurrent/goroutine_spec.rb +67 -67
  46. data/spec/concurrent/null_thread_pool_spec.rb +57 -54
  47. data/spec/concurrent/obligation_shared.rb +132 -132
  48. data/spec/concurrent/promise_spec.rb +312 -312
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  51. data/spec/concurrent/reactor_spec.rb +364 -364
  52. data/spec/concurrent/supervisor_spec.rb +269 -258
  53. data/spec/concurrent/thread_pool_shared.rb +204 -204
  54. data/spec/concurrent/utilities_spec.rb +74 -74
  55. data/spec/spec_helper.rb +32 -32
  56. metadata +20 -16
  57. checksums.yaml +0 -7
@@ -1,224 +1,224 @@
1
- # We're Going to Need a Bigger Boat
2
-
3
- Thread pools are neither a new idea nor an implementation of the actor pattern. Nevertheless, thread
4
- pools are still an extremely relevant concurrency tool. Every time a thread is created then
5
- subsequently destroyed there is overhead. Creating a pool of reusable worker threads then repeatedly'
6
- dipping into the pool can have huge performace benefits for a long-running application like a service.
7
- Ruby's blocks provide an excellent mechanism for passing a generic work request to a thread, making
8
- Ruby an excellent candidate language for thread pools.
9
-
10
- The inspiration for thread pools in this library is Java's `java.util.concurrent` implementation of
11
- [thread pools](java.util.concurrent). The `java.util.concurrent` library is a well-designed, stable,
12
- scalable, and battle-tested concurrency library. It provides three different implementations of thread
13
- pools. One of those implementations is simply a special case of the first and doesn't offer much
14
- advantage in Ruby, so only the first two (`FixedThreadPool` and `CachedThreadPool`) are implemented here.
15
-
16
- Thread pools share common `behavior` defined by `:thread_pool`. The most imortant method is `post`
17
- (aliased with the left-shift operator `<<`). The `post` method sends a block to the pool for future
18
- processing.
19
-
20
- A running thread pool can be shutdown in an orderly or disruptive manner. Once a thread pool has been
21
- shutdown in cannot be started again. The `shutdown` method can be used to initiate an orderly shutdown
22
- of the thread pool. All new `post` calls will reject the given block and immediately return `false`.
23
- Threads in the pool will continue to process all in-progress work and will process all tasks still in
24
- the queue. The `kill` method can be used to immediately shutdown the pool. All new `post` calls will
25
- reject the given block and immediately return `false`. Ruby's `Thread.kill` will be called on all threads
26
- in the pool, aborting all in-progress work. Tasks in the queue will be discarded.
27
-
28
- A client thread can choose to block and wait for pool shutdown to complete. This is useful when shutting
29
- down an application and ensuring the app doesn't exit before pool processing is complete. The method
30
- `wait_for_termination` will block for a maximum of the given number of seconds then return `true` if
31
- shutdown completed successfully or `false`. When the timeout value is `nil` the call will block
32
- indefinitely. Calling `wait_for_termination` on a stopped thread pool will immediately return `true`.
33
-
34
- Predicate methods are provided to describe the current state of the thread pool. Provided methods are
35
- `running?`, `shutdown?`, and `killed?`. The `shutdown` method will return true regardless of whether
36
- the pool was shutdown wil `shutdown` or `kill`.
37
-
38
- ## FixedThreadPool
39
-
40
- From the docs:
41
-
42
- > Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
43
- > At any point, at most `nThreads` threads will be active processing tasks. If additional tasks are submitted
44
- > when all threads are active, they will wait in the queue until a thread is available. If any thread terminates
45
- > due to a failure during execution prior to shutdown, a new one will take its place if needed to execute
46
- > subsequent tasks. The threads in the pool will exist until it is explicitly `shutdown`.
47
-
48
- ### Examples
49
-
50
- ```ruby
51
- require 'concurrent'
52
-
53
- pool = Concurrent::FixedThreadPool.new(5)
54
-
55
- pool.size #=> 5
56
- pool.running? #=> true
57
- pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
58
-
59
- pool.post(1,2,3){|*args| sleep(10) }
60
- pool << proc{ sleep(10) }
61
- pool.size #=> 5
62
-
63
- sleep(11)
64
- pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
65
-
66
- pool.shutdown #=> :shuttingdown
67
- pool.status #=> []
68
- pool.wait_for_termination
69
-
70
- pool.size #=> 0
71
- pool.status #=> []
72
- pool.shutdown? #=> true
73
- ```
74
-
75
- ## CachedThreadPool
76
-
77
- From the docs:
78
-
79
- > Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when
80
- > they are available. These pools will typically improve the performance of programs that execute many short-lived
81
- > asynchronous tasks. Calls to [`post`] will reuse previously constructed threads if available. If no existing
82
- > thread is available, a new thread will be created and added to the pool. Threads that have not been used for
83
- > sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will
84
- > not consume any resources. Note that pools with similar properties but different details (for example,
85
- > timeout parameters) may be created using [`CachedThreadPool`] constructors.
86
-
87
- ### Examples
88
-
89
- ```ruby
90
- require 'functional/cached_thread_pool'
91
- # or
92
- require 'functional/concurrency'
93
-
94
- pool = Concurrent::CachedThreadPool.new
95
-
96
- pool.size #=> 0
97
- pool.running? #=> true
98
- pool.status #=> []
99
-
100
- pool.post(1,2,3){|*args| sleep(10) }
101
- pool << proc{ sleep(10) }
102
- pool.size #=> 2
103
- pool.status #=> [[:working, nil, "sleep"], [:working, nil, "sleep"]]
104
-
105
- sleep(11)
106
- pool.status #=> [[:idle, 23, "sleep"], [:idle, 23, "sleep"]]
107
-
108
- sleep(60)
109
- pool.size #=> 0
110
- pool.status #=> []
111
-
112
- pool.shutdown #=> :shuttingdown
113
- pool.status #=> []
114
- pool.wait_for_termination
115
-
116
- pool.size #=> 0
117
- pool.status #=> []
118
- pool.shutdown? #=> true
119
- ```
120
-
121
- ## Global Thread Pool
122
-
123
- For efficiency, of the aforementioned concurrency methods (agents, futures, promises, and
124
- goroutines) run against a global thread pool. This pool can be directly accessed through the
125
- `$GLOBAL_THREAD_POOL` global variable. Generally, this pool should not be directly accessed.
126
- Use the other concurrency features instead.
127
-
128
- By default the global thread pool is a `CachedThreadPool`. This means it consumes no resources
129
- unless concurrency functions are called. Most of the time this pool can simply be left alone.
130
-
131
- ### Changing the Global Thread Pool
132
-
133
- It is possible to change the global thread pool. Simply assign a new pool to the `$GLOBAL_THREAD_POOL`
134
- variable:
135
-
136
- ```ruby
137
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
138
- ```
139
-
140
- Ideally this should be done at application startup, before any concurrency functions are called.
141
- If the circumstances warrant the global thread pool can be changed at runtime. Just make sure to
142
- shutdown the old global thread pool so that no tasks are lost:
143
-
144
- ```ruby
145
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
146
-
147
- # do stuff...
148
-
149
- old_global_pool = $GLOBAL_THREAD_POOL
150
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
151
- old_global_pool.shutdown
152
- ```
153
-
154
- ### NullThreadPool
155
-
156
- If for some reason an appliction would be better served by *not* having a global thread pool, the
157
- `NullThreadPool` is provided. The `NullThreadPool` is compatible with the global thread pool but
158
- it is not an actual thread pool. Instead it spawns a new thread on every call to the `post` method.
159
-
160
- ### EventMachine
161
-
162
- The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
163
- is an awesome library for creating evented applications. EventMachine provides its own thread pool
164
- and the authors recommend using their pool rather than using Ruby's `Thread`. No sweat,
165
- `functional-ruby` is fully compatible with EventMachine. Simple require `eventmachine`
166
- *before* requiring `functional-ruby` then replace the global thread pool with an instance
167
- of `EventMachineDeferProxy`:
168
-
169
- ```ruby
170
- require 'eventmachine' # do this FIRST
171
- require 'functional/concurrency'
172
-
173
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
174
- ```
175
-
176
- ## Per-class Thread Pools
177
-
178
- Many of the classes in this library use the global thread pool rather than creating new threads.
179
- Classes such as `Agent`, `Defer`, and others follow this pattern. There may be cases where a
180
- program would be better suited for one or more of these classes used a different thread pool.
181
- All classes that use the global thread pool support a class-level `thread_pool` attribute accessor.
182
- This property defaults to the global thread pool but can be changed at any time. Once changed, all
183
- new instances of that class will use the new thread pool.
184
-
185
- ```ruby
186
- Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> true
187
-
188
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10) #=> #<Concurrent::FixedThreadPool:0x007fe31130f1f0 ...
189
-
190
- Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> false
191
-
192
- Concurrent::Defer.thread_pool = Concurrent::CachedThreadPool.new #=> #<Concurrent::CachedThreadPool:0x007fef1c6b6b48 ...
193
- Concurrent::Defer.thread_pool == Concurrent::Agent.thread_pool #=> false
194
- Concurrent::Defer.thread_pool == $GLOBAL_THREAD_POOL #=> false
195
- ```
196
-
197
- ## Copyright
198
-
199
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
200
- It is free software and may be redistributed under the terms specified in the LICENSE file.
201
-
202
- ## License
203
-
204
- Released under the MIT license.
205
-
206
- http://www.opensource.org/licenses/mit-license.php
207
-
208
- > Permission is hereby granted, free of charge, to any person obtaining a copy
209
- > of this software and associated documentation files (the "Software"), to deal
210
- > in the Software without restriction, including without limitation the rights
211
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
212
- > copies of the Software, and to permit persons to whom the Software is
213
- > furnished to do so, subject to the following conditions:
214
- >
215
- > The above copyright notice and this permission notice shall be included in
216
- > all copies or substantial portions of the Software.
217
- >
218
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
219
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
220
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
221
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
222
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
223
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
224
- > THE SOFTWARE.
1
+ # We're Going to Need a Bigger Boat
2
+
3
+ Thread pools are neither a new idea nor an implementation of the actor pattern. Nevertheless, thread
4
+ pools are still an extremely relevant concurrency tool. Every time a thread is created then
5
+ subsequently destroyed there is overhead. Creating a pool of reusable worker threads then repeatedly'
6
+ dipping into the pool can have huge performace benefits for a long-running application like a service.
7
+ Ruby's blocks provide an excellent mechanism for passing a generic work request to a thread, making
8
+ Ruby an excellent candidate language for thread pools.
9
+
10
+ The inspiration for thread pools in this library is Java's `java.util.concurrent` implementation of
11
+ [thread pools](java.util.concurrent). The `java.util.concurrent` library is a well-designed, stable,
12
+ scalable, and battle-tested concurrency library. It provides three different implementations of thread
13
+ pools. One of those implementations is simply a special case of the first and doesn't offer much
14
+ advantage in Ruby, so only the first two (`FixedThreadPool` and `CachedThreadPool`) are implemented here.
15
+
16
+ Thread pools share common `behavior` defined by `:thread_pool`. The most imortant method is `post`
17
+ (aliased with the left-shift operator `<<`). The `post` method sends a block to the pool for future
18
+ processing.
19
+
20
+ A running thread pool can be shutdown in an orderly or disruptive manner. Once a thread pool has been
21
+ shutdown in cannot be started again. The `shutdown` method can be used to initiate an orderly shutdown
22
+ of the thread pool. All new `post` calls will reject the given block and immediately return `false`.
23
+ Threads in the pool will continue to process all in-progress work and will process all tasks still in
24
+ the queue. The `kill` method can be used to immediately shutdown the pool. All new `post` calls will
25
+ reject the given block and immediately return `false`. Ruby's `Thread.kill` will be called on all threads
26
+ in the pool, aborting all in-progress work. Tasks in the queue will be discarded.
27
+
28
+ A client thread can choose to block and wait for pool shutdown to complete. This is useful when shutting
29
+ down an application and ensuring the app doesn't exit before pool processing is complete. The method
30
+ `wait_for_termination` will block for a maximum of the given number of seconds then return `true` if
31
+ shutdown completed successfully or `false`. When the timeout value is `nil` the call will block
32
+ indefinitely. Calling `wait_for_termination` on a stopped thread pool will immediately return `true`.
33
+
34
+ Predicate methods are provided to describe the current state of the thread pool. Provided methods are
35
+ `running?`, `shutdown?`, and `killed?`. The `shutdown` method will return true regardless of whether
36
+ the pool was shutdown wil `shutdown` or `kill`.
37
+
38
+ ## FixedThreadPool
39
+
40
+ From the docs:
41
+
42
+ > Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
43
+ > At any point, at most `nThreads` threads will be active processing tasks. If additional tasks are submitted
44
+ > when all threads are active, they will wait in the queue until a thread is available. If any thread terminates
45
+ > due to a failure during execution prior to shutdown, a new one will take its place if needed to execute
46
+ > subsequent tasks. The threads in the pool will exist until it is explicitly `shutdown`.
47
+
48
+ ### Examples
49
+
50
+ ```ruby
51
+ require 'concurrent'
52
+
53
+ pool = Concurrent::FixedThreadPool.new(5)
54
+
55
+ pool.size #=> 5
56
+ pool.running? #=> true
57
+ pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
58
+
59
+ pool.post(1,2,3){|*args| sleep(10) }
60
+ pool << proc{ sleep(10) }
61
+ pool.size #=> 5
62
+
63
+ sleep(11)
64
+ pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
65
+
66
+ pool.shutdown #=> :shuttingdown
67
+ pool.status #=> []
68
+ pool.wait_for_termination
69
+
70
+ pool.size #=> 0
71
+ pool.status #=> []
72
+ pool.shutdown? #=> true
73
+ ```
74
+
75
+ ## CachedThreadPool
76
+
77
+ From the docs:
78
+
79
+ > Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when
80
+ > they are available. These pools will typically improve the performance of programs that execute many short-lived
81
+ > asynchronous tasks. Calls to [`post`] will reuse previously constructed threads if available. If no existing
82
+ > thread is available, a new thread will be created and added to the pool. Threads that have not been used for
83
+ > sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will
84
+ > not consume any resources. Note that pools with similar properties but different details (for example,
85
+ > timeout parameters) may be created using [`CachedThreadPool`] constructors.
86
+
87
+ ### Examples
88
+
89
+ ```ruby
90
+ require 'functional/cached_thread_pool'
91
+ # or
92
+ require 'functional/concurrency'
93
+
94
+ pool = Concurrent::CachedThreadPool.new
95
+
96
+ pool.size #=> 0
97
+ pool.running? #=> true
98
+ pool.status #=> []
99
+
100
+ pool.post(1,2,3){|*args| sleep(10) }
101
+ pool << proc{ sleep(10) }
102
+ pool.size #=> 2
103
+ pool.status #=> [[:working, nil, "sleep"], [:working, nil, "sleep"]]
104
+
105
+ sleep(11)
106
+ pool.status #=> [[:idle, 23, "sleep"], [:idle, 23, "sleep"]]
107
+
108
+ sleep(60)
109
+ pool.size #=> 0
110
+ pool.status #=> []
111
+
112
+ pool.shutdown #=> :shuttingdown
113
+ pool.status #=> []
114
+ pool.wait_for_termination
115
+
116
+ pool.size #=> 0
117
+ pool.status #=> []
118
+ pool.shutdown? #=> true
119
+ ```
120
+
121
+ ## Global Thread Pool
122
+
123
+ For efficiency, of the aforementioned concurrency methods (agents, futures, promises, and
124
+ goroutines) run against a global thread pool. This pool can be directly accessed through the
125
+ `$GLOBAL_THREAD_POOL` global variable. Generally, this pool should not be directly accessed.
126
+ Use the other concurrency features instead.
127
+
128
+ By default the global thread pool is a `CachedThreadPool`. This means it consumes no resources
129
+ unless concurrency functions are called. Most of the time this pool can simply be left alone.
130
+
131
+ ### Changing the Global Thread Pool
132
+
133
+ It is possible to change the global thread pool. Simply assign a new pool to the `$GLOBAL_THREAD_POOL`
134
+ variable:
135
+
136
+ ```ruby
137
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
138
+ ```
139
+
140
+ Ideally this should be done at application startup, before any concurrency functions are called.
141
+ If the circumstances warrant the global thread pool can be changed at runtime. Just make sure to
142
+ shutdown the old global thread pool so that no tasks are lost:
143
+
144
+ ```ruby
145
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
146
+
147
+ # do stuff...
148
+
149
+ old_global_pool = $GLOBAL_THREAD_POOL
150
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
151
+ old_global_pool.shutdown
152
+ ```
153
+
154
+ ### NullThreadPool
155
+
156
+ If for some reason an appliction would be better served by *not* having a global thread pool, the
157
+ `NullThreadPool` is provided. The `NullThreadPool` is compatible with the global thread pool but
158
+ it is not an actual thread pool. Instead it spawns a new thread on every call to the `post` method.
159
+
160
+ ### EventMachine
161
+
162
+ The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
163
+ is an awesome library for creating evented applications. EventMachine provides its own thread pool
164
+ and the authors recommend using their pool rather than using Ruby's `Thread`. No sweat,
165
+ `functional-ruby` is fully compatible with EventMachine. Simple require `eventmachine`
166
+ *before* requiring `functional-ruby` then replace the global thread pool with an instance
167
+ of `EventMachineDeferProxy`:
168
+
169
+ ```ruby
170
+ require 'eventmachine' # do this FIRST
171
+ require 'functional/concurrency'
172
+
173
+ $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
174
+ ```
175
+
176
+ ## Per-class Thread Pools
177
+
178
+ Many of the classes in this library use the global thread pool rather than creating new threads.
179
+ Classes such as `Agent`, `Defer`, and others follow this pattern. There may be cases where a
180
+ program would be better suited for one or more of these classes used a different thread pool.
181
+ All classes that use the global thread pool support a class-level `thread_pool` attribute accessor.
182
+ This property defaults to the global thread pool but can be changed at any time. Once changed, all
183
+ new instances of that class will use the new thread pool.
184
+
185
+ ```ruby
186
+ Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> true
187
+
188
+ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10) #=> #<Concurrent::FixedThreadPool:0x007fe31130f1f0 ...
189
+
190
+ Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> false
191
+
192
+ Concurrent::Defer.thread_pool = Concurrent::CachedThreadPool.new #=> #<Concurrent::CachedThreadPool:0x007fef1c6b6b48 ...
193
+ Concurrent::Defer.thread_pool == Concurrent::Agent.thread_pool #=> false
194
+ Concurrent::Defer.thread_pool == $GLOBAL_THREAD_POOL #=> false
195
+ ```
196
+
197
+ ## Copyright
198
+
199
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
200
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
201
+
202
+ ## License
203
+
204
+ Released under the MIT license.
205
+
206
+ http://www.opensource.org/licenses/mit-license.php
207
+
208
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
209
+ > of this software and associated documentation files (the "Software"), to deal
210
+ > in the Software without restriction, including without limitation the rights
211
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
212
+ > copies of the Software, and to permit persons to whom the Software is
213
+ > furnished to do so, subject to the following conditions:
214
+ >
215
+ > The above copyright notice and this permission notice shall be included in
216
+ > all copies or substantial portions of the Software.
217
+ >
218
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
219
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
220
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
221
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
222
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
223
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
224
+ > THE SOFTWARE.
@@ -1,386 +1,386 @@
1
- require 'spec_helper'
2
-
3
- module Concurrent
4
-
5
- describe Agent do
6
-
7
- subject { Agent.new(0) }
8
-
9
- let(:observer) do
10
- Class.new do
11
- attr_reader :value
12
- define_method(:update) do |time, value|
13
- @value = value
14
- end
15
- end.new
16
- end
17
-
18
- before(:each) do
19
- Agent.thread_pool = FixedThreadPool.new(1)
20
- end
21
-
22
- context '#initialize' do
23
-
24
- it 'sets the value to the given initial state' do
25
- Agent.new(10).value.should eq 10
26
- end
27
-
28
- it 'sets the timeout to the given value' do
29
- Agent.new(0, 5).timeout.should eq 5
30
- end
31
-
32
- it 'sets the timeout to the default when nil' do
33
- Agent.new(0).timeout.should eq Agent::TIMEOUT
34
- end
35
-
36
- it 'sets the length to zero' do
37
- Agent.new(10).length.should eq 0
38
- end
39
-
40
- it 'spawns the worker thread' do
41
- Agent.thread_pool.should_receive(:post).once.with(any_args())
42
- Agent.new(0)
43
- end
44
- end
45
-
46
- context '#rescue' do
47
-
48
- it 'returns self when a block is given' do
49
- a1 = subject
50
- a2 = a1.rescue{}
51
- a1.object_id.should eq a2.object_id
52
- end
53
-
54
- it 'returns self when no block is given' do
55
- a1 = subject
56
- a2 = a1.rescue
57
- a1.object_id.should eq a2.object_id
58
- end
59
-
60
- it 'accepts an exception class as the first parameter' do
61
- lambda {
62
- subject.rescue(StandardError){}
63
- }.should_not raise_error
64
- end
65
-
66
- it 'ignores rescuers without a block' do
67
- subject.rescue
68
- subject.instance_variable_get(:@rescuers).should be_empty
69
- end
70
- end
71
-
72
- context '#validate' do
73
-
74
- it 'returns self when a block is given' do
75
- a1 = subject
76
- a2 = a1.validate{}
77
- a1.object_id.should eq a2.object_id
78
- end
79
-
80
- it 'returns self when no block is given' do
81
- a1 = subject
82
- a2 = a1.validate
83
- a1.object_id.should eq a2.object_id
84
- end
85
-
86
- it 'ignores validators without a block' do
87
- subject.validate
88
- subject.instance_variable_get(:@validator).should be_nil
89
- end
90
- end
91
-
92
- context '#post' do
93
-
94
- it 'adds the given block to the queue' do
95
- subject.post{ sleep(100) }
96
- sleep(0.1)
97
- before = subject.length
98
- subject.post{ nil }
99
- subject.post{ nil }
100
- subject.length.should eq before+2
101
- end
102
-
103
- it 'does not add to the queue when no block is given' do
104
- subject.post{ sleep(100) }
105
- sleep(0.1)
106
- before = subject.length
107
- subject.post
108
- subject.post{ nil }
109
- subject.length.should eq before+1
110
- end
111
- end
112
-
113
- context '#length' do
114
-
115
- it 'should be zero for a new agent' do
116
- subject.length.should eq 0
117
- end
118
-
119
- it 'should increase by one for each #post' do
120
- subject.post{ sleep(100) }
121
- sleep(0.1)
122
- subject.post{ sleep }
123
- subject.post{ sleep }
124
- subject.post{ sleep }
125
- subject.length.should eq 3
126
- end
127
-
128
- it 'should decrease by one each time a handler is run' do
129
- subject.post{ nil }
130
- subject.post{ sleep }
131
- subject.post{ sleep }
132
- sleep(0.1)
133
- subject.length.should eq 1
134
- end
135
- end
136
-
137
- context 'fulfillment' do
138
-
139
- it 'process each block in the queue' do
140
- @expected = []
141
- subject.post{ @expected << 1 }
142
- subject.post{ @expected << 2 }
143
- subject.post{ @expected << 3 }
144
- sleep(0.1)
145
- @expected.should eq [1,2,3]
146
- end
147
-
148
- it 'passes the current value to the handler' do
149
- @expected = nil
150
- Agent.new(10).post{|i| @expected = i }
151
- sleep(0.1)
152
- @expected.should eq 10
153
- end
154
-
155
- it 'sets the value to the handler return value on success' do
156
- subject.post{ 100 }
157
- sleep(0.1)
158
- subject.value.should eq 100
159
- end
160
-
161
- it 'rejects the handler after timeout reached' do
162
- agent = Agent.new(0, 0.1)
163
- agent.post{ sleep(1); 10 }
164
- agent.value.should eq 0
165
- end
166
- end
167
-
168
- context 'validation' do
169
-
170
- it 'processes the validator when present' do
171
- @expected = nil
172
- subject.validate{ @expected = 10; true }
173
- subject.post{ nil }
174
- sleep(0.1)
175
- @expected.should eq 10
176
- end
177
-
178
- it 'passes the new value to the validator' do
179
- @expected = nil
180
- subject.validate{|v| @expected = v; true }
181
- subject.post{ 10 }
182
- sleep(0.1)
183
- @expected.should eq 10
184
- end
185
-
186
- it 'sets the new value when the validator returns true' do
187
- agent = Agent.new(0).validate{ true }
188
- agent.post{ 10 }
189
- sleep(0.1)
190
- agent.value.should eq 10
191
- end
192
-
193
- it 'does not change the value when the validator returns false' do
194
- agent = Agent.new(0).validate{ false }
195
- agent.post{ 10 }
196
- sleep(0.1)
197
- agent.value.should eq 0
198
- end
199
-
200
- it 'does not change the value when the validator raises an exception' do
201
- agent = Agent.new(0).validate{ raise StandardError }
202
- agent.post{ 10 }
203
- sleep(0.1)
204
- agent.value.should eq 0
205
- end
206
- end
207
-
208
- context 'rejection' do
209
-
210
- it 'calls the first exception block with a matching class' do
211
- @expected = nil
212
- subject.
213
- rescue(StandardError){|ex| @expected = 1 }.
214
- rescue(StandardError){|ex| @expected = 2 }.
215
- rescue(StandardError){|ex| @expected = 3 }
216
- subject.post{ raise StandardError }
217
- sleep(0.1)
218
- @expected.should eq 1
219
- end
220
-
221
- it 'matches all with a rescue with no class given' do
222
- @expected = nil
223
- subject.
224
- rescue(LoadError){|ex| @expected = 1 }.
225
- rescue{|ex| @expected = 2 }.
226
- rescue(StandardError){|ex| @expected = 3 }
227
- subject.post{ raise NoMethodError }
228
- sleep(0.1)
229
- @expected.should eq 2
230
- end
231
-
232
- it 'searches associated rescue handlers in order' do
233
- @expected = nil
234
- subject.
235
- rescue(ArgumentError){|ex| @expected = 1 }.
236
- rescue(LoadError){|ex| @expected = 2 }.
237
- rescue(Exception){|ex| @expected = 3 }
238
- subject.post{ raise ArgumentError }
239
- sleep(0.1)
240
- @expected.should eq 1
241
-
242
- @expected = nil
243
- subject.
244
- rescue(ArgumentError){|ex| @expected = 1 }.
245
- rescue(LoadError){|ex| @expected = 2 }.
246
- rescue(Exception){|ex| @expected = 3 }
247
- subject.post{ raise LoadError }
248
- sleep(0.1)
249
- @expected.should eq 2
250
-
251
- @expected = nil
252
- subject.
253
- rescue(ArgumentError){|ex| @expected = 1 }.
254
- rescue(LoadError){|ex| @expected = 2 }.
255
- rescue(Exception){|ex| @expected = 3 }
256
- subject.post{ raise StandardError }
257
- sleep(0.1)
258
- @expected.should eq 3
259
- end
260
-
261
- it 'passes the exception object to the matched block' do
262
- @expected = nil
263
- subject.
264
- rescue(ArgumentError){|ex| @expected = ex }.
265
- rescue(LoadError){|ex| @expected = ex }.
266
- rescue(Exception){|ex| @expected = ex }
267
- subject.post{ raise StandardError }
268
- sleep(0.1)
269
- @expected.should be_a(StandardError)
270
- end
271
-
272
- it 'ignores rescuers without a block' do
273
- @expected = nil
274
- subject.
275
- rescue(StandardError).
276
- rescue(StandardError){|ex| @expected = ex }.
277
- rescue(Exception){|ex| @expected = ex }
278
- subject.post{ raise StandardError }
279
- sleep(0.1)
280
- @expected.should be_a(StandardError)
281
- end
282
-
283
- it 'supresses the exception if no rescue matches' do
284
- lambda {
285
- subject.
286
- rescue(ArgumentError){|ex| @expected = ex }.
287
- rescue(StandardError){|ex| @expected = ex }.
288
- rescue(Exception){|ex| @expected = ex }
289
- subject.post{ raise StandardError }
290
- sleep(0.1)
291
- }.should_not raise_error
292
- end
293
-
294
- it 'supresses exceptions thrown from rescue handlers' do
295
- lambda {
296
- subject.rescue(Exception){ raise StandardError }
297
- subject.post{ raise ArgumentError }
298
- sleep(0.1)
299
- }.should_not raise_error
300
- end
301
- end
302
-
303
- context 'observation' do
304
-
305
- it 'notifies all observers when the value changes' do
306
- agent = Agent.new(0)
307
- agent.add_observer(observer)
308
- agent.post{ 10 }
309
- sleep(0.1)
310
- observer.value.should eq 10
311
- end
312
-
313
- it 'does not notify observers when validation fails' do
314
- agent = Agent.new(0)
315
- agent.validate{ false }
316
- agent.add_observer(observer)
317
- agent.post{ 10 }
318
- sleep(0.1)
319
- observer.value.should be_nil
320
- end
321
-
322
- it 'does not notify observers when the handler raises an exception' do
323
- agent = Agent.new(0)
324
- agent.add_observer(observer)
325
- agent.post{ raise StandardError }
326
- sleep(0.1)
327
- observer.value.should be_nil
328
- end
329
- end
330
-
331
- context 'aliases' do
332
-
333
- it 'aliases #deref for #value' do
334
- Agent.new(10).deref.should eq 10
335
- end
336
-
337
- it 'aliases #validates for :validate' do
338
- @expected = nil
339
- subject.validates{|v| @expected = v }
340
- subject.post{ 10 }
341
- sleep(0.1)
342
- @expected.should eq 10
343
- end
344
-
345
- it 'aliases #validate_with for :validate' do
346
- @expected = nil
347
- subject.validate_with{|v| @expected = v }
348
- subject.post{ 10 }
349
- sleep(0.1)
350
- @expected.should eq 10
351
- end
352
-
353
- it 'aliases #validates_with for :validate' do
354
- @expected = nil
355
- subject.validates_with{|v| @expected = v }
356
- subject.post{ 10 }
357
- sleep(0.1)
358
- @expected.should eq 10
359
- end
360
-
361
- it 'aliases #catch for #rescue' do
362
- @expected = nil
363
- subject.catch{ @expected = true }
364
- subject.post{ raise StandardError }
365
- sleep(0.1)
366
- @expected.should be_true
367
- end
368
-
369
- it 'aliases #on_error for #rescue' do
370
- @expected = nil
371
- subject.on_error{ @expected = true }
372
- subject.post{ raise StandardError }
373
- sleep(0.1)
374
- @expected.should be_true
375
- end
376
-
377
- it 'aliases #add_watch for #add_observer' do
378
- agent = Agent.new(0)
379
- agent.add_watch(observer)
380
- agent.post{ 10 }
381
- sleep(0.1)
382
- observer.value.should eq 10
383
- end
384
- end
385
- end
386
- end
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe Agent do
6
+
7
+ subject { Agent.new(0) }
8
+
9
+ let(:observer) do
10
+ Class.new do
11
+ attr_reader :value
12
+ define_method(:update) do |time, value|
13
+ @value = value
14
+ end
15
+ end.new
16
+ end
17
+
18
+ before(:each) do
19
+ Agent.thread_pool = FixedThreadPool.new(1)
20
+ end
21
+
22
+ context '#initialize' do
23
+
24
+ it 'sets the value to the given initial state' do
25
+ Agent.new(10).value.should eq 10
26
+ end
27
+
28
+ it 'sets the timeout to the given value' do
29
+ Agent.new(0, 5).timeout.should eq 5
30
+ end
31
+
32
+ it 'sets the timeout to the default when nil' do
33
+ Agent.new(0).timeout.should eq Agent::TIMEOUT
34
+ end
35
+
36
+ it 'sets the length to zero' do
37
+ Agent.new(10).length.should eq 0
38
+ end
39
+
40
+ it 'spawns the worker thread' do
41
+ Agent.thread_pool.should_receive(:post).once.with(any_args())
42
+ Agent.new(0)
43
+ end
44
+ end
45
+
46
+ context '#rescue' do
47
+
48
+ it 'returns self when a block is given' do
49
+ a1 = subject
50
+ a2 = a1.rescue{}
51
+ a1.object_id.should eq a2.object_id
52
+ end
53
+
54
+ it 'returns self when no block is given' do
55
+ a1 = subject
56
+ a2 = a1.rescue
57
+ a1.object_id.should eq a2.object_id
58
+ end
59
+
60
+ it 'accepts an exception class as the first parameter' do
61
+ lambda {
62
+ subject.rescue(StandardError){}
63
+ }.should_not raise_error
64
+ end
65
+
66
+ it 'ignores rescuers without a block' do
67
+ subject.rescue
68
+ subject.instance_variable_get(:@rescuers).should be_empty
69
+ end
70
+ end
71
+
72
+ context '#validate' do
73
+
74
+ it 'returns self when a block is given' do
75
+ a1 = subject
76
+ a2 = a1.validate{}
77
+ a1.object_id.should eq a2.object_id
78
+ end
79
+
80
+ it 'returns self when no block is given' do
81
+ a1 = subject
82
+ a2 = a1.validate
83
+ a1.object_id.should eq a2.object_id
84
+ end
85
+
86
+ it 'ignores validators without a block' do
87
+ subject.validate
88
+ subject.instance_variable_get(:@validator).should be_nil
89
+ end
90
+ end
91
+
92
+ context '#post' do
93
+
94
+ it 'adds the given block to the queue' do
95
+ subject.post{ sleep(100) }
96
+ sleep(0.1)
97
+ before = subject.length
98
+ subject.post{ nil }
99
+ subject.post{ nil }
100
+ subject.length.should eq before+2
101
+ end
102
+
103
+ it 'does not add to the queue when no block is given' do
104
+ subject.post{ sleep(100) }
105
+ sleep(0.1)
106
+ before = subject.length
107
+ subject.post
108
+ subject.post{ nil }
109
+ subject.length.should eq before+1
110
+ end
111
+ end
112
+
113
+ context '#length' do
114
+
115
+ it 'should be zero for a new agent' do
116
+ subject.length.should eq 0
117
+ end
118
+
119
+ it 'should increase by one for each #post' do
120
+ subject.post{ sleep(100) }
121
+ sleep(0.1)
122
+ subject.post{ sleep }
123
+ subject.post{ sleep }
124
+ subject.post{ sleep }
125
+ subject.length.should eq 3
126
+ end
127
+
128
+ it 'should decrease by one each time a handler is run' do
129
+ subject.post{ nil }
130
+ subject.post{ sleep }
131
+ subject.post{ sleep }
132
+ sleep(0.1)
133
+ subject.length.should eq 1
134
+ end
135
+ end
136
+
137
+ context 'fulfillment' do
138
+
139
+ it 'process each block in the queue' do
140
+ @expected = []
141
+ subject.post{ @expected << 1 }
142
+ subject.post{ @expected << 2 }
143
+ subject.post{ @expected << 3 }
144
+ sleep(0.1)
145
+ @expected.should eq [1,2,3]
146
+ end
147
+
148
+ it 'passes the current value to the handler' do
149
+ @expected = nil
150
+ Agent.new(10).post{|i| @expected = i }
151
+ sleep(0.1)
152
+ @expected.should eq 10
153
+ end
154
+
155
+ it 'sets the value to the handler return value on success' do
156
+ subject.post{ 100 }
157
+ sleep(0.1)
158
+ subject.value.should eq 100
159
+ end
160
+
161
+ it 'rejects the handler after timeout reached' do
162
+ agent = Agent.new(0, 0.1)
163
+ agent.post{ sleep(1); 10 }
164
+ agent.value.should eq 0
165
+ end
166
+ end
167
+
168
+ context 'validation' do
169
+
170
+ it 'processes the validator when present' do
171
+ @expected = nil
172
+ subject.validate{ @expected = 10; true }
173
+ subject.post{ nil }
174
+ sleep(0.1)
175
+ @expected.should eq 10
176
+ end
177
+
178
+ it 'passes the new value to the validator' do
179
+ @expected = nil
180
+ subject.validate{|v| @expected = v; true }
181
+ subject.post{ 10 }
182
+ sleep(0.1)
183
+ @expected.should eq 10
184
+ end
185
+
186
+ it 'sets the new value when the validator returns true' do
187
+ agent = Agent.new(0).validate{ true }
188
+ agent.post{ 10 }
189
+ sleep(0.1)
190
+ agent.value.should eq 10
191
+ end
192
+
193
+ it 'does not change the value when the validator returns false' do
194
+ agent = Agent.new(0).validate{ false }
195
+ agent.post{ 10 }
196
+ sleep(0.1)
197
+ agent.value.should eq 0
198
+ end
199
+
200
+ it 'does not change the value when the validator raises an exception' do
201
+ agent = Agent.new(0).validate{ raise StandardError }
202
+ agent.post{ 10 }
203
+ sleep(0.1)
204
+ agent.value.should eq 0
205
+ end
206
+ end
207
+
208
+ context 'rejection' do
209
+
210
+ it 'calls the first exception block with a matching class' do
211
+ @expected = nil
212
+ subject.
213
+ rescue(StandardError){|ex| @expected = 1 }.
214
+ rescue(StandardError){|ex| @expected = 2 }.
215
+ rescue(StandardError){|ex| @expected = 3 }
216
+ subject.post{ raise StandardError }
217
+ sleep(0.1)
218
+ @expected.should eq 1
219
+ end
220
+
221
+ it 'matches all with a rescue with no class given' do
222
+ @expected = nil
223
+ subject.
224
+ rescue(LoadError){|ex| @expected = 1 }.
225
+ rescue{|ex| @expected = 2 }.
226
+ rescue(StandardError){|ex| @expected = 3 }
227
+ subject.post{ raise NoMethodError }
228
+ sleep(0.1)
229
+ @expected.should eq 2
230
+ end
231
+
232
+ it 'searches associated rescue handlers in order' do
233
+ @expected = nil
234
+ subject.
235
+ rescue(ArgumentError){|ex| @expected = 1 }.
236
+ rescue(LoadError){|ex| @expected = 2 }.
237
+ rescue(Exception){|ex| @expected = 3 }
238
+ subject.post{ raise ArgumentError }
239
+ sleep(0.1)
240
+ @expected.should eq 1
241
+
242
+ @expected = nil
243
+ subject.
244
+ rescue(ArgumentError){|ex| @expected = 1 }.
245
+ rescue(LoadError){|ex| @expected = 2 }.
246
+ rescue(Exception){|ex| @expected = 3 }
247
+ subject.post{ raise LoadError }
248
+ sleep(0.1)
249
+ @expected.should eq 2
250
+
251
+ @expected = nil
252
+ subject.
253
+ rescue(ArgumentError){|ex| @expected = 1 }.
254
+ rescue(LoadError){|ex| @expected = 2 }.
255
+ rescue(Exception){|ex| @expected = 3 }
256
+ subject.post{ raise StandardError }
257
+ sleep(0.1)
258
+ @expected.should eq 3
259
+ end
260
+
261
+ it 'passes the exception object to the matched block' do
262
+ @expected = nil
263
+ subject.
264
+ rescue(ArgumentError){|ex| @expected = ex }.
265
+ rescue(LoadError){|ex| @expected = ex }.
266
+ rescue(Exception){|ex| @expected = ex }
267
+ subject.post{ raise StandardError }
268
+ sleep(0.1)
269
+ @expected.should be_a(StandardError)
270
+ end
271
+
272
+ it 'ignores rescuers without a block' do
273
+ @expected = nil
274
+ subject.
275
+ rescue(StandardError).
276
+ rescue(StandardError){|ex| @expected = ex }.
277
+ rescue(Exception){|ex| @expected = ex }
278
+ subject.post{ raise StandardError }
279
+ sleep(0.1)
280
+ @expected.should be_a(StandardError)
281
+ end
282
+
283
+ it 'supresses the exception if no rescue matches' do
284
+ lambda {
285
+ subject.
286
+ rescue(ArgumentError){|ex| @expected = ex }.
287
+ rescue(StandardError){|ex| @expected = ex }.
288
+ rescue(Exception){|ex| @expected = ex }
289
+ subject.post{ raise StandardError }
290
+ sleep(0.1)
291
+ }.should_not raise_error
292
+ end
293
+
294
+ it 'supresses exceptions thrown from rescue handlers' do
295
+ lambda {
296
+ subject.rescue(Exception){ raise StandardError }
297
+ subject.post{ raise ArgumentError }
298
+ sleep(0.1)
299
+ }.should_not raise_error
300
+ end
301
+ end
302
+
303
+ context 'observation' do
304
+
305
+ it 'notifies all observers when the value changes' do
306
+ agent = Agent.new(0)
307
+ agent.add_observer(observer)
308
+ agent.post{ 10 }
309
+ sleep(0.1)
310
+ observer.value.should eq 10
311
+ end
312
+
313
+ it 'does not notify observers when validation fails' do
314
+ agent = Agent.new(0)
315
+ agent.validate{ false }
316
+ agent.add_observer(observer)
317
+ agent.post{ 10 }
318
+ sleep(0.1)
319
+ observer.value.should be_nil
320
+ end
321
+
322
+ it 'does not notify observers when the handler raises an exception' do
323
+ agent = Agent.new(0)
324
+ agent.add_observer(observer)
325
+ agent.post{ raise StandardError }
326
+ sleep(0.1)
327
+ observer.value.should be_nil
328
+ end
329
+ end
330
+
331
+ context 'aliases' do
332
+
333
+ it 'aliases #deref for #value' do
334
+ Agent.new(10).deref.should eq 10
335
+ end
336
+
337
+ it 'aliases #validates for :validate' do
338
+ @expected = nil
339
+ subject.validates{|v| @expected = v }
340
+ subject.post{ 10 }
341
+ sleep(0.1)
342
+ @expected.should eq 10
343
+ end
344
+
345
+ it 'aliases #validate_with for :validate' do
346
+ @expected = nil
347
+ subject.validate_with{|v| @expected = v }
348
+ subject.post{ 10 }
349
+ sleep(0.1)
350
+ @expected.should eq 10
351
+ end
352
+
353
+ it 'aliases #validates_with for :validate' do
354
+ @expected = nil
355
+ subject.validates_with{|v| @expected = v }
356
+ subject.post{ 10 }
357
+ sleep(0.1)
358
+ @expected.should eq 10
359
+ end
360
+
361
+ it 'aliases #catch for #rescue' do
362
+ @expected = nil
363
+ subject.catch{ @expected = true }
364
+ subject.post{ raise StandardError }
365
+ sleep(0.1)
366
+ @expected.should be_true
367
+ end
368
+
369
+ it 'aliases #on_error for #rescue' do
370
+ @expected = nil
371
+ subject.on_error{ @expected = true }
372
+ subject.post{ raise StandardError }
373
+ sleep(0.1)
374
+ @expected.should be_true
375
+ end
376
+
377
+ it 'aliases #add_watch for #add_observer' do
378
+ agent = Agent.new(0)
379
+ agent.add_watch(observer)
380
+ agent.post{ 10 }
381
+ sleep(0.1)
382
+ observer.value.should eq 10
383
+ end
384
+ end
385
+ end
386
+ end