concurrent-ruby 0.1.1.pre.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -48
- data/lib/concurrent.rb +0 -6
- data/lib/concurrent/agent.rb +40 -19
- data/lib/concurrent/cached_thread_pool.rb +11 -10
- data/lib/concurrent/defer.rb +12 -8
- data/lib/concurrent/fixed_thread_pool.rb +6 -12
- data/lib/concurrent/future.rb +20 -8
- data/lib/concurrent/global_thread_pool.rb +0 -13
- data/lib/concurrent/goroutine.rb +1 -5
- data/lib/concurrent/obligation.rb +64 -10
- data/lib/concurrent/promise.rb +60 -38
- data/lib/concurrent/thread_pool.rb +5 -16
- data/lib/concurrent/utilities.rb +0 -8
- data/lib/concurrent/version.rb +1 -1
- data/md/defer.md +4 -4
- data/md/promise.md +0 -2
- data/md/thread_pool.md +0 -27
- data/spec/concurrent/agent_spec.rb +27 -8
- data/spec/concurrent/cached_thread_pool_spec.rb +1 -14
- data/spec/concurrent/defer_spec.rb +21 -17
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +149 -159
- data/spec/concurrent/fixed_thread_pool_spec.rb +3 -2
- data/spec/concurrent/future_spec.rb +10 -3
- data/spec/concurrent/goroutine_spec.rb +0 -15
- data/spec/concurrent/obligation_shared.rb +2 -16
- data/spec/concurrent/promise_spec.rb +13 -15
- data/spec/concurrent/thread_pool_shared.rb +5 -5
- data/spec/concurrent/utilities_spec.rb +1 -30
- data/spec/spec_helper.rb +0 -25
- metadata +7 -28
- data/lib/concurrent/executor.rb +0 -95
- data/lib/concurrent/functions.rb +0 -120
- data/lib/concurrent/null_thread_pool.rb +0 -22
- data/lib/concurrent/reactor.rb +0 -161
- data/lib/concurrent/reactor/drb_async_demux.rb +0 -74
- data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -98
- data/md/executor.md +0 -176
- data/spec/concurrent/executor_spec.rb +0 -200
- data/spec/concurrent/functions_spec.rb +0 -217
- data/spec/concurrent/global_thread_pool_spec.rb +0 -38
- data/spec/concurrent/null_thread_pool_spec.rb +0 -54
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -12
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -12
- data/spec/concurrent/reactor_spec.rb +0 -351
@@ -1,16 +1,3 @@
|
|
1
1
|
require 'concurrent/cached_thread_pool'
|
2
2
|
|
3
3
|
$GLOBAL_THREAD_POOL ||= Concurrent::CachedThreadPool.new
|
4
|
-
|
5
|
-
module Concurrent
|
6
|
-
|
7
|
-
module UsesGlobalThreadPool
|
8
|
-
|
9
|
-
def self.included(base)
|
10
|
-
class << base
|
11
|
-
attr_accessor :thread_pool
|
12
|
-
end
|
13
|
-
base.thread_pool = $GLOBAL_THREAD_POOL
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
data/lib/concurrent/goroutine.rb
CHANGED
@@ -19,11 +19,7 @@ module Kernel
|
|
19
19
|
# @see https://gobyexample.com/goroutines
|
20
20
|
def go(*args, &block)
|
21
21
|
return false unless block_given?
|
22
|
-
|
23
|
-
args.first.post(*args.slice(1, args.length), &block)
|
24
|
-
else
|
25
|
-
$GLOBAL_THREAD_POOL.post(*args, &block)
|
26
|
-
end
|
22
|
+
$GLOBAL_THREAD_POOL.post(*args, &block)
|
27
23
|
end
|
28
24
|
module_function :go
|
29
25
|
end
|
@@ -33,23 +33,19 @@ module Concurrent
|
|
33
33
|
def fulfilled?() return(@state == :fulfilled); end
|
34
34
|
alias_method :realized?, :fulfilled?
|
35
35
|
|
36
|
-
# Has the promise been rejected?
|
37
|
-
# @return [Boolean]
|
38
|
-
def rejected?() return(@state == :rejected); end
|
39
|
-
|
40
36
|
# Is obligation completion still pending?
|
41
37
|
# @return [Boolean]
|
42
|
-
def pending?() return(
|
38
|
+
def pending?() return(!(fulfilled? || rejected?)); end
|
43
39
|
|
44
40
|
def value(timeout = nil)
|
45
|
-
if timeout == 0
|
41
|
+
if !pending? || timeout == 0
|
46
42
|
return @value
|
47
43
|
elsif timeout.nil?
|
48
|
-
return
|
44
|
+
return semaphore.synchronize { @value }
|
49
45
|
else
|
50
46
|
begin
|
51
47
|
return Timeout::timeout(timeout.to_f) {
|
52
|
-
|
48
|
+
semaphore.synchronize { @value }
|
53
49
|
}
|
54
50
|
rescue Timeout::Error => ex
|
55
51
|
return nil
|
@@ -58,10 +54,68 @@ module Concurrent
|
|
58
54
|
end
|
59
55
|
alias_method :deref, :value
|
60
56
|
|
57
|
+
# Has the promise been rejected?
|
58
|
+
# @return [Boolean]
|
59
|
+
def rejected?() return(@state == :rejected); end
|
60
|
+
|
61
61
|
protected
|
62
62
|
|
63
|
-
def
|
64
|
-
@
|
63
|
+
def semaphore
|
64
|
+
@semaphore ||= Mutex.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Kernel
|
70
|
+
|
71
|
+
def deref(obligation, timeout = nil)
|
72
|
+
if obligation.respond_to?(:deref)
|
73
|
+
return obligation.deref(timeout)
|
74
|
+
elsif obligation.respond_to?(:value)
|
75
|
+
return obligation.deref(timeout)
|
76
|
+
else
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
module_function :deref
|
81
|
+
|
82
|
+
def pending?(obligation)
|
83
|
+
if obligation.respond_to?(:pending?)
|
84
|
+
return obligation.pending?
|
85
|
+
else
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
module_function :pending?
|
90
|
+
|
91
|
+
def fulfilled?(obligation)
|
92
|
+
if obligation.respond_to?(:fulfilled?)
|
93
|
+
return obligation.fulfilled?
|
94
|
+
elsif obligation.respond_to?(:realized?)
|
95
|
+
return obligation.realized?
|
96
|
+
else
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
module_function :fulfilled?
|
101
|
+
|
102
|
+
def realized?(obligation)
|
103
|
+
if obligation.respond_to?(:realized?)
|
104
|
+
return obligation.realized?
|
105
|
+
elsif obligation.respond_to?(:fulfilled?)
|
106
|
+
return obligation.fulfilled?
|
107
|
+
else
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
module_function :realized?
|
112
|
+
|
113
|
+
def rejected?(obligation)
|
114
|
+
if obligation.respond_to?(:rejected?)
|
115
|
+
return obligation.rejected?
|
116
|
+
else
|
117
|
+
return false
|
65
118
|
end
|
66
119
|
end
|
120
|
+
module_function :rejected?
|
67
121
|
end
|
data/lib/concurrent/promise.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
-
require 'concurrent/global_thread_pool'
|
4
3
|
require 'concurrent/obligation'
|
5
4
|
require 'concurrent/utilities'
|
6
5
|
|
@@ -8,8 +7,6 @@ module Concurrent
|
|
8
7
|
|
9
8
|
class Promise
|
10
9
|
include Obligation
|
11
|
-
include UsesGlobalThreadPool
|
12
|
-
|
13
10
|
behavior(:future)
|
14
11
|
behavior(:promise)
|
15
12
|
|
@@ -36,22 +33,17 @@ module Concurrent
|
|
36
33
|
@chain = [self]
|
37
34
|
end
|
38
35
|
|
39
|
-
@
|
36
|
+
@mutex = Mutex.new
|
40
37
|
@handler = block || Proc.new{|result| result }
|
41
38
|
@state = :pending
|
42
39
|
@value = nil
|
43
40
|
@reason = nil
|
44
|
-
@rescued = false
|
45
41
|
@children = []
|
46
42
|
@rescuers = []
|
47
43
|
|
48
44
|
realize(*args) if root?
|
49
45
|
end
|
50
46
|
|
51
|
-
def rescued?
|
52
|
-
return @rescued
|
53
|
-
end
|
54
|
-
|
55
47
|
# Create a new child Promise. The block argument for the child will
|
56
48
|
# be the result of fulfilling its parent. If the child will
|
57
49
|
# immediately be rejected if the parent has already been rejected.
|
@@ -60,7 +52,7 @@ module Concurrent
|
|
60
52
|
#
|
61
53
|
# @return [Promise] the new promise
|
62
54
|
def then(&block)
|
63
|
-
child = @
|
55
|
+
child = @mutex.synchronize do
|
64
56
|
block = Proc.new{|result| result } unless block_given?
|
65
57
|
@children << Promise.new(self, &block)
|
66
58
|
@children.last.on_reject(@reason) if rejected?
|
@@ -81,14 +73,8 @@ module Concurrent
|
|
81
73
|
#
|
82
74
|
# @return [self] so that additional chaining can occur
|
83
75
|
def rescue(clazz = Exception, &block)
|
84
|
-
|
85
|
-
|
86
|
-
rescuer = Rescuer.new(clazz, block)
|
87
|
-
if pending?
|
88
|
-
@rescuers << rescuer
|
89
|
-
else
|
90
|
-
try_rescue(reason, rescuer)
|
91
|
-
end
|
76
|
+
@mutex.synchronize do
|
77
|
+
@rescuers << Rescuer.new(clazz, block) if block_given?
|
92
78
|
end
|
93
79
|
return self
|
94
80
|
end
|
@@ -104,6 +90,15 @@ module Concurrent
|
|
104
90
|
# @private
|
105
91
|
Rescuer = Struct.new(:clazz, :block)
|
106
92
|
|
93
|
+
# @private
|
94
|
+
def root # :nodoc:
|
95
|
+
return atomic {
|
96
|
+
current = self
|
97
|
+
current = current.parent until current.root?
|
98
|
+
current
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
107
102
|
# @private
|
108
103
|
def root? # :nodoc:
|
109
104
|
@parent.nil?
|
@@ -120,44 +115,47 @@ module Concurrent
|
|
120
115
|
|
121
116
|
# @private
|
122
117
|
def on_fulfill(value) # :nodoc:
|
123
|
-
@
|
124
|
-
|
125
|
-
|
126
|
-
|
118
|
+
@mutex.synchronize do
|
119
|
+
if pending?
|
120
|
+
@value = @handler.call(value)
|
121
|
+
@state = :fulfilled
|
122
|
+
@reason = nil
|
123
|
+
end
|
127
124
|
end
|
128
125
|
return @value
|
129
126
|
end
|
130
127
|
|
131
128
|
# @private
|
132
129
|
def on_reject(reason) # :nodoc:
|
133
|
-
@
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
130
|
+
@mutex.synchronize do
|
131
|
+
if pending?
|
132
|
+
@state = :rejected
|
133
|
+
@reason = reason
|
134
|
+
self.try_rescue(reason)
|
135
|
+
@value = nil
|
136
|
+
end
|
137
|
+
@children.each{|child| child.on_reject(reason) }
|
138
|
+
end
|
138
139
|
end
|
139
140
|
|
140
141
|
# @private
|
141
|
-
def try_rescue(ex
|
142
|
-
|
143
|
-
rescuer
|
144
|
-
if rescuer
|
145
|
-
rescuer.block.call(ex)
|
146
|
-
@rescued = true
|
147
|
-
end
|
142
|
+
def try_rescue(ex) # :nodoc:
|
143
|
+
rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
|
144
|
+
rescuer.block.call(ex) if rescuer
|
148
145
|
rescue Exception => e
|
149
146
|
# supress
|
150
147
|
end
|
151
148
|
|
152
149
|
# @private
|
153
150
|
def realize(*args) # :nodoc:
|
154
|
-
|
151
|
+
Thread.new(@chain, @mutex, args) do |chain, mutex, args|
|
155
152
|
result = args.length == 1 ? args.first : args
|
156
153
|
index = 0
|
157
154
|
loop do
|
158
|
-
|
155
|
+
Thread.pass
|
156
|
+
current = mutex.synchronize{ chain[index] }
|
159
157
|
unless current.rejected?
|
160
|
-
current.
|
158
|
+
current.semaphore.synchronize do
|
161
159
|
begin
|
162
160
|
result = current.on_fulfill(result)
|
163
161
|
rescue Exception => ex
|
@@ -166,9 +164,33 @@ module Concurrent
|
|
166
164
|
end
|
167
165
|
end
|
168
166
|
index += 1
|
169
|
-
|
167
|
+
sleep while index >= chain.length
|
170
168
|
end
|
171
169
|
end
|
172
170
|
end
|
173
171
|
end
|
174
172
|
end
|
173
|
+
|
174
|
+
module Kernel
|
175
|
+
|
176
|
+
# Creates a new promise object. "A promise represents the eventual
|
177
|
+
# value returned from the single completion of an operation."
|
178
|
+
# Promises can be chained in a tree structure where each promise
|
179
|
+
# has zero or more children. Promises are resolved asynchronously
|
180
|
+
# in the order they are added to the tree. Parents are guaranteed
|
181
|
+
# to be resolved before their children. The result of each promise
|
182
|
+
# is passes to each of its children when the child resolves. When
|
183
|
+
# a promise is rejected all its children will be summarily rejected.
|
184
|
+
# A promise added to a rejected promise will immediately be rejected.
|
185
|
+
# A promise that is neither resolved or rejected is pending.
|
186
|
+
#
|
187
|
+
# @param args [Array] zero or more arguments for the block
|
188
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
189
|
+
#
|
190
|
+
# @see Promise
|
191
|
+
# @see http://wiki.commonjs.org/wiki/Promises/A
|
192
|
+
def promise(*args, &block)
|
193
|
+
return Concurrent::Promise.new(*args, &block)
|
194
|
+
end
|
195
|
+
module_function :promise
|
196
|
+
end
|
@@ -35,7 +35,7 @@ module Concurrent
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def shutdown?
|
38
|
-
return
|
38
|
+
return ! running?
|
39
39
|
end
|
40
40
|
|
41
41
|
def killed?
|
@@ -43,14 +43,10 @@ module Concurrent
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def shutdown
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@status = :shuttingdown
|
51
|
-
@pool.size.times{ @queue << :stop }
|
52
|
-
end
|
53
|
-
end
|
46
|
+
atomic {
|
47
|
+
@pool.size.times{ @queue << :stop }
|
48
|
+
@status = :shuttingdown
|
49
|
+
}
|
54
50
|
end
|
55
51
|
|
56
52
|
def wait_for_termination(timeout = nil)
|
@@ -65,12 +61,5 @@ module Concurrent
|
|
65
61
|
self.post(&block)
|
66
62
|
return self
|
67
63
|
end
|
68
|
-
|
69
|
-
protected
|
70
|
-
|
71
|
-
# @private
|
72
|
-
def mutex # :nodoc:
|
73
|
-
@mutex || Mutex.new
|
74
|
-
end
|
75
64
|
end
|
76
65
|
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`.
|
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
|
-
This approach fits much more cleanly within an
|
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
|
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/promise.md
CHANGED
@@ -28,8 +28,6 @@ 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
|
-
|
33
31
|
## Examples
|
34
32
|
|
35
33
|
Start by requiring promises
|
data/md/thread_pool.md
CHANGED
@@ -151,12 +151,6 @@ $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
|
-
|
160
154
|
### EventMachine
|
161
155
|
|
162
156
|
The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
|
@@ -173,27 +167,6 @@ require 'functional/concurrency'
|
|
173
167
|
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
174
168
|
```
|
175
169
|
|
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
170
|
## Copyright
|
198
171
|
|
199
172
|
*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
|
+
$GLOBAL_THREAD_POOL = CachedThreadPool.new
|
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
|
+
$GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
|
42
42
|
Agent.new(0)
|
43
43
|
end
|
44
44
|
end
|
@@ -92,8 +92,6 @@ 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)
|
97
95
|
before = subject.length
|
98
96
|
subject.post{ nil }
|
99
97
|
subject.post{ nil }
|
@@ -101,8 +99,6 @@ module Concurrent
|
|
101
99
|
end
|
102
100
|
|
103
101
|
it 'does not add to the queue when no block is given' do
|
104
|
-
subject.post{ sleep(100) }
|
105
|
-
sleep(0.1)
|
106
102
|
before = subject.length
|
107
103
|
subject.post
|
108
104
|
subject.post{ nil }
|
@@ -117,8 +113,6 @@ module Concurrent
|
|
117
113
|
end
|
118
114
|
|
119
115
|
it 'should increase by one for each #post' do
|
120
|
-
subject.post{ sleep(100) }
|
121
|
-
sleep(0.1)
|
122
116
|
subject.post{ sleep }
|
123
117
|
subject.post{ sleep }
|
124
118
|
subject.post{ sleep }
|
@@ -381,6 +375,31 @@ module Concurrent
|
|
381
375
|
sleep(0.1)
|
382
376
|
observer.value.should eq 10
|
383
377
|
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
|
384
403
|
end
|
385
404
|
end
|
386
405
|
end
|