concurrent-ruby 0.1.1.pre.2 → 0.1.1.pre.3

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.
@@ -35,7 +35,7 @@ module Concurrent
35
35
  end
36
36
 
37
37
  def shutdown?
38
- return ! running?
38
+ return @status == :shutdown
39
39
  end
40
40
 
41
41
  def killed?
@@ -44,8 +44,12 @@ module Concurrent
44
44
 
45
45
  def shutdown
46
46
  atomic {
47
- @pool.size.times{ @queue << :stop }
48
- @status = :shuttingdown
47
+ if @pool.empty?
48
+ @status = :shutdown
49
+ else
50
+ @status = :shuttingdown
51
+ @pool.size.times{ @queue << :stop }
52
+ end
49
53
  }
50
54
  end
51
55
 
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.1.1.pre.2'
2
+ VERSION = '0.1.1.pre.3'
3
3
  end
@@ -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
- the same thread as the *operation*. The raised exception is passed to the *errorback*. The calling thread is
12
- never aware of the result of the *operation*. This approach fits much more cleanly within an
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
@@ -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
@@ -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 &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
@@ -15,6 +15,10 @@ module Concurrent
15
15
  end.new
16
16
  end
17
17
 
18
+ before(:each) do
19
+ Agent.thread_pool = FixedThreadPool.new(1)
20
+ end
21
+
18
22
  context '#initialize' do
19
23
 
20
24
  it 'sets the value to the given initial state' do
@@ -34,7 +38,7 @@ module Concurrent
34
38
  end
35
39
 
36
40
  it 'spawns the worker thread' do
37
- Thread.should_receive(:new).once.with(any_args())
41
+ Agent.thread_pool.should_receive(:post).once.with(any_args())
38
42
  Agent.new(0)
39
43
  end
40
44
  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)
@@ -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
- context '#initialize' do
7
+ before(:each) do
8
+ Defer.thread_pool = FixedThreadPool.new(1)
9
+ end
8
10
 
