concurrent-ruby 0.2.1 → 0.2.2

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -21
  3. data/README.md +276 -275
  4. data/lib/concurrent.rb +28 -28
  5. data/lib/concurrent/agent.rb +114 -114
  6. data/lib/concurrent/cached_thread_pool.rb +131 -131
  7. data/lib/concurrent/defer.rb +65 -65
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +96 -96
  11. data/lib/concurrent/fixed_thread_pool.rb +99 -99
  12. data/lib/concurrent/functions.rb +120 -120
  13. data/lib/concurrent/future.rb +42 -42
  14. data/lib/concurrent/global_thread_pool.rb +24 -16
  15. data/lib/concurrent/goroutine.rb +29 -29
  16. data/lib/concurrent/null_thread_pool.rb +22 -22
  17. data/lib/concurrent/obligation.rb +67 -67
  18. data/lib/concurrent/promise.rb +174 -174
  19. data/lib/concurrent/reactor.rb +166 -166
  20. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  21. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  22. data/lib/concurrent/supervisor.rb +105 -105
  23. data/lib/concurrent/thread_pool.rb +76 -76
  24. data/lib/concurrent/utilities.rb +32 -32
  25. data/lib/concurrent/version.rb +3 -3
  26. data/lib/concurrent_ruby.rb +1 -1
  27. data/md/agent.md +123 -123
  28. data/md/defer.md +174 -174
  29. data/md/event.md +32 -32
  30. data/md/executor.md +187 -187
  31. data/md/future.md +83 -83
  32. data/md/goroutine.md +52 -52
  33. data/md/obligation.md +32 -32
  34. data/md/promise.md +227 -227
  35. data/md/thread_pool.md +224 -224
  36. data/spec/concurrent/agent_spec.rb +390 -386
  37. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  38. data/spec/concurrent/defer_spec.rb +199 -195
  39. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  40. data/spec/concurrent/event_spec.rb +134 -134
  41. data/spec/concurrent/executor_spec.rb +200 -200
  42. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  43. data/spec/concurrent/functions_spec.rb +217 -217
  44. data/spec/concurrent/future_spec.rb +112 -108
  45. data/spec/concurrent/global_thread_pool_spec.rb +11 -38
  46. data/spec/concurrent/goroutine_spec.rb +67 -67
  47. data/spec/concurrent/null_thread_pool_spec.rb +57 -57
  48. data/spec/concurrent/obligation_shared.rb +132 -132
  49. data/spec/concurrent/promise_spec.rb +316 -312
  50. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  51. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  52. data/spec/concurrent/reactor_spec.rb +364 -364
  53. data/spec/concurrent/supervisor_spec.rb +269 -269
  54. data/spec/concurrent/thread_pool_shared.rb +204 -204
  55. data/spec/concurrent/uses_global_thread_pool_shared.rb +64 -0
  56. data/spec/concurrent/utilities_spec.rb +74 -74
  57. data/spec/spec_helper.rb +32 -32
  58. metadata +17 -19
