concurrent-ruby 0.1.1 → 0.2.0
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 +48 -1
- data/lib/concurrent.rb +8 -1
- data/lib/concurrent/agent.rb +19 -40
- data/lib/concurrent/cached_thread_pool.rb +10 -11
- data/lib/concurrent/defer.rb +8 -12
- data/lib/concurrent/executor.rb +95 -0
- data/lib/concurrent/fixed_thread_pool.rb +12 -6
- data/lib/concurrent/functions.rb +120 -0
- data/lib/concurrent/future.rb +8 -20
- data/lib/concurrent/global_thread_pool.rb +13 -0
- data/lib/concurrent/goroutine.rb +5 -1
- data/lib/concurrent/null_thread_pool.rb +22 -0
- data/lib/concurrent/obligation.rb +10 -64
- data/lib/concurrent/promise.rb +38 -60
- data/lib/concurrent/reactor.rb +166 -0
- data/lib/concurrent/reactor/drb_async_demux.rb +83 -0
- data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -0
- data/lib/concurrent/supervisor.rb +100 -0
- data/lib/concurrent/thread_pool.rb +16 -5
- data/lib/concurrent/utilities.rb +8 -0
- data/lib/concurrent/version.rb +1 -1
- data/md/defer.md +4 -4
- data/md/executor.md +187 -0
- data/md/promise.md +2 -0
- data/md/thread_pool.md +27 -0
- data/spec/concurrent/agent_spec.rb +8 -27
- data/spec/concurrent/cached_thread_pool_spec.rb +14 -1
- data/spec/concurrent/defer_spec.rb +17 -21
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +159 -149
- data/spec/concurrent/executor_spec.rb +200 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +2 -3
- data/spec/concurrent/functions_spec.rb +217 -0
- data/spec/concurrent/future_spec.rb +4 -11
- data/spec/concurrent/global_thread_pool_spec.rb +38 -0
- data/spec/concurrent/goroutine_spec.rb +15 -0
- data/spec/concurrent/null_thread_pool_spec.rb +54 -0
- data/spec/concurrent/obligation_shared.rb +127 -116
- data/spec/concurrent/promise_spec.rb +16 -14
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -0
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -0
- data/spec/concurrent/reactor_spec.rb +364 -0
- data/spec/concurrent/supervisor_spec.rb +258 -0
- data/spec/concurrent/thread_pool_shared.rb +156 -161
- data/spec/concurrent/utilities_spec.rb +30 -1
- data/spec/spec_helper.rb +13 -0
- metadata +38 -9
@@ -35,7 +35,7 @@ module Concurrent
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def shutdown?
|
38
|
-
return
|
38
|
+
return @status == :shutdown
|
39
39
|
end
|
40
40
|
|
41
41
|
def killed?
|
@@ -43,10 +43,14 @@ module Concurrent
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def shutdown
|
46
|
-
|
47
|
-
@pool.
|
48
|
-
|
49
|
-
|
46
|
+
mutex.synchronize do
|
47
|
+
if @pool.empty?
|
48
|
+
@status = :shutdown
|
49
|
+
else
|
50
|
+
@status = :shuttingdown
|
51
|
+
@pool.size.times{ @queue << :stop }
|
52
|
+
end
|
53
|
+
end
|
50
54
|
end
|
51
55
|
|
52
56
|
def wait_for_termination(timeout = nil)
|
@@ -61,5 +65,12 @@ module Concurrent
|
|
61
65
|
self.post(&block)
|
62
66
|
return self
|
63
67
|
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
# @private
|
72
|
+
def mutex # :nodoc:
|
73
|
+
@mutex || Mutex.new
|
74
|
+
end
|
64
75
|
end
|
65
76
|
end
|
data/lib/concurrent/utilities.rb
CHANGED
data/lib/concurrent/version.rb
CHANGED
data/md/defer.md
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
In the pantheon of concurrency objects a `Defer` sits somewhere between `Future` and `Promise`.
|
4
4
|
Inspired by [EventMachine's *defer* method](https://github.com/eventmachine/eventmachine/wiki/EM::Deferrable-and-EM.defer),
|
5
|
-
a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`.
|
5
|
+
a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`. Defers run on the global thread pool.
|
6
6
|
|
7
7
|
Unlike `Future` and `Promise` a defer is non-blocking. The deferred *operation* is performed on another
|
8
8
|
thread. If the *operation* is successful an optional *callback* is called on the same thread as the *operation*.
|
9
9
|
The result of the *operation* is passed to the *callbacl*. If the *operation* fails (by raising an exception)
|
10
|
-
then an optional *errorback* (error callback) is called on
|
11
|
-
|
12
|
-
|
10
|
+
then an optional *errorback* (error callback) is called on the same thread as the *operation*. The raised
|
11
|
+
exception is passed to the *errorback*. The calling thread is never aware of the result of the *operation*.
|
12
|
+
This approach fits much more cleanly within an
|
13
13
|
[event-driven](http://en.wikipedia.org/wiki/Event-driven_programming) application.
|
14
14
|
|
15
15
|
The operation of a `Defer` can easily be simulated using either `Future` or `Promise` and traditional branching
|
data/md/executor.md
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
# Being of Sound Mind
|
2
|
+
|
3
|
+
A very common currency pattern is to run a thread that performs a task at regular
|
4
|
+
intervals. The thread that peforms the task sleeps for the given interval then
|
5
|
+
waked up and performs the task. Later, rinse, repeat... This pattern causes two
|
6
|
+
problems. First, it is difficult to test the business logic of the task becuse the
|
7
|
+
task itself is tightly couple with the threading. Second, an exception in the task
|
8
|
+
can cause the entire thread to abend. In a long-running application where the task
|
9
|
+
thread is intended to run for days/weeks/years a crashed task thread can pose a real
|
10
|
+
problem. The `Executor` class alleviates both problems.
|
11
|
+
|
12
|
+
When an executor is launched it starts a thread for monitoring the execution interval.
|
13
|
+
The executor thread does not perform the task, however. Instead, the executor
|
14
|
+
launches the task on a separat thread. The advantage of this approach is that if
|
15
|
+
the task crashes it will only kill the task thread, not the executor thread. The
|
16
|
+
executor thread can then log the success or failure of the task. The executor
|
17
|
+
can even be configured with a timeout value allowing it to kill a task that runs
|
18
|
+
to long and then log the error.
|
19
|
+
|
20
|
+
One other advantage of the `Executor` class is that it forces the bsiness logic to
|
21
|
+
be completely decoupled from the threading logic. The business logic can be tested
|
22
|
+
separately then passed to the an executor for scheduling and running.
|
23
|
+
|
24
|
+
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 from
|
26
|
+
the `Executor` class tend to also be long running. For this reason they get their
|
27
|
+
own thread every time the task is executed.
|
28
|
+
|
29
|
+
## ExecutionContext
|
30
|
+
|
31
|
+
When an executor is run the return value is an `ExecutionContext` object. An
|
32
|
+
`ExecutionContext` object has several attribute readers (`#name`, `#execution_interval`,
|
33
|
+
and `#timeout_interval`). It also provides several `Thread` operations which can
|
34
|
+
be performed against the internal thread. These include `#status`, `#join`, and
|
35
|
+
`kill`.
|
36
|
+
|
37
|
+
## Custom Logging
|
38
|
+
|
39
|
+
An executor will write a log message to standard out at the completion of every
|
40
|
+
task run. When the task is successful the log message is tagged at the `:info`
|
41
|
+
level. When the task times out the log message is tagged at the `warn` level.
|
42
|
+
When the task fails tocomplete (most likely because of exception) the log
|
43
|
+
message is tagged at the `error` level.
|
44
|
+
|
45
|
+
The default logging behavior can be overridden by passing a `proc` to the executor
|
46
|
+
on creation. The block will be passes three (3) arguments every time it is run:
|
47
|
+
executor `name`, log `level`, and the log `msg` (message). The `proc` can do
|
48
|
+
whatever it wanst with these arguments.
|
49
|
+
|
50
|
+
## Examples
|
51
|
+
|
52
|
+
A basic example:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'concurrent'
|
56
|
+
|
57
|
+
ec = Concurrent::Executor.run('Foo'){ puts 'Boom!' }
|
58
|
+
|
59
|
+
ec.name #=> "Foo"
|
60
|
+
ec.execution_interval #=> 60 == Concurrent::Executor::EXECUTION_INTERVAL
|
61
|
+
ec.timeout_interval #=> 30 == Concurrent::Executor::TIMEOUT_INTERVAL
|
62
|
+
ec.status #=> "sleep"
|
63
|
+
|
64
|
+
# wait 60 seconds...
|
65
|
+
#=> 'Boom!'
|
66
|
+
#=> ' INFO (2013-08-02 23:20:15) Foo: execution completed successfully'
|
67
|
+
|
68
|
+
ec.kill #=> true
|
69
|
+
```
|
70
|
+
|
71
|
+
Both the execution_interval and the timeout_interval can be configured:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
ec = Concurrent::Executor.run('Foo', execution_interval: 5, timeout_interval: 5) do
|
75
|
+
puts 'Boom!'
|
76
|
+
end
|
77
|
+
|
78
|
+
ec.execution_interval #=> 5
|
79
|
+
ec.timeout_interval #=> 5
|
80
|
+
```
|
81
|
+
|
82
|
+
By default an `Executor` will wait for `:execution_interval` seconds before running the block.
|
83
|
+
To run the block immediately set the `:run_now` option to `true`:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
ec = Concurrent::Executor.run('Foo', run_now: true){ puts 'Boom!' }
|
87
|
+
#=> 'Boom!''
|
88
|
+
#=> ' INFO (2013-08-15 21:35:14) Foo: execution completed successfully'
|
89
|
+
ec.status #=> "sleep"
|
90
|
+
>>
|
91
|
+
```
|
92
|
+
|
93
|
+
A simple example with timeout and task exception:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
ec = Concurrent::Executor.run('Foo', execution_interval: 1, timeout_interval: 1){ sleep(10) }
|
97
|
+
|
98
|
+
#=> WARN (2013-08-02 23:45:26) Foo: execution timed out after 1 seconds
|
99
|
+
#=> WARN (2013-08-02 23:45:28) Foo: execution timed out after 1 seconds
|
100
|
+
#=> WARN (2013-08-02 23:45:30) Foo: execution timed out after 1 seconds
|
101
|
+
|
102
|
+
ec = Concurrent::Executor.run('Foo', execution_interval: 1){ raise StandardError }
|
103
|
+
|
104
|
+
#=> ERROR (2013-08-02 23:47:31) Foo: execution failed with error 'StandardError'
|
105
|
+
#=> ERROR (2013-08-02 23:47:32) Foo: execution failed with error 'StandardError'
|
106
|
+
#=> ERROR (2013-08-02 23:47:33) Foo: execution failed with error 'StandardError'
|
107
|
+
```
|
108
|
+
|
109
|
+
For custom logging, simply provide a `proc` when creating an executor:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
file_logger = proc do |name, level, msg|
|
113
|
+
open('executor.log', 'a') do |f|
|
114
|
+
f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
|
119
|
+
puts 'Boom!'
|
120
|
+
end
|
121
|
+
|
122
|
+
# the log file contains
|
123
|
+
# INFO (2013-08-02 23:30:19) Foo: execution completed successfully
|
124
|
+
# INFO (2013-08-02 23:30:24) Foo: execution completed successfully
|
125
|
+
# INFO (2013-08-02 23:30:29) Foo: execution completed successfully
|
126
|
+
# INFO (2013-08-02 23:30:34) Foo: execution completed successfully
|
127
|
+
# INFO (2013-08-02 23:30:39) Foo: execution completed successfully
|
128
|
+
# INFO (2013-08-02 23:30:44) Foo: execution completed successfully
|
129
|
+
```
|
130
|
+
|
131
|
+
It is also possible to access the default stdout logger from within a logger `proc`:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
file_logger = proc do |name, level, msg|
|
135
|
+
Concurrent::Executor::STDOUT_LOGGER.call(name, level, msg)
|
136
|
+
open('executor.log', 'a') do |f|
|
137
|
+
f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
|
142
|
+
puts 'Boom!'
|
143
|
+
end
|
144
|
+
|
145
|
+
# wait...
|
146
|
+
|
147
|
+
#=> Boom!
|
148
|
+
#=> INFO (2013-08-02 23:40:49) Foo: execution completed successfully
|
149
|
+
#=> Boom!
|
150
|
+
#=> INFO (2013-08-02 23:40:54) Foo: execution completed successfully
|
151
|
+
#=> Boom!
|
152
|
+
#=> INFO (2013-08-02 23:40:59) Foo: execution completed successfully
|
153
|
+
|
154
|
+
# and the log file contains
|
155
|
+
# INFO (2013-08-02 23:39:52) Foo: execution completed successfully
|
156
|
+
# INFO (2013-08-02 23:39:57) Foo: execution completed successfully
|
157
|
+
# INFO (2013-08-02 23:40:49) Foo: execution completed successfully
|
158
|
+
```
|
159
|
+
|
160
|
+
## Copyright
|
161
|
+
|
162
|
+
*Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
163
|
+
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
164
|
+
|
165
|
+
## License
|
166
|
+
|
167
|
+
Released under the MIT license.
|
168
|
+
|
169
|
+
http://www.opensource.org/licenses/mit-license.php
|
170
|
+
|
171
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
172
|
+
> of this software and associated documentation files (the "Software"), to deal
|
173
|
+
> in the Software without restriction, including without limitation the rights
|
174
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
175
|
+
> copies of the Software, and to permit persons to whom the Software is
|
176
|
+
> furnished to do so, subject to the following conditions:
|
177
|
+
>
|
178
|
+
> The above copyright notice and this permission notice shall be included in
|
179
|
+
> all copies or substantial portions of the Software.
|
180
|
+
>
|
181
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
182
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
183
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
184
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
185
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
186
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
187
|
+
> THE SOFTWARE.
|
data/md/promise.md
CHANGED
@@ -28,6 +28,8 @@ A *timeout* value can be passed to `value` to limit how long the call will block
|
|
28
28
|
block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
|
29
29
|
maximum number of seconds to block.
|
30
30
|
|
31
|
+
Promises run on the global thread pool.
|
32
|
+
|
31
33
|
## Examples
|
32
34
|
|
33
35
|
Start by requiring promises
|
data/md/thread_pool.md
CHANGED
@@ -151,6 +151,12 @@ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
|
|
151
151
|
old_global_pool.shutdown
|
152
152
|
```
|
153
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
|
+
|
154
160
|
### EventMachine
|
155
161
|
|
156
162
|
The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
|
@@ -167,6 +173,27 @@ require 'functional/concurrency'
|
|
167
173
|
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
168
174
|
```
|
169
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
|
+
|
170
197
|
## Copyright
|
171
198
|
|
172
199
|
*Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
@@ -16,7 +16,7 @@ module Concurrent
|
|
16
16
|
end
|
17
17
|
|
18
18
|
before(:each) do
|
19
|
-
|
19
|
+
Agent.thread_pool = FixedThreadPool.new(1)
|
20
20
|
end
|
21
21
|
|
22
22
|
context '#initialize' do
|
@@ -38,7 +38,7 @@ module Concurrent
|
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'spawns the worker thread' do
|
41
|
-
|
41
|
+
Agent.thread_pool.should_receive(:post).once.with(any_args())
|
42
42
|
Agent.new(0)
|
43
43
|
end
|
44
44
|
end
|
@@ -92,6 +92,8 @@ module Concurrent
|
|
92
92
|
context '#post' do
|
93
93
|
|
94
94
|
it 'adds the given block to the queue' do
|
95
|
+
subject.post{ sleep(100) }
|
96
|
+
sleep(0.1)
|
95
97
|
before = subject.length
|
96
98
|
subject.post{ nil }
|
97
99
|
subject.post{ nil }
|
@@ -99,6 +101,8 @@ module Concurrent
|
|
99
101
|
end
|
100
102
|
|
101
103
|
it 'does not add to the queue when no block is given' do
|
104
|
+
subject.post{ sleep(100) }
|
105
|
+
sleep(0.1)
|
102
106
|
before = subject.length
|
103
107
|
subject.post
|
104
108
|
subject.post{ nil }
|
@@ -113,6 +117,8 @@ module Concurrent
|
|
113
117
|
end
|
114
118
|
|
115
119
|
it 'should increase by one for each #post' do
|
120
|
+
subject.post{ sleep(100) }
|
121
|
+
sleep(0.1)
|
116
122
|
subject.post{ sleep }
|
117
123
|
subject.post{ sleep }
|
118
124
|
subject.post{ sleep }
|
@@ -375,31 +381,6 @@ module Concurrent
|
|
375
381
|
sleep(0.1)
|
376
382
|
observer.value.should eq 10
|
377
383
|
end
|
378
|
-
|
379
|
-
it 'aliases #<< for Agent#post' do
|
380
|
-
subject << proc{ 100 }
|
381
|
-
sleep(0.1)
|
382
|
-
subject.value.should eq 100
|
383
|
-
|
384
|
-
subject << lambda{ 100 }
|
385
|
-
sleep(0.1)
|
386
|
-
subject.value.should eq 100
|
387
|
-
end
|
388
|
-
|
389
|
-
it 'aliases Kernel#agent for Agent.new' do
|
390
|
-
agent(10).should be_a(Agent)
|
391
|
-
end
|
392
|
-
|
393
|
-
it 'aliases Kernel#deref for #deref' do
|
394
|
-
deref(Agent.new(10)).should eq 10
|
395
|
-
deref(Agent.new(10), 10).should eq 10
|
396
|
-
end
|
397
|
-
|
398
|
-
it 'aliases Kernel:post for Agent#post' do
|
399
|
-
post(subject){ 100 }
|
400
|
-
sleep(0.1)
|
401
|
-
subject.value.should eq 100
|
402
|
-
end
|
403
384
|
end
|
404
385
|
end
|
405
386
|
end
|
@@ -10,6 +10,7 @@ module Concurrent
|
|
10
10
|
it_should_behave_like 'Thread Pool'
|
11
11
|
|
12
12
|
context '#initialize' do
|
13
|
+
|
13
14
|
it 'aliases Concurrent#new_cached_thread_pool' do
|
14
15
|
pool = Concurrent.new_cached_thread_pool
|
15
16
|
pool.should be_a(CachedThreadPool)
|
@@ -20,7 +21,7 @@ module Concurrent
|
|
20
21
|
context '#kill' do
|
21
22
|
|
22
23
|
it 'kills all threads' do
|
23
|
-
Thread.should_receive(:kill).
|
24
|
+
Thread.should_receive(:kill).at_least(5).times
|
24
25
|
pool = CachedThreadPool.new
|
25
26
|
5.times{ sleep(0.1); pool << proc{ sleep(1) } }
|
26
27
|
sleep(1)
|
@@ -108,5 +109,17 @@ module Concurrent
|
|
108
109
|
subject.instance_variable_get(:@collector).status.should be_false
|
109
110
|
end
|
110
111
|
end
|
112
|
+
|
113
|
+
context '#status' do
|
114
|
+
|
115
|
+
it 'returns an empty collection when the pool is empty' do
|
116
|
+
subject.status.should be_empty
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'returns one status object for each thread in the pool' do
|
120
|
+
3.times{ sleep(0.1); subject << proc{ sleep(0.5) } }
|
121
|
+
subject.status.length.should eq 3
|
122
|
+
end
|
123
|
+
end
|
111
124
|
end
|
112
125
|
end
|
@@ -4,11 +4,11 @@ module Concurrent
|
|
4
4
|
|
5
5
|
describe Defer do
|
6
6
|
|
7
|
-
|
7
|
+
before(:each) do
|
8
|
+
Defer.thread_pool = FixedThreadPool.new(1)
|
9
|
+
end
|
8
10
|
|
9
|
-
|
10
|
-
$GLOBAL_THREAD_POOL = FixedThreadPool.new(1)
|
11
|
-
end
|
11
|
+
context '#initialize' do
|
12
12
|
|
13
13
|
it 'raises an exception if no block or operation given' do
|
14
14
|
lambda {
|
@@ -19,23 +19,19 @@ module Concurrent
|
|
19
19
|
it 'raises an exception if both a block and an operation given' do
|
20
20
|
lambda {
|
21
21
|
operation = proc{ nil }
|
22
|
-
Defer.new(operation
|
22
|
+
Defer.new(op: operation){ nil }
|
23
23
|
}.should raise_error(ArgumentError)
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'starts the thread if an operation is given' do
|
27
|
-
|
27
|
+
Defer.thread_pool.should_receive(:post).once.with(any_args())
|
28
28
|
operation = proc{ nil }
|
29
|
-
Defer.new(operation
|
29
|
+
Defer.new(op: operation)
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'does not start the thread if neither a callback or errorback is given' do
|
33
|
-
|
34
|
-
Defer.new
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'aliases Kernel#defer' do
|
38
|
-
defer{ nil }.should be_a(Defer)
|
33
|
+
Defer.thread_pool.should_not_receive(:post)
|
34
|
+
Defer.new{ nil }
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
@@ -56,14 +52,14 @@ module Concurrent
|
|
56
52
|
it 'raises an exception if an operation was provided at construction' do
|
57
53
|
lambda {
|
58
54
|
operation = proc{ nil }
|
59
|
-
Defer.new(operation
|
55
|
+
Defer.new(op: operation).then{|result| nil }
|
60
56
|
}.should raise_error(IllegalMethodCallError)
|
61
57
|
end
|
62
58
|
|
63
59
|
it 'raises an exception if a callback was provided at construction' do
|
64
60
|
lambda {
|
65
61
|
callback = proc{|result|nil }
|
66
|
-
Defer.new(
|
62
|
+
Defer.new(callback: callback){ nil }.then{|result| nil }
|
67
63
|
}.should raise_error(IllegalMethodCallError)
|
68
64
|
end
|
69
65
|
|
@@ -90,14 +86,14 @@ module Concurrent
|
|
90
86
|
it 'raises an exception if an operation was provided at construction' do
|
91
87
|
lambda {
|
92
88
|
operation = proc{ nil }
|
93
|
-
Defer.new(operation
|
89
|
+
Defer.new(op: operation).rescue{|ex| nil }
|
94
90
|
}.should raise_error(IllegalMethodCallError)
|
95
91
|
end
|
96
92
|
|
97
93
|
it 'raises an exception if an errorback was provided at construction' do
|
98
94
|
lambda {
|
99
95
|
errorback = proc{|ex| nil }
|
100
|
-
Defer.new(
|
96
|
+
Defer.new(errorback: errorback){ nil }.rescue{|ex| nil }
|
101
97
|
}.should raise_error(IllegalMethodCallError)
|
102
98
|
end
|
103
99
|
|
@@ -123,14 +119,14 @@ module Concurrent
|
|
123
119
|
|
124
120
|
it 'starts the thread if not started' do
|
125
121
|
deferred = Defer.new{ nil }
|
126
|
-
|
122
|
+
Defer.thread_pool.should_receive(:post).once.with(any_args())
|
127
123
|
deferred.go
|
128
124
|
end
|
129
125
|
|
130
126
|
it 'does nothing if called more than once' do
|
131
127
|
deferred = Defer.new{ nil }
|
132
128
|
deferred.go
|
133
|
-
|
129
|
+
Defer.thread_pool.should_not_receive(:post)
|
134
130
|
deferred.go
|
135
131
|
end
|
136
132
|
|
@@ -138,8 +134,8 @@ module Concurrent
|
|
138
134
|
operation = proc{ nil }
|
139
135
|
callback = proc{|result| nil }
|
140
136
|
errorback = proc{|ex| nil }
|
141
|
-
deferred = Defer.new(operation, callback, errorback)
|
142
|
-
|
137
|
+
deferred = Defer.new(op: operation, callback: callback, errorback: errorback)
|
138
|
+
Defer.thread_pool.should_not_receive(:post)
|
143
139
|
deferred.go
|
144
140
|
end
|
145
141
|
end
|