9
- before(:each) do
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 {
@@ -24,22 +24,15 @@ module Concurrent
24
24
  end
25
25
 
26
26
  it 'starts the thread if an operation is given' do
27
- $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
27
+ Defer.thread_pool.should_receive(:post).once.with(any_args())
28
28
  operation = proc{ nil }
29
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
- $GLOBAL_THREAD_POOL.should_not_receive(:post)
33
+ Defer.thread_pool.should_not_receive(:post)
34
34
  Defer.new{ nil }
35
35
  end
36
-
37
- it 'runs on the alternate thread pool if given' do
38
- operation = proc{ nil }
39
- pool = Concurrent::FixedThreadPool.new(2)
40
- pool.should_receive(:post).with(no_args())
41
- Defer.new(op: operation, pool: pool)
42
- end
43
36
  end
44
37
 
45
38
  context '#then' do
@@ -126,14 +119,14 @@ module Concurrent
126
119
 
127
120
  it 'starts the thread if not started' do
128
121
  deferred = Defer.new{ nil }
129
- $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
122
+ Defer.thread_pool.should_receive(:post).once.with(any_args())
130
123
  deferred.go
131
124
  end
132
125
 
133
126
  it 'does nothing if called more than once' do
134
127
  deferred = Defer.new{ nil }
135
128
  deferred.go
136
- $GLOBAL_THREAD_POOL.should_not_receive(:post)
129
+ Defer.thread_pool.should_not_receive(:post)
137
130
  deferred.go
138
131
  end
139
132
 
@@ -142,16 +135,9 @@ module Concurrent
142
135
  callback = proc{|result| nil }
143
136
  errorback = proc{|ex| nil }
144
137
  deferred = Defer.new(op: operation, callback: callback, errorback: errorback)
145
- $GLOBAL_THREAD_POOL.should_not_receive(:post)
138
+ Defer.thread_pool.should_not_receive(:post)
146
139
  deferred.go
147
140
  end
148
-
149
- it 'runs on the alternate thread pool if given' do
150
- pool = Concurrent::FixedThreadPool.new(2)
151
- pool.should_receive(:post).with(no_args())
152
- deferred = Defer.new{ nil }
153
- deferred.go(pool)
154
- end
155
141
  end
156
142
 
157
143
  context 'fulfillment' do
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  require 'concurrent/agent'
4
4
  require 'concurrent/future'
5
+ require 'concurrent/goroutine'
5
6
  require 'concurrent/promise'
6
7
 
7
8
  module Concurrent
@@ -70,8 +71,11 @@ module Concurrent
70
71
 
71
72
  subject { Agent.new(0) }
72
73
 
74
+ before(:each) do
75
+ Agent.thread_pool = EventMachineDeferProxy.new
76
+ end
77
+
73
78
  it 'supports fulfillment' do
74
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
75
79
 
76
80
  EventMachine.run do
77
81
 
@@ -87,7 +91,6 @@ module Concurrent
87
91
  end
88
92
 
89
93
  it 'supports validation' do
90
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
91
94
 
92
95
  EventMachine.run do
93
96
 
@@ -102,7 +105,6 @@ module Concurrent
102
105
  end
103
106
 
104
107
  it 'supports rejection' do
105
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
106
108
 
107
109
  EventMachine.run do
108
110
 
@@ -122,8 +124,11 @@ module Concurrent
122
124
 
123
125
  context Future do
124
126
 
127
+ before(:each) do
128
+ Future.thread_pool = EventMachineDeferProxy.new
129
+ end
130
+
125
131
  it 'supports fulfillment' do
126
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
127
132
 
128
133
  EventMachine.run do
129
134
 
@@ -142,10 +147,13 @@ module Concurrent
142
147
 
143
148
  context Promise do
144
149
 
150
+ before(:each) do
151
+ Promise.thread_pool = EventMachineDeferProxy.new
152
+ end
153
+
145
154
  context 'fulfillment' do
146
155
 
147
156
  it 'passes all arguments to the first promise in the chain' do
148
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
149
157
 
150
158
  EventMachine.run do
151
159
 
@@ -162,7 +170,6 @@ module Concurrent
162
170
  end
163
171
 
164
172
  it 'passes the result of each block to all its children' do
165
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
166
173
 
167
174
  EventMachine.run do
168
175
  @expected = nil
@@ -176,7 +183,6 @@ module Concurrent
176
183
  end
177
184
 
178
185
  it 'sets the promise value to the result if its block' do
179
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
180
186
 
181
187
  EventMachine.run do
182
188
 
@@ -193,7 +199,6 @@ module Concurrent
193
199
  context 'rejection' do
194
200
 
195
201
  it 'sets the promise reason and error on exception' do
196
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
197
202
 
198
203
  EventMachine.run do
199
204
 
@@ -209,7 +214,6 @@ module Concurrent
209
214
  end
210
215
 
211
216
  it 'calls the first exception block with a matching class' do
212
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
213
217
 
214
218
  EventMachine.run do
215
219
 
@@ -227,7 +231,6 @@ module Concurrent
227
231
  end
228
232
 
229
233
  it 'passes the exception object to the matched block' do
230
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
231
234
 
232
235
  EventMachine.run do
233
236
 
@@ -142,5 +142,43 @@ module Concurrent
142
142
  @level.should eq :error
143
143
  end
144
144
  end
145
+
146
+ context '#status' do
147
+
148
+ it 'returns the status of the executor thread when running' do
149
+ @ec = Executor.run('Foo'){ nil }
150
+ sleep(0.1)
151
+ @ec.status.should eq 'sleep'
152
+ end
153
+
154
+ it 'returns nil when not running' do
155
+ @ec = Executor.run('Foo'){ nil }
156
+ @ec.kill
157
+ sleep(0.1)
158
+ @ec.status.should be_nil
159
+ end
160
+ end
161
+
162
+ context '#join' do
163
+
164
+ it 'joins the executor thread when running' do
165
+ @ec = Executor.run('Foo'){ nil }
166
+ Thread.new{ sleep(1); @ec.kill }
167
+ @ec.join.should be_a(Thread)
168
+ end
169
+
170
+ it 'joins the executor thread with timeout when running' do
171
+ @ec = Executor.run('Foo'){ nil }
172
+ @ec.join(1).should be_nil
173
+ end
174
+
175
+ it 'immediately returns nil when not running' do
176
+ @ec = Executor.run('Foo'){ nil }
177
+ @ec.kill
178
+ sleep(0.1)
179
+ @ec.join.should be_nil
180
+ @ec.join(1).should be_nil
181
+ end
182
+ end
145
183
  end
146
184
  end
@@ -2,8 +2,147 @@ require 'spec_helper'
2
2
 
3
3
  module Concurrent
4
4
 
5
+ describe 'functions' do
6
+
7
+ context '#post' do
8
+
9
+ it 'calls #post when supported by the object' do
10
+ object = Class.new{
11
+ def post() nil; end
12
+ }.new
13
+ object.should_receive(:post).with(no_args())
14
+ post(object){ nil }
15
+ end
16
+
17
+ it 'raises an exception when not supported by the object' do
18
+ object = Class.new{ }.new
19
+ lambda {
20
+ post(object){ nil }
21
+ }.should raise_error(ArgumentError)
22
+ end
23
+ end
24
+
25
+ context '#deref' do
26
+
27
+ it 'returns the value of the #deref function' do
28
+ object = Class.new{
29
+ def deref() nil; end
30
+ }.new
31
+ object.should_receive(:deref).with(nil)
32
+ deref(object, nil){ nil }
33
+ end
34
+
35
+ it 'returns the value of the #value function' do
36
+ object = Class.new{
37
+ def value() nil; end
38
+ }.new
39
+ object.should_receive(:value).with(nil)
40
+ deref(object, nil){ nil }
41
+ end
42
+
43
+ it 'raises an exception when not supported by the object' do
44
+ object = Class.new{ }.new
45
+ lambda {
46
+ deref(object, nil){ nil }
47
+ }.should raise_error(ArgumentError)
48
+ end
49
+ end
50
+
51
+ context '#pending?' do
52
+
53
+ it 'returns the value of the #pending? function' do
54
+ object = Class.new{
55
+ def pending?() nil; end
56
+ }.new
57
+ object.should_receive(:pending?).with(no_args())
58
+ pending?(object){ nil }
59
+ end
60
+
61
+ it 'raises an exception when not supported by the object' do
62
+ object = Class.new{ }.new
63
+ lambda {
64
+ pending?(object){ nil }
65
+ }.should raise_error(ArgumentError)
66
+ end
67
+ end
68
+
69
+ context '#fulfilled?' do
70
+
71
+ it 'returns the value of the #fulfilled? function' do
72
+ object = Class.new{
73
+ def fulfilled?() nil; end
74
+ }.new
75
+ object.should_receive(:fulfilled?).with(no_args())
76
+ fulfilled?(object){ nil }
77
+ end
78
+
79
+ it 'returns the value of the #realized? function' do
80
+ object = Class.new{
81
+ def realized?() nil; end
82
+ }.new
83
+ object.should_receive(:realized?).with(no_args())
84
+ fulfilled?(object){ nil }
85
+ end
86
+
87
+ it 'raises an exception when not supported by the object' do
88
+ object = Class.new{ }.new
89
+ lambda {
90
+ fulfilled?(object){ nil }
91
+ }.should raise_error(ArgumentError)
92
+ end
93
+ end
94
+
95
+ context '#realized?' do
96
+
97
+ it 'returns the value of the #realized? function' do
98
+ object = Class.new{
99
+ def realized?() nil; end
100
+ }.new
101
+ object.should_receive(:realized?).with(no_args())
102
+ realized?(object){ nil }
103
+ end
104
+
105
+ it 'returns the value of the #fulfilled? function' do
106
+ object = Class.new{
107
+ def fulfilled?() nil; end
108
+ }.new
109
+ object.should_receive(:fulfilled?).with(no_args())
110
+ realized?(object){ nil }
111
+ end
112
+
113
+ it 'raises an exception when not supported by the object' do
114
+ object = Class.new{ }.new
115
+ lambda {
116
+ realized?(object){ nil }
117
+ }.should raise_error(ArgumentError)
118
+ end
119
+ end
120
+
121
+ context '#rejected?' do
122
+
123
+ it 'returns the value of the #rejected? function' do
124
+ object = Class.new{
125
+ def rejected?() nil; end
126
+ }.new
127
+ object.should_receive(:rejected?).with(no_args())
128
+ rejected?(object){ nil }
129
+ end
130
+
131
+ it 'raises an exception when not supported by the object' do
132
+ object = Class.new{ }.new
133
+ lambda {
134
+ rejected?(object){ nil }
135
+ }.should raise_error(ArgumentError)
136
+ end
137
+ end
138
+ end
139
+
5
140
  describe Agent do
6
141
 
142
+ before(:each) do
143
+ Agent.thread_pool = FixedThreadPool.new(1)
144
+ end
145
+
7
146
  it 'aliases #<< for Agent#post' do
8
147
  subject = Agent.new(0)
9
148
  subject << proc{ 100 }
@@ -30,13 +169,30 @@ module Concurrent
30
169
 
31
170
  describe Defer do
32
171
 
172
+ before(:each) do
173
+ Defer.thread_pool = FixedThreadPool.new(1)
174
+ end
175
+
33
176
  it 'aliases Kernel#defer' do
34
177
  defer{ nil }.should be_a(Defer)
35
178
  end
36
179
  end
37
180
 
181
+ describe Executor do
182
+
183
+ it 'aliases Kernel#executor' do
184
+ ex = executor('executor'){ nil }
185
+ ex.should be_a(Executor::ExecutionContext)
186
+ ex.kill
187
+ end
188
+ end
189
+
38
190
  describe Future do
39
191
 
192
+ before(:each) do
193
+ Future.thread_pool = FixedThreadPool.new(1)
194
+ end
195
+
40
196
  it 'aliases Kernel#future for Future.new' do
41
197
  future().should be_a(Future)
42
198
  future(){ nil }.should be_a(Future)
@@ -47,6 +203,10 @@ module Concurrent
47
203
 
48
204
  describe Promise do
49
205
 
206
+ before(:each) do
207
+ Promise.thread_pool = FixedThreadPool.new(1)
208
+ end
209
+
50
210
  it 'aliases Kernel#promise for Promise.new' do
51
211
  promise().should be_a(Promise)
52
212
  promise(){ nil }.should be_a(Promise)