@@ -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,390 @@
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
+ require_relative 'uses_global_thread_pool_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe Agent do
7
+
8
+ let!(:thread_pool_user){ Agent }
9
+ it_should_behave_like Concurrent::UsesGlobalThreadPool
10
+
11
+ subject { Agent.new(0) }
12
+
13
+ let(:observer) do
14
+ Class.new do
15
+ attr_reader :value
16
+ define_method(:update) do |time, value|
17
+ @value = value
18
+ end
19
+ end.new
20
+ end
21
+
22
+ before(:each) do
23
+ Agent.thread_pool = FixedThreadPool.new(1)
24
+ end
25
+
26
+ context '#initialize' do
27
+
28
+ it 'sets the value to the given initial state' do
29
+ Agent.new(10).value.should eq 10
30
+ end
31
+
32
+ it 'sets the timeout to the given value' do
33
+ Agent.new(0, 5).timeout.should eq 5
34
+ end
35
+
36
+ it 'sets the timeout to the default when nil' do
37
+ Agent.new(0).timeout.should eq Agent::TIMEOUT
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
+ end
49
+
50
+ context '#rescue' do
51
+
52
+ it 'returns self when a block is given' do
53
+ a1 = subject
54
+ a2 = a1.rescue{}
55
+ a1.object_id.should eq a2.object_id
56
+ end
57
+
58
+ it 'returns self when no block is given' do
59
+ a1 = subject
60
+ a2 = a1.rescue
61
+ a1.object_id.should eq a2.object_id
62
+ end
63
+
64
+ it 'accepts an exception class as the first parameter' do
65
+ lambda {
66
+ subject.rescue(StandardError){}
67
+ }.should_not raise_error
68
+ end
69
+
70
+ it 'ignores rescuers without a block' do
71
+ subject.rescue
72
+ subject.instance_variable_get(:@rescuers).should be_empty
73
+ end
74
+ end
75
+
76
+ context '#validate' do
77
+
78
+ it 'returns self when a block is given' do
79
+ a1 = subject
80
+ a2 = a1.validate{}
81
+ a1.object_id.should eq a2.object_id
82
+ end
83
+
84
+ it 'returns self when no block is given' do
85
+ a1 = subject
86
+ a2 = a1.validate
87
+ a1.object_id.should eq a2.object_id
88
+ end
89
+
90
+ it 'ignores validators without a block' do
91
+ subject.validate
92
+ subject.instance_variable_get(:@validator).should be_nil
93
+ end
94
+ end
95
+
96
+ context '#post' do
97
+
98
+ it 'adds the given block to the queue' do
99
+ subject.post{ sleep(100) }
100
+ sleep(0.1)
101
+ before = subject.length
102
+ subject.post{ nil }
103
+ subject.post{ nil }
104
+ subject.length.should eq before+2
105
+ end
106
+
107
+ it 'does not add to the queue when no block is given' do
108
+ subject.post{ sleep(100) }
109
+ sleep(0.1)
110
+ before = subject.length
111
+ subject.post
112
+ 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
+ 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
+ end
139
+ end
140
+
141
+ context 'fulfillment' do
142
+
143
+ it 'process each block in the queue' do
144
+ @expected = []
145
+ subject.post{ @expected << 1 }
146
+ subject.post{ @expected << 2 }
147
+ subject.post{ @expected << 3 }
148
+ sleep(0.1)
149
+ @expected.should eq [1,2,3]
150
+ end
151
+
152
+ it 'passes the current value to the handler' do
153
+ @expected = nil
154
+ Agent.new(10).post{|i| @expected = i }
155
+ sleep(0.1)
156
+ @expected.should eq 10
157
+ end
158
+
159
+ it 'sets the value to the handler return value on success' do
160
+ subject.post{ 100 }
161
+ sleep(0.1)
162
+ subject.value.should eq 100
163
+ end
164
+
165
+ it 'rejects the handler after timeout reached' do
166
+ agent = Agent.new(0, 0.1)
167
+ agent.post{ sleep(1); 10 }
168
+ agent.value.should eq 0
169
+ end
170
+ end
171
+
172
+ context 'validation' do
173
+
174
+ it 'processes the validator when present' do
175
+ @expected = nil
176
+ subject.validate{ @expected = 10; true }
177
+ subject.post{ nil }
178
+ sleep(0.1)
179
+ @expected.should eq 10
180
+ end
181
+
182
+ it 'passes the new value to the validator' do
183
+ @expected = nil
184
+ subject.validate{|v| @expected = v; true }
185
+ subject.post{ 10 }
186
+ sleep(0.1)
187
+ @expected.should eq 10
188
+ end
189
+
190
+ it 'sets the new value when the validator returns true' do
191
+ agent = Agent.new(0).validate{ true }
192
+ agent.post{ 10 }
193
+ sleep(0.1)
194
+ agent.value.should eq 10
195
+ end
196
+
197
+ it 'does not change the value when the validator returns false' do
198
+ agent = Agent.new(0).validate{ false }
199
+ agent.post{ 10 }
200
+ sleep(0.1)
201
+ agent.value.should eq 0
202
+ end
203
+
204
+ it 'does not change the value when the validator raises an exception' do
205
+ agent = Agent.new(0).validate{ raise StandardError }
206
+ agent.post{ 10 }
207
+ sleep(0.1)
208
+ agent.value.should eq 0
209
+ end
210
+ end
211
+
212
+ context 'rejection' do
213
+
214
+ it 'calls the first exception block with a matching class' do
215
+ @expected = nil
216
+ subject.
217
+ rescue(StandardError){|ex| @expected = 1 }.
218
+ rescue(StandardError){|ex| @expected = 2 }.
219
+ rescue(StandardError){|ex| @expected = 3 }
220
+ subject.post{ raise StandardError }
221
+ sleep(0.1)
222
+ @expected.should eq 1
223
+ end
224
+
225
+ it 'matches all with a rescue with no class given' do
226
+ @expected = nil
227
+ subject.
228
+ rescue(LoadError){|ex| @expected = 1 }.
229
+ rescue{|ex| @expected = 2 }.
230
+ rescue(StandardError){|ex| @expected = 3 }
231
+ subject.post{ raise NoMethodError }
232
+ sleep(0.1)
233
+ @expected.should eq 2
234
+ end
235
+
236
+ it 'searches associated rescue handlers in order' do
237
+ @expected = nil
238
+ subject.
239
+ rescue(ArgumentError){|ex| @expected = 1 }.
240
+ rescue(LoadError){|ex| @expected = 2 }.
241
+ rescue(Exception){|ex| @expected = 3 }
242
+ subject.post{ raise ArgumentError }
243
+ sleep(0.1)
244
+ @expected.should eq 1
245
+
246
+ @expected = nil
247
+ subject.
248
+ rescue(ArgumentError){|ex| @expected = 1 }.
249
+ rescue(LoadError){|ex| @expected = 2 }.
250
+ rescue(Exception){|ex| @expected = 3 }
251
+ subject.post{ raise LoadError }
252
+ sleep(0.1)
253
+ @expected.should eq 2
254
+
255
+ @expected = nil
256
+ subject.
257
+ rescue(ArgumentError){|ex| @expected = 1 }.
258
+ rescue(LoadError){|ex| @expected = 2 }.
259
+ rescue(Exception){|ex| @expected = 3 }
260
+ subject.post{ raise StandardError }
261
+ sleep(0.1)
262
+ @expected.should eq 3
263
+ end
264
+
265
+ it 'passes the exception object to the matched block' do
266
+ @expected = nil
267
+ subject.
268
+ rescue(ArgumentError){|ex| @expected = ex }.
269
+ rescue(LoadError){|ex| @expected = ex }.
270
+ rescue(Exception){|ex| @expected = ex }
271
+ subject.post{ raise StandardError }
272
+ sleep(0.1)
273
+ @expected.should be_a(StandardError)
274
+ end
275
+
276
+ it 'ignores rescuers without a block' do
277
+ @expected = nil
278
+ subject.
279
+ rescue(StandardError).
280
+ rescue(StandardError){|ex| @expected = ex }.
281
+ rescue(Exception){|ex| @expected = ex }
282
+ subject.post{ raise StandardError }
283
+ sleep(0.1)
284
+ @expected.should be_a(StandardError)
285
+ end
286
+
287
+ it 'supresses the exception if no rescue matches' do
288
+ lambda {
289
+ subject.
290
+ rescue(ArgumentError){|ex| @expected = ex }.
291
+ rescue(StandardError){|ex| @expected = ex }.
292
+ rescue(Exception){|ex| @expected = ex }
293
+ subject.post{ raise StandardError }
294
+ sleep(0.1)
295
+ }.should_not raise_error
296
+ end
297
+
298
+ it 'supresses exceptions thrown from rescue handlers' do
299
+ lambda {
300
+ subject.rescue(Exception){ raise StandardError }
301
+ subject.post{ raise ArgumentError }
302
+ sleep(0.1)
303
+ }.should_not raise_error
304
+ end
305
+ end
306
+
307
+ context 'observation' do
308
+
309
+ it 'notifies all observers when the value changes' do
310
+ agent = Agent.new(0)
311
+ agent.add_observer(observer)
312
+ agent.post{ 10 }
313
+ sleep(0.1)
314
+ observer.value.should eq 10
315
+ end
316
+
317
+ it 'does not notify observers when validation fails' do
318
+ agent = Agent.new(0)
319
+ agent.validate{ false }
320
+ agent.add_observer(observer)
321
+ agent.post{ 10 }
322
+ sleep(0.1)
323
+ observer.value.should be_nil
324
+ end
325
+
326
+ it 'does not notify observers when the handler raises an exception' do
327
+ agent = Agent.new(0)
328
+ agent.add_observer(observer)
329
+ agent.post{ raise StandardError }
330
+ sleep(0.1)
331
+ observer.value.should be_nil
332
+ end
333
+ end
334
+
335
+ context 'aliases' do
336
+
337
+ it 'aliases #deref for #value' do
338
+ Agent.new(10).deref.should eq 10
339
+ end
340
+
341
+ it 'aliases #validates for :validate' do
342
+ @expected = nil
343
+ subject.validates{|v| @expected = v }
344
+ subject.post{ 10 }
345
+ sleep(0.1)
346
+ @expected.should eq 10
347
+ end
348
+
349
+ it 'aliases #validate_with for :validate' do
350
+ @expected = nil
351
+ subject.validate_with{|v| @expected = v }
352
+ subject.post{ 10 }
353
+ sleep(0.1)
354
+ @expected.should eq 10
355
+ end
356
+
357
+ it 'aliases #validates_with for :validate' do
358
+ @expected = nil
359
+ subject.validates_with{|v| @expected = v }
360
+ subject.post{ 10 }
361
+ sleep(0.1)
362
+ @expected.should eq 10
363
+ end
364
+
365
+ it 'aliases #catch for #rescue' do
366
+ @expected = nil
367
+ subject.catch{ @expected = true }
368
+ subject.post{ raise StandardError }
369
+ sleep(0.1)
370
+ @expected.should be_true
371
+ end
372
+
373
+ it 'aliases #on_error for #rescue' do
374
+ @expected = nil
375
+ subject.on_error{ @expected = true }
376
+ subject.post{ raise StandardError }
377
+ sleep(0.1)
378
+ @expected.should be_true
379
+ end
380
+
381
+ it 'aliases #add_watch for #add_observer' do
382
+ agent = Agent.new(0)
383
+ agent.add_watch(observer)
384
+ agent.post{ 10 }
385
+ sleep(0.1)
386
+ observer.value.should eq 10
387
+ end
388
+ end
389
+ end
390
+ end