concurrent-ruby 0.4.1 → 0.5.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|