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.
- checksums.yaml +7 -0
- data/LICENSE +21 -21
- data/README.md +276 -275
- data/lib/concurrent.rb +28 -28
- data/lib/concurrent/agent.rb +114 -114
- data/lib/concurrent/cached_thread_pool.rb +131 -131
- data/lib/concurrent/defer.rb +65 -65
- data/lib/concurrent/event.rb +60 -60
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
- data/lib/concurrent/executor.rb +96 -96
- data/lib/concurrent/fixed_thread_pool.rb +99 -99
- data/lib/concurrent/functions.rb +120 -120
- data/lib/concurrent/future.rb +42 -42
- data/lib/concurrent/global_thread_pool.rb +24 -16
- data/lib/concurrent/goroutine.rb +29 -29
- data/lib/concurrent/null_thread_pool.rb +22 -22
- data/lib/concurrent/obligation.rb +67 -67
- data/lib/concurrent/promise.rb +174 -174
- data/lib/concurrent/reactor.rb +166 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
- data/lib/concurrent/supervisor.rb +105 -105
- data/lib/concurrent/thread_pool.rb +76 -76
- data/lib/concurrent/utilities.rb +32 -32
- data/lib/concurrent/version.rb +3 -3
- data/lib/concurrent_ruby.rb +1 -1
- data/md/agent.md +123 -123
- data/md/defer.md +174 -174
- data/md/event.md +32 -32
- data/md/executor.md +187 -187
- data/md/future.md +83 -83
- data/md/goroutine.md +52 -52
- data/md/obligation.md +32 -32
- data/md/promise.md +227 -227
- data/md/thread_pool.md +224 -224
- data/spec/concurrent/agent_spec.rb +390 -386
- data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
- data/spec/concurrent/defer_spec.rb +199 -195
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
- data/spec/concurrent/event_spec.rb +134 -134
- data/spec/concurrent/executor_spec.rb +200 -200
- data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
- data/spec/concurrent/functions_spec.rb +217 -217
- data/spec/concurrent/future_spec.rb +112 -108
- data/spec/concurrent/global_thread_pool_spec.rb +11 -38
- data/spec/concurrent/goroutine_spec.rb +67 -67
- data/spec/concurrent/null_thread_pool_spec.rb +57 -57
- data/spec/concurrent/obligation_shared.rb +132 -132
- data/spec/concurrent/promise_spec.rb +316 -312
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
- data/spec/concurrent/reactor_spec.rb +364 -364
- data/spec/concurrent/supervisor_spec.rb +269 -269
- data/spec/concurrent/thread_pool_shared.rb +204 -204
- data/spec/concurrent/uses_global_thread_pool_shared.rb +64 -0
- data/spec/concurrent/utilities_spec.rb +74 -74
- data/spec/spec_helper.rb +32 -32
- metadata +17 -19
@@ -1,16 +1,24 @@
|
|
1
|
-
require 'concurrent/cached_thread_pool'
|
2
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
require 'concurrent/cached_thread_pool'
|
2
|
+
|
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
|
+
def thread_pool
|
12
|
+
@thread_pool || $GLOBAL_THREAD_POOL
|
13
|
+
end
|
14
|
+
def thread_pool=(pool)
|
15
|
+
if pool == $GLOBAL_THREAD_POOL
|
16
|
+
@thread_pool = nil
|
17
|
+
else
|
18
|
+
@thread_pool = pool
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/concurrent/goroutine.rb
CHANGED
@@ -1,29 +1,29 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/global_thread_pool'
|
4
|
-
|
5
|
-
module Kernel
|
6
|
-
|
7
|
-
# Post the given agruments and block to the Global Thread Pool.
|
8
|
-
#
|
9
|
-
# @param args [Array] zero or more arguments for the block
|
10
|
-
# @param block [Proc] operation to be performed concurrently
|
11
|
-
#
|
12
|
-
# @return [true,false] success/failre of thread creation
|
13
|
-
#
|
14
|
-
# @note Althought based on Go's goroutines and Erlang's spawn/1,
|
15
|
-
# Ruby has a vastly different runtime. Threads aren't nearly as
|
16
|
-
# efficient in Ruby. Use this function appropriately.
|
17
|
-
#
|
18
|
-
# @see http://golang.org/doc/effective_go.html#goroutines
|
19
|
-
# @see https://gobyexample.com/goroutines
|
20
|
-
def go(*args, &block)
|
21
|
-
return false unless block_given?
|
22
|
-
if args.first.behaves_as?(:global_thread_pool)
|
23
|
-
args.first.post(*args.slice(1, args.length), &block)
|
24
|
-
else
|
25
|
-
$GLOBAL_THREAD_POOL.post(*args, &block)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
module_function :go
|
29
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
|
7
|
+
# Post the given agruments and block to the Global Thread Pool.
|
8
|
+
#
|
9
|
+
# @param args [Array] zero or more arguments for the block
|
10
|
+
# @param block [Proc] operation to be performed concurrently
|
11
|
+
#
|
12
|
+
# @return [true,false] success/failre of thread creation
|
13
|
+
#
|
14
|
+
# @note Althought based on Go's goroutines and Erlang's spawn/1,
|
15
|
+
# Ruby has a vastly different runtime. Threads aren't nearly as
|
16
|
+
# efficient in Ruby. Use this function appropriately.
|
17
|
+
#
|
18
|
+
# @see http://golang.org/doc/effective_go.html#goroutines
|
19
|
+
# @see https://gobyexample.com/goroutines
|
20
|
+
def go(*args, &block)
|
21
|
+
return false unless block_given?
|
22
|
+
if args.first.behaves_as?(:global_thread_pool)
|
23
|
+
args.first.post(*args.slice(1, args.length), &block)
|
24
|
+
else
|
25
|
+
$GLOBAL_THREAD_POOL.post(*args, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module_function :go
|
29
|
+
end
|
@@ -1,22 +1,22 @@
|
|
1
|
-
require 'concurrent/global_thread_pool'
|
2
|
-
|
3
|
-
module Concurrent
|
4
|
-
|
5
|
-
class NullThreadPool
|
6
|
-
behavior(:global_thread_pool)
|
7
|
-
|
8
|
-
def self.post(*args, &block)
|
9
|
-
Thread.new(*args, &block).abort_on_exception = false
|
10
|
-
return true
|
11
|
-
end
|
12
|
-
|
13
|
-
def post(*args, &block)
|
14
|
-
return NullThreadPool.post(*args, &block)
|
15
|
-
end
|
16
|
-
|
17
|
-
def <<(block)
|
18
|
-
NullThreadPool.post(&block)
|
19
|
-
return self
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
1
|
+
require 'concurrent/global_thread_pool'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
class NullThreadPool
|
6
|
+
behavior(:global_thread_pool)
|
7
|
+
|
8
|
+
def self.post(*args, &block)
|
9
|
+
Thread.new(*args, &block).abort_on_exception = false
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(*args, &block)
|
14
|
+
return NullThreadPool.post(*args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(block)
|
18
|
+
NullThreadPool.post(&block)
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,67 +1,67 @@
|
|
1
|
-
require 'thread'
|
2
|
-
require 'timeout'
|
3
|
-
|
4
|
-
require 'functional/behavior'
|
5
|
-
|
6
|
-
behavior_info(:future,
|
7
|
-
state: 0,
|
8
|
-
value: -1,
|
9
|
-
reason: 0,
|
10
|
-
pending?: 0,
|
11
|
-
fulfilled?: 0,
|
12
|
-
rejected?: 0)
|
13
|
-
|
14
|
-
behavior_info(:promise,
|
15
|
-
state: 0,
|
16
|
-
value: -1,
|
17
|
-
reason: 0,
|
18
|
-
pending?: 0,
|
19
|
-
fulfilled?: 0,
|
20
|
-
rejected?: 0,
|
21
|
-
then: 0,
|
22
|
-
rescue: -1)
|
23
|
-
|
24
|
-
module Concurrent
|
25
|
-
|
26
|
-
module Obligation
|
27
|
-
|
28
|
-
attr_reader :state
|
29
|
-
attr_reader :reason
|
30
|
-
|
31
|
-
# Has the obligation been fulfilled?
|
32
|
-
# @return [Boolean]
|
33
|
-
def fulfilled?() return(@state == :fulfilled); end
|
34
|
-
alias_method :realized?, :fulfilled?
|
35
|
-
|
36
|
-
# Has the promise been rejected?
|
37
|
-
# @return [Boolean]
|
38
|
-
def rejected?() return(@state == :rejected); end
|
39
|
-
|
40
|
-
# Is obligation completion still pending?
|
41
|
-
# @return [Boolean]
|
42
|
-
def pending?() return(@state == :pending); end
|
43
|
-
|
44
|
-
def value(timeout = nil)
|
45
|
-
if timeout == 0 || ! pending?
|
46
|
-
return @value
|
47
|
-
elsif timeout.nil?
|
48
|
-
return mutex.synchronize { v = @value }
|
49
|
-
else
|
50
|
-
begin
|
51
|
-
return Timeout::timeout(timeout.to_f) {
|
52
|
-
mutex.synchronize { v = @value }
|
53
|
-
}
|
54
|
-
rescue Timeout::Error => ex
|
55
|
-
return nil
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
alias_method :deref, :value
|
60
|
-
|
61
|
-
protected
|
62
|
-
|
63
|
-
def mutex
|
64
|
-
@mutex ||= Mutex.new
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
require 'functional/behavior'
|
5
|
+
|
6
|
+
behavior_info(:future,
|
7
|
+
state: 0,
|
8
|
+
value: -1,
|
9
|
+
reason: 0,
|
10
|
+
pending?: 0,
|
11
|
+
fulfilled?: 0,
|
12
|
+
rejected?: 0)
|
13
|
+
|
14
|
+
behavior_info(:promise,
|
15
|
+
state: 0,
|
16
|
+
value: -1,
|
17
|
+
reason: 0,
|
18
|
+
pending?: 0,
|
19
|
+
fulfilled?: 0,
|
20
|
+
rejected?: 0,
|
21
|
+
then: 0,
|
22
|
+
rescue: -1)
|
23
|
+
|
24
|
+
module Concurrent
|
25
|
+
|
26
|
+
module Obligation
|
27
|
+
|
28
|
+
attr_reader :state
|
29
|
+
attr_reader :reason
|
30
|
+
|
31
|
+
# Has the obligation been fulfilled?
|
32
|
+
# @return [Boolean]
|
33
|
+
def fulfilled?() return(@state == :fulfilled); end
|
34
|
+
alias_method :realized?, :fulfilled?
|
35
|
+
|
36
|
+
# Has the promise been rejected?
|
37
|
+
# @return [Boolean]
|
38
|
+
def rejected?() return(@state == :rejected); end
|
39
|
+
|
40
|
+
# Is obligation completion still pending?
|
41
|
+
# @return [Boolean]
|
42
|
+
def pending?() return(@state == :pending); end
|
43
|
+
|
44
|
+
def value(timeout = nil)
|
45
|
+
if timeout == 0 || ! pending?
|
46
|
+
return @value
|
47
|
+
elsif timeout.nil?
|
48
|
+
return mutex.synchronize { v = @value }
|
49
|
+
else
|
50
|
+
begin
|
51
|
+
return Timeout::timeout(timeout.to_f) {
|
52
|
+
mutex.synchronize { v = @value }
|
53
|
+
}
|
54
|
+
rescue Timeout::Error => ex
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias_method :deref, :value
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def mutex
|
64
|
+
@mutex ||= Mutex.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/concurrent/promise.rb
CHANGED
@@ -1,174 +1,174 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/global_thread_pool'
|
4
|
-
require 'concurrent/obligation'
|
5
|
-
require 'concurrent/utilities'
|
6
|
-
|
7
|
-
module Concurrent
|
8
|
-
|
9
|
-
class Promise
|
10
|
-
include Obligation
|
11
|
-
include UsesGlobalThreadPool
|
12
|
-
|
13
|
-
behavior(:future)
|
14
|
-
behavior(:promise)
|
15
|
-
|
16
|
-
# Creates a new promise object. "A promise represents the eventual
|
17
|
-
# value returned from the single completion of an operation."
|
18
|
-
# Promises can be chained in a tree structure where each promise
|
19
|
-
# has zero or more children. Promises are resolved asynchronously
|
20
|
-
# in the order they are added to the tree. Parents are guaranteed
|
21
|
-
# to be resolved before their children. The result of each promise
|
22
|
-
# is passed to each of its children upon resolution. When
|
23
|
-
# a promise is rejected all its children will be summarily rejected.
|
24
|
-
# A promise that is neither resolved or rejected is pending.
|
25
|
-
#
|
26
|
-
# @param args [Array] zero or more arguments for the block
|
27
|
-
# @param block [Proc] the block to call when attempting fulfillment
|
28
|
-
#
|
29
|
-
# @see http://wiki.commonjs.org/wiki/Promises/A
|
30
|
-
# @see http://promises-aplus.github.io/promises-spec/
|
31
|
-
def initialize(*args, &block)
|
32
|
-
if args.first.is_a?(Promise)
|
33
|
-
@parent = args.first
|
34
|
-
else
|
35
|
-
@parent = nil
|
36
|
-
@chain = [self]
|
37
|
-
end
|
38
|
-
|
39
|
-
@lock = Mutex.new
|
40
|
-
@handler = block || Proc.new{|result| result }
|
41
|
-
@state = :pending
|
42
|
-
@value = nil
|
43
|
-
@reason = nil
|
44
|
-
@rescued = false
|
45
|
-
@children = []
|
46
|
-
@rescuers = []
|
47
|
-
|
48
|
-
realize(*args) if root?
|
49
|
-
end
|
50
|
-
|
51
|
-
def rescued?
|
52
|
-
return @rescued
|
53
|
-
end
|
54
|
-
|
55
|
-
# Create a new child Promise. The block argument for the child will
|
56
|
-
# be the result of fulfilling its parent. If the child will
|
57
|
-
# immediately be rejected if the parent has already been rejected.
|
58
|
-
#
|
59
|
-
# @param block [Proc] the block to call when attempting fulfillment
|
60
|
-
#
|
61
|
-
# @return [Promise] the new promise
|
62
|
-
def then(&block)
|
63
|
-
child = @lock.synchronize do
|
64
|
-
block = Proc.new{|result| result } unless block_given?
|
65
|
-
@children << Promise.new(self, &block)
|
66
|
-
@children.last.on_reject(@reason) if rejected?
|
67
|
-
push(@children.last)
|
68
|
-
@children.last
|
69
|
-
end
|
70
|
-
return child
|
71
|
-
end
|
72
|
-
|
73
|
-
# Add a rescue handler to be run if the promise is rejected (via raised
|
74
|
-
# exception). Multiple rescue handlers may be added to a Promise.
|
75
|
-
# Rescue blocks will be checked in order and the first one with a
|
76
|
-
# matching Exception class will be processed. The block argument
|
77
|
-
# will be the exception that caused the rejection.
|
78
|
-
#
|
79
|
-
# @param clazz [Class] The class of exception to rescue
|
80
|
-
# @param block [Proc] the block to call if the rescue is matched
|
81
|
-
#
|
82
|
-
# @return [self] so that additional chaining can occur
|
83
|
-
def rescue(clazz = Exception, &block)
|
84
|
-
return self if fulfilled? || rescued? || ! block_given?
|
85
|
-
@lock.synchronize do
|
86
|
-
rescuer = Rescuer.new(clazz, block)
|
87
|
-
if pending?
|
88
|
-
@rescuers << rescuer
|
89
|
-
else
|
90
|
-
try_rescue(reason, rescuer)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
return self
|
94
|
-
end
|
95
|
-
alias_method :catch, :rescue
|
96
|
-
alias_method :on_error, :rescue
|
97
|
-
|
98
|
-
protected
|
99
|
-
|
100
|
-
attr_reader :parent
|
101
|
-
attr_reader :handler
|
102
|
-
attr_reader :rescuers
|
103
|
-
|
104
|
-
# @private
|
105
|
-
Rescuer = Struct.new(:clazz, :block)
|
106
|
-
|
107
|
-
# @private
|
108
|
-
def root? # :nodoc:
|
109
|
-
@parent.nil?
|
110
|
-
end
|
111
|
-
|
112
|
-
# @private
|
113
|
-
def push(promise) # :nodoc:
|
114
|
-
if root?
|
115
|
-
@chain << promise
|
116
|
-
else
|
117
|
-
@parent.push(promise)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# @private
|
122
|
-
def on_fulfill(value) # :nodoc:
|
123
|
-
@lock.synchronize do
|
124
|
-
@value = @handler.call(value)
|
125
|
-
@state = :fulfilled
|
126
|
-
@reason = nil
|
127
|
-
end
|
128
|
-
return @value
|
129
|
-
end
|
130
|
-
|
131
|
-
# @private
|
132
|
-
def on_reject(reason) # :nodoc:
|
133
|
-
@value = nil
|
134
|
-
@state = :rejected
|
135
|
-
@reason = reason
|
136
|
-
try_rescue(reason)
|
137
|
-
@children.each{|child| child.on_reject(reason) }
|
138
|
-
end
|
139
|
-
|
140
|
-
# @private
|
141
|
-
def try_rescue(ex, *rescuers) # :nodoc:
|
142
|
-
rescuers = @rescuers if rescuers.empty?
|
143
|
-
rescuer = rescuers.find{|r| ex.is_a?(r.clazz) }
|
144
|
-
if rescuer
|
145
|
-
rescuer.block.call(ex)
|
146
|
-
@rescued = true
|
147
|
-
end
|
148
|
-
rescue Exception => e
|
149
|
-
# supress
|
150
|
-
end
|
151
|
-
|
152
|
-
# @private
|
153
|
-
def realize(*args) # :nodoc:
|
154
|
-
Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
|
155
|
-
result = args.length == 1 ? args.first : args
|
156
|
-
index = 0
|
157
|
-
loop do
|
158
|
-
current = lock.synchronize{ chain[index] }
|
159
|
-
unless current.rejected?
|
160
|
-
current.mutex.synchronize do
|
161
|
-
begin
|
162
|
-
result = current.on_fulfill(result)
|
163
|
-
rescue Exception => ex
|
164
|
-
current.on_reject(ex)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
index += 1
|
169
|
-
Thread.pass while index >= chain.length
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
require 'concurrent/obligation'
|
5
|
+
require 'concurrent/utilities'
|
6
|
+
|
7
|
+
module Concurrent
|
8
|
+
|
9
|
+
class Promise
|
10
|
+
include Obligation
|
11
|
+
include UsesGlobalThreadPool
|
12
|
+
|
13
|
+
behavior(:future)
|
14
|
+
behavior(:promise)
|
15
|
+
|
16
|
+
# Creates a new promise object. "A promise represents the eventual
|
17
|
+
# value returned from the single completion of an operation."
|
18
|
+
# Promises can be chained in a tree structure where each promise
|
19
|
+
# has zero or more children. Promises are resolved asynchronously
|
20
|
+
# in the order they are added to the tree. Parents are guaranteed
|
21
|
+
# to be resolved before their children. The result of each promise
|
22
|
+
# is passed to each of its children upon resolution. When
|
23
|
+
# a promise is rejected all its children will be summarily rejected.
|
24
|
+
# A promise that is neither resolved or rejected is pending.
|
25
|
+
#
|
26
|
+
# @param args [Array] zero or more arguments for the block
|
27
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
28
|
+
#
|
29
|
+
# @see http://wiki.commonjs.org/wiki/Promises/A
|
30
|
+
# @see http://promises-aplus.github.io/promises-spec/
|
31
|
+
def initialize(*args, &block)
|
32
|
+
if args.first.is_a?(Promise)
|
33
|
+
@parent = args.first
|
34
|
+
else
|
35
|
+
@parent = nil
|
36
|
+
@chain = [self]
|
37
|
+
end
|
38
|
+
|
39
|
+
@lock = Mutex.new
|
40
|
+
@handler = block || Proc.new{|result| result }
|
41
|
+
@state = :pending
|
42
|
+
@value = nil
|
43
|
+
@reason = nil
|
44
|
+
@rescued = false
|
45
|
+
@children = []
|
46
|
+
@rescuers = []
|
47
|
+
|
48
|
+
realize(*args) if root?
|
49
|
+
end
|
50
|
+
|
51
|
+
def rescued?
|
52
|
+
return @rescued
|
53
|
+
end
|
54
|
+
|
55
|
+
# Create a new child Promise. The block argument for the child will
|
56
|
+
# be the result of fulfilling its parent. If the child will
|
57
|
+
# immediately be rejected if the parent has already been rejected.
|
58
|
+
#
|
59
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
60
|
+
#
|
61
|
+
# @return [Promise] the new promise
|
62
|
+
def then(&block)
|
63
|
+
child = @lock.synchronize do
|
64
|
+
block = Proc.new{|result| result } unless block_given?
|
65
|
+
@children << Promise.new(self, &block)
|
66
|
+
@children.last.on_reject(@reason) if rejected?
|
67
|
+
push(@children.last)
|
68
|
+
@children.last
|
69
|
+
end
|
70
|
+
return child
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add a rescue handler to be run if the promise is rejected (via raised
|
74
|
+
# exception). Multiple rescue handlers may be added to a Promise.
|
75
|
+
# Rescue blocks will be checked in order and the first one with a
|
76
|
+
# matching Exception class will be processed. The block argument
|
77
|
+
# will be the exception that caused the rejection.
|
78
|
+
#
|
79
|
+
# @param clazz [Class] The class of exception to rescue
|
80
|
+
# @param block [Proc] the block to call if the rescue is matched
|
81
|
+
#
|
82
|
+
# @return [self] so that additional chaining can occur
|
83
|
+
def rescue(clazz = Exception, &block)
|
84
|
+
return self if fulfilled? || rescued? || ! block_given?
|
85
|
+
@lock.synchronize do
|
86
|
+
rescuer = Rescuer.new(clazz, block)
|
87
|
+
if pending?
|
88
|
+
@rescuers << rescuer
|
89
|
+
else
|
90
|
+
try_rescue(reason, rescuer)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
return self
|
94
|
+
end
|
95
|
+
alias_method :catch, :rescue
|
96
|
+
alias_method :on_error, :rescue
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
attr_reader :parent
|
101
|
+
attr_reader :handler
|
102
|
+
attr_reader :rescuers
|
103
|
+
|
104
|
+
# @private
|
105
|
+
Rescuer = Struct.new(:clazz, :block)
|
106
|
+
|
107
|
+
# @private
|
108
|
+
def root? # :nodoc:
|
109
|
+
@parent.nil?
|
110
|
+
end
|
111
|
+
|
112
|
+
# @private
|
113
|
+
def push(promise) # :nodoc:
|
114
|
+
if root?
|
115
|
+
@chain << promise
|
116
|
+
else
|
117
|
+
@parent.push(promise)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# @private
|
122
|
+
def on_fulfill(value) # :nodoc:
|
123
|
+
@lock.synchronize do
|
124
|
+
@value = @handler.call(value)
|
125
|
+
@state = :fulfilled
|
126
|
+
@reason = nil
|
127
|
+
end
|
128
|
+
return @value
|
129
|
+
end
|
130
|
+
|
131
|
+
# @private
|
132
|
+
def on_reject(reason) # :nodoc:
|
133
|
+
@value = nil
|
134
|
+
@state = :rejected
|
135
|
+
@reason = reason
|
136
|
+
try_rescue(reason)
|
137
|
+
@children.each{|child| child.on_reject(reason) }
|
138
|
+
end
|
139
|
+
|
140
|
+
# @private
|
141
|
+
def try_rescue(ex, *rescuers) # :nodoc:
|
142
|
+
rescuers = @rescuers if rescuers.empty?
|
143
|
+
rescuer = rescuers.find{|r| ex.is_a?(r.clazz) }
|
144
|
+
if rescuer
|
145
|
+
rescuer.block.call(ex)
|
146
|
+
@rescued = true
|
147
|
+
end
|
148
|
+
rescue Exception => e
|
149
|
+
# supress
|
150
|
+
end
|
151
|
+
|
152
|
+
# @private
|
153
|
+
def realize(*args) # :nodoc:
|
154
|
+
Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
|
155
|
+
result = args.length == 1 ? args.first : args
|
156
|
+
index = 0
|
157
|
+
loop do
|
158
|
+
current = lock.synchronize{ chain[index] }
|
159
|
+
unless current.rejected?
|
160
|
+
current.mutex.synchronize do
|
161
|
+
begin
|
162
|
+
result = current.on_fulfill(result)
|
163
|
+
rescue Exception => ex
|
164
|
+
current.on_reject(ex)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
index += 1
|
169
|
+
Thread.pass while index >= chain.length
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|