concurrent-ruby 0.4.1 → 0.5.0.pre.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 +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/obligation'
|
4
|
+
require 'concurrent/copy_on_write_observer_set'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
MultipleAssignmentError = Class.new(StandardError)
|
9
|
+
|
10
|
+
class IVar
|
11
|
+
include Obligation
|
12
|
+
|
13
|
+
NO_VALUE = Object.new
|
14
|
+
|
15
|
+
# Create a new +Ivar+ in the +:pending+ state with the (optional) initial value.
|
16
|
+
#
|
17
|
+
# @param [Object] value the initial value
|
18
|
+
# @param [Hash] opts the options to create a message with
|
19
|
+
# @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
|
20
|
+
# @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
|
21
|
+
# @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
|
22
|
+
# returning the value returned from the proc
|
23
|
+
def initialize(value = NO_VALUE, opts = {})
|
24
|
+
init_obligation
|
25
|
+
@observers = CopyOnWriteObserverSet.new
|
26
|
+
set_deref_options(opts)
|
27
|
+
|
28
|
+
if value == NO_VALUE
|
29
|
+
@state = :pending
|
30
|
+
else
|
31
|
+
set(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add an observer on this object that will receive notification on update.
|
36
|
+
#
|
37
|
+
# Upon completion the +IVar+ will notify all observers in a thread-say way. The +func+
|
38
|
+
# method of the observer will be called with three arguments: the +Time+ at which the
|
39
|
+
# +Future+ completed the asynchronous operation, the final +value+ (or +nil+ on rejection),
|
40
|
+
# and the final +reason+ (or +nil+ on fulfillment).
|
41
|
+
#
|
42
|
+
# @param [Object] observer the object that will be notified of changes
|
43
|
+
# @param [Symbol] func symbol naming the method to call when this +Observable+ has changes`
|
44
|
+
def add_observer(observer, func = :update)
|
45
|
+
direct_notification = false
|
46
|
+
|
47
|
+
mutex.synchronize do
|
48
|
+
if event.set?
|
49
|
+
direct_notification = true
|
50
|
+
else
|
51
|
+
@observers.add_observer(observer, func)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
observer.send(func, Time.now, self.value, reason) if direct_notification
|
56
|
+
func
|
57
|
+
end
|
58
|
+
|
59
|
+
def set(value)
|
60
|
+
complete(true, value, nil)
|
61
|
+
end
|
62
|
+
|
63
|
+
def fail(reason = nil)
|
64
|
+
complete(false, nil, reason)
|
65
|
+
end
|
66
|
+
|
67
|
+
def complete(success, value, reason)
|
68
|
+
mutex.synchronize do
|
69
|
+
raise MultipleAssignmentError.new('multiple assignment') if [:fulfilled, :rejected].include? @state
|
70
|
+
set_state(success, value, reason)
|
71
|
+
event.set
|
72
|
+
end
|
73
|
+
|
74
|
+
time = Time.now
|
75
|
+
@observers.notify_and_delete_observers{ [time, self.value, reason] }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'concurrent/dereferenceable'
|
2
|
+
require 'concurrent/event'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
class MVar
|
7
|
+
|
8
|
+
include Dereferenceable
|
9
|
+
|
10
|
+
EMPTY = Object.new
|
11
|
+
TIMEOUT = Object.new
|
12
|
+
|
13
|
+
def initialize(value = EMPTY, opts = {})
|
14
|
+
@value = value
|
15
|
+
@mutex = Mutex.new
|
16
|
+
@empty_condition = Condition.new
|
17
|
+
@full_condition = Condition.new
|
18
|
+
set_deref_options(opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def take(timeout = nil)
|
22
|
+
@mutex.synchronize do
|
23
|
+
wait_for_full(timeout)
|
24
|
+
|
25
|
+
# If we timed out we'll still be empty
|
26
|
+
if unlocked_full?
|
27
|
+
value = @value
|
28
|
+
@value = EMPTY
|
29
|
+
@empty_condition.signal
|
30
|
+
apply_deref_options(value)
|
31
|
+
else
|
32
|
+
TIMEOUT
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def put(value, timeout = nil)
|
38
|
+
@mutex.synchronize do
|
39
|
+
wait_for_empty(timeout)
|
40
|
+
|
41
|
+
# If we timed out we won't be empty
|
42
|
+
if unlocked_empty?
|
43
|
+
@value = value
|
44
|
+
@full_condition.signal
|
45
|
+
apply_deref_options(value)
|
46
|
+
else
|
47
|
+
TIMEOUT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def modify(timeout = nil)
|
53
|
+
raise ArgumentError.new('no block given') unless block_given?
|
54
|
+
|
55
|
+
@mutex.synchronize do
|
56
|
+
wait_for_full(timeout)
|
57
|
+
|
58
|
+
# If we timed out we'll still be empty
|
59
|
+
if unlocked_full?
|
60
|
+
value = @value
|
61
|
+
@value = yield value
|
62
|
+
@full_condition.signal
|
63
|
+
apply_deref_options(value)
|
64
|
+
else
|
65
|
+
TIMEOUT
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def try_take!
|
71
|
+
@mutex.synchronize do
|
72
|
+
if unlocked_full?
|
73
|
+
value = @value
|
74
|
+
@value = EMPTY
|
75
|
+
@empty_condition.signal
|
76
|
+
apply_deref_options(value)
|
77
|
+
else
|
78
|
+
EMPTY
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def try_put!(value)
|
84
|
+
@mutex.synchronize do
|
85
|
+
if unlocked_empty?
|
86
|
+
@value = value
|
87
|
+
@full_condition.signal
|
88
|
+
true
|
89
|
+
else
|
90
|
+
false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def set!(value)
|
96
|
+
@mutex.synchronize do
|
97
|
+
old_value = @value
|
98
|
+
@value = value
|
99
|
+
@full_condition.signal
|
100
|
+
apply_deref_options(old_value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def modify!
|
105
|
+
raise ArgumentError.new('no block given') unless block_given?
|
106
|
+
|
107
|
+
@mutex.synchronize do
|
108
|
+
value = @value
|
109
|
+
@value = yield value
|
110
|
+
if unlocked_empty?
|
111
|
+
@empty_condition.signal
|
112
|
+
else
|
113
|
+
@full_condition.signal
|
114
|
+
end
|
115
|
+
apply_deref_options(value)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def empty?
|
120
|
+
@mutex.synchronize { @value == EMPTY }
|
121
|
+
end
|
122
|
+
|
123
|
+
def full?
|
124
|
+
not empty?
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def unlocked_empty?
|
130
|
+
@value == EMPTY
|
131
|
+
end
|
132
|
+
|
133
|
+
def unlocked_full?
|
134
|
+
! unlocked_empty?
|
135
|
+
end
|
136
|
+
|
137
|
+
def wait_for_full(timeout)
|
138
|
+
wait_while(@full_condition, timeout) { unlocked_empty? }
|
139
|
+
end
|
140
|
+
|
141
|
+
def wait_for_empty(timeout)
|
142
|
+
wait_while(@empty_condition, timeout) { unlocked_full? }
|
143
|
+
end
|
144
|
+
|
145
|
+
def wait_while(condition, timeout)
|
146
|
+
remaining = Condition::Result.new(timeout)
|
147
|
+
while yield && remaining.can_wait?
|
148
|
+
remaining = condition.wait(@mutex, remaining.remaining_time)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -9,31 +9,116 @@ module Concurrent
|
|
9
9
|
module Obligation
|
10
10
|
include Dereferenceable
|
11
11
|
|
12
|
-
attr_reader :state
|
13
|
-
attr_reader :reason
|
14
|
-
|
15
12
|
# Has the obligation been fulfilled?
|
16
13
|
# @return [Boolean]
|
17
|
-
def fulfilled?
|
14
|
+
def fulfilled?
|
15
|
+
state == :fulfilled
|
16
|
+
end
|
18
17
|
alias_method :realized?, :fulfilled?
|
19
18
|
|
20
19
|
# Has the obligation been rejected?
|
21
20
|
# @return [Boolean]
|
22
|
-
def rejected?
|
21
|
+
def rejected?
|
22
|
+
state == :rejected
|
23
|
+
end
|
23
24
|
|
24
25
|
# Is obligation completion still pending?
|
25
26
|
# @return [Boolean]
|
26
|
-
def pending?
|
27
|
+
def pending?
|
28
|
+
state == :pending
|
29
|
+
end
|
30
|
+
|
31
|
+
# Is the obligation still unscheduled?
|
32
|
+
# @return [Boolean]
|
33
|
+
def unscheduled?
|
34
|
+
state == :unscheduled
|
35
|
+
end
|
36
|
+
|
37
|
+
def completed?
|
38
|
+
[:fulfilled, :rejected].include? state
|
39
|
+
end
|
40
|
+
|
41
|
+
def incomplete?
|
42
|
+
[:unscheduled, :pending].include? state
|
43
|
+
end
|
27
44
|
|
28
45
|
def value(timeout = nil)
|
29
|
-
event.wait(timeout)
|
46
|
+
event.wait(timeout) if timeout != 0 && incomplete?
|
30
47
|
super()
|
31
48
|
end
|
32
49
|
|
50
|
+
def state
|
51
|
+
mutex.synchronize { @state }
|
52
|
+
end
|
53
|
+
|
54
|
+
def reason
|
55
|
+
mutex.synchronize { @reason }
|
56
|
+
end
|
57
|
+
|
33
58
|
protected
|
34
59
|
|
35
|
-
|
36
|
-
|
60
|
+
# @!visibility private
|
61
|
+
def init_obligation # :nodoc:
|
62
|
+
init_mutex
|
63
|
+
@event = Event.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
def event # :nodoc:
|
68
|
+
@event
|
69
|
+
end
|
70
|
+
|
71
|
+
# @!visibility private
|
72
|
+
def set_state(success, value, reason) # :nodoc:
|
73
|
+
if success
|
74
|
+
@value = value
|
75
|
+
@state = :fulfilled
|
76
|
+
else
|
77
|
+
@reason = reason
|
78
|
+
@state = :rejected
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!visibility private
|
83
|
+
def state=(value) # :nodoc:
|
84
|
+
mutex.synchronize { @state = value }
|
85
|
+
end
|
86
|
+
|
87
|
+
# atomic compare and set operation
|
88
|
+
# state is set to next_state only if current state is == expected_current
|
89
|
+
#
|
90
|
+
# @param [Symbol] next_state
|
91
|
+
# @param [Symbol] expected_current
|
92
|
+
#
|
93
|
+
# @return [Boolean] true is state is changed, false otherwise
|
94
|
+
#
|
95
|
+
# @!visibility private
|
96
|
+
def compare_and_set_state(next_state, expected_current) # :nodoc:
|
97
|
+
mutex.synchronize do
|
98
|
+
if @state == expected_current
|
99
|
+
@state = next_state
|
100
|
+
true
|
101
|
+
else
|
102
|
+
false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# executes the block within mutex if current state is included in expected_states
|
108
|
+
#
|
109
|
+
# @return block value if executed, false otherwise
|
110
|
+
#
|
111
|
+
# @!visibility private
|
112
|
+
def if_state(*expected_states) # :nodoc:
|
113
|
+
raise ArgumentError.new('no block given') unless block_given?
|
114
|
+
|
115
|
+
mutex.synchronize do
|
116
|
+
if expected_states.include? @state
|
117
|
+
yield
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
37
122
|
end
|
38
123
|
end
|
39
124
|
end
|
data/lib/concurrent/postable.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'concurrent/event'
|
2
|
+
|
1
3
|
module Concurrent
|
2
4
|
|
3
5
|
module Postable
|
@@ -37,20 +39,20 @@ module Concurrent
|
|
37
39
|
raise ArgumentError.new('empty message') if message.empty?
|
38
40
|
return false unless ready?
|
39
41
|
queue.push(Package.new(message))
|
40
|
-
|
42
|
+
true
|
41
43
|
end
|
42
44
|
|
43
45
|
def <<(message)
|
44
46
|
post(*message)
|
45
|
-
|
47
|
+
self
|
46
48
|
end
|
47
49
|
|
48
50
|
def post?(*message)
|
49
51
|
raise ArgumentError.new('empty message') if message.empty?
|
50
52
|
return nil unless ready?
|
51
|
-
|
52
|
-
queue.push(Package.new(message,
|
53
|
-
|
53
|
+
ivar = IVar.new
|
54
|
+
queue.push(Package.new(message, ivar))
|
55
|
+
ivar
|
54
56
|
end
|
55
57
|
|
56
58
|
def post!(seconds, *message)
|
@@ -77,20 +79,20 @@ module Concurrent
|
|
77
79
|
raise ArgumentError.new('empty message') if message.empty?
|
78
80
|
return false unless ready?
|
79
81
|
queue.push(Package.new(message, receiver))
|
80
|
-
|
82
|
+
queue.length
|
81
83
|
end
|
82
84
|
|
83
85
|
def ready?
|
84
86
|
if self.respond_to?(:running?) && ! running?
|
85
|
-
|
87
|
+
false
|
86
88
|
else
|
87
|
-
|
89
|
+
true
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
93
|
private
|
92
94
|
|
93
|
-
#
|
95
|
+
# @!visibility private
|
94
96
|
def queue # :nodoc:
|
95
97
|
@queue ||= Queue.new
|
96
98
|
end
|
data/lib/concurrent/promise.rb
CHANGED
@@ -9,163 +9,137 @@ module Concurrent
|
|
9
9
|
include Obligation
|
10
10
|
include UsesGlobalThreadPool
|
11
11
|
|
12
|
-
# Creates a new promise object. "A promise represents the eventual
|
13
|
-
# value returned from the single completion of an operation."
|
14
|
-
# Promises can be chained in a tree structure where each promise
|
15
|
-
# has zero or more children. Promises are resolved asynchronously
|
16
|
-
# in the order they are added to the tree. Parents are guaranteed
|
17
|
-
# to be resolved before their children. The result of each promise
|
18
|
-
# is passed to each of its children upon resolution. When
|
19
|
-
# a promise is rejected all its children will be summarily rejected.
|
20
|
-
# A promise that is neither resolved or rejected is pending.
|
21
|
-
#
|
22
|
-
# @param args [Array] zero or more arguments for the block
|
23
|
-
# @param block [Proc] the block to call when attempting fulfillment
|
24
|
-
#
|
25
12
|
# @see http://wiki.commonjs.org/wiki/Promises/A
|
26
13
|
# @see http://promises-aplus.github.io/promises-spec/
|
27
|
-
def initialize(
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
14
|
+
def initialize(options = {}, &block)
|
15
|
+
options.delete_if {|k, v| v.nil?}
|
16
|
+
|
17
|
+
@parent = options.fetch(:parent) { nil }
|
18
|
+
@on_fulfill = options.fetch(:on_fulfill) { Proc.new{ |result| result } }
|
19
|
+
@on_reject = options.fetch(:on_reject) { Proc.new{ |reason| raise reason } }
|
34
20
|
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@state = :pending
|
38
|
-
@value = nil
|
39
|
-
@reason = nil
|
40
|
-
@rescued = false
|
21
|
+
@promise_body = block || Proc.new{|result| result }
|
22
|
+
@state = :unscheduled
|
41
23
|
@children = []
|
42
|
-
@rescuers = []
|
43
24
|
|
44
|
-
|
45
|
-
realize(*args) if root?
|
25
|
+
init_obligation
|
46
26
|
end
|
47
27
|
|
48
|
-
|
49
|
-
|
28
|
+
# @return [Promise]
|
29
|
+
def self.fulfill(value)
|
30
|
+
Promise.new.tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
|
50
31
|
end
|
51
32
|
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# @return [Promise]
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
@children.last
|
66
|
-
end
|
67
|
-
return child
|
68
|
-
end
|
69
|
-
|
70
|
-
# Add a rescue handler to be run if the promise is rejected (via raised
|
71
|
-
# exception). Multiple rescue handlers may be added to a Promise.
|
72
|
-
# Rescue blocks will be checked in order and the first one with a
|
73
|
-
# matching Exception class will be processed. The block argument
|
74
|
-
# will be the exception that caused the rejection.
|
75
|
-
#
|
76
|
-
# @param clazz [Class] The class of exception to rescue
|
77
|
-
# @param block [Proc] the block to call if the rescue is matched
|
78
|
-
#
|
79
|
-
# @return [self] so that additional chaining can occur
|
80
|
-
def rescue(clazz = nil, &block)
|
81
|
-
return self if fulfilled? || rescued? || block.nil?
|
82
|
-
@lock.synchronize do
|
83
|
-
rescuer = Rescuer.new(clazz, block)
|
84
|
-
if pending?
|
85
|
-
@rescuers << rescuer
|
86
|
-
else
|
87
|
-
try_rescue(reason, rescuer)
|
33
|
+
|
34
|
+
# @return [Promise]
|
35
|
+
def self.reject(reason)
|
36
|
+
Promise.new.tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Promise]
|
40
|
+
# @since 0.5.0
|
41
|
+
def execute
|
42
|
+
if root?
|
43
|
+
if compare_and_set_state(:pending, :unscheduled)
|
44
|
+
set_pending
|
45
|
+
realize(@promise_body)
|
88
46
|
end
|
47
|
+
else
|
48
|
+
@parent.execute
|
89
49
|
end
|
90
|
-
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# @since 0.5.0
|
54
|
+
def self.execute(&block)
|
55
|
+
new(&block).execute
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# @return [Promise] the new promise
|
60
|
+
def then(rescuer = nil, &block)
|
61
|
+
raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
|
62
|
+
block = Proc.new{ |result| result } if block.nil?
|
63
|
+
child = Promise.new(parent: self, on_fulfill: block, on_reject: rescuer)
|
64
|
+
|
65
|
+
mutex.synchronize do
|
66
|
+
child.state = :pending if @state == :pending
|
67
|
+
child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
|
68
|
+
child.on_reject(@reason) if @state == :rejected
|
69
|
+
@children << child
|
70
|
+
end
|
71
|
+
|
72
|
+
child
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Promise]
|
76
|
+
def on_success(&block)
|
77
|
+
raise ArgumentError.new('no block given') unless block_given?
|
78
|
+
self.then &block
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Promise]
|
82
|
+
def rescue(&block)
|
83
|
+
self.then(block)
|
91
84
|
end
|
92
85
|
alias_method :catch, :rescue
|
93
86
|
alias_method :on_error, :rescue
|
94
87
|
|
95
88
|
protected
|
96
89
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
90
|
+
def set_pending
|
91
|
+
mutex.synchronize do
|
92
|
+
@state = :pending
|
93
|
+
@children.each { |c| c.set_pending }
|
94
|
+
end
|
95
|
+
end
|
103
96
|
|
104
|
-
#
|
97
|
+
# @!visibility private
|
105
98
|
def root? # :nodoc:
|
106
99
|
@parent.nil?
|
107
100
|
end
|
108
101
|
|
109
|
-
#
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
else
|
114
|
-
@parent.push(promise)
|
115
|
-
end
|
102
|
+
# @!visibility private
|
103
|
+
def on_fulfill(result)
|
104
|
+
realize Proc.new{ @on_fulfill.call(result) }
|
105
|
+
nil
|
116
106
|
end
|
117
107
|
|
118
|
-
#
|
119
|
-
def
|
120
|
-
@
|
121
|
-
|
122
|
-
@state = :fulfilled
|
123
|
-
@reason = nil
|
124
|
-
end
|
125
|
-
return self.value
|
108
|
+
# @!visibility private
|
109
|
+
def on_reject(reason)
|
110
|
+
realize Proc.new{ @on_reject.call(reason) }
|
111
|
+
nil
|
126
112
|
end
|
127
113
|
|
128
|
-
|
129
|
-
|
130
|
-
@
|
131
|
-
@state = :rejected
|
132
|
-
@reason = reason
|
133
|
-
try_rescue(reason)
|
134
|
-
@children.each{|child| child.on_reject(reason) }
|
114
|
+
def notify_child(child)
|
115
|
+
if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
|
116
|
+
if_state(:rejected) { child.on_reject(@reason) }
|
135
117
|
end
|
136
118
|
|
137
|
-
#
|
138
|
-
def
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
rescue Exception => ex
|
146
|
-
# supress
|
147
|
-
end
|
148
|
-
|
149
|
-
# @private
|
150
|
-
def realize(*args) # :nodoc:
|
151
|
-
Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
|
152
|
-
result = args.length == 1 ? args.first : args
|
153
|
-
index = 0
|
154
|
-
loop do
|
155
|
-
current = lock.synchronize{ chain[index] }
|
156
|
-
unless current.rejected?
|
157
|
-
begin
|
158
|
-
result = current.on_fulfill(result)
|
159
|
-
rescue Exception => ex
|
160
|
-
current.on_reject(ex)
|
161
|
-
ensure
|
162
|
-
event.set
|
163
|
-
end
|
164
|
-
end
|
165
|
-
index += 1
|
166
|
-
Thread.pass while index >= chain.length
|
119
|
+
# @!visibility private
|
120
|
+
def realize(task)
|
121
|
+
Promise.thread_pool.post do
|
122
|
+
success, value, reason = SafeTaskExecutor.new( task ).execute
|
123
|
+
|
124
|
+
children_to_notify = mutex.synchronize do
|
125
|
+
set_state!(success, value, reason)
|
126
|
+
@children.dup
|
167
127
|
end
|
128
|
+
|
129
|
+
children_to_notify.each{ |child| notify_child(child) }
|
168
130
|
end
|
169
131
|
end
|
132
|
+
|
133
|
+
def set_state!(success, value, reason)
|
134
|
+
set_state(success, value, reason)
|
135
|
+
event.set
|
136
|
+
end
|
137
|
+
|
138
|
+
def synchronized_set_state!(success, value, reason)
|
139
|
+
mutex.synchronize do
|
140
|
+
set_state!(success, value, reason)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
170
144
|
end
|
171
145
|
end
|