concurrent-ruby-edge 0.2.4 → 0.3.0
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 +28 -38
- data/lib/concurrent-edge.rb +6 -2
- data/lib/concurrent/actor.rb +10 -6
- data/lib/concurrent/actor/behaviour/sets_results.rb +3 -3
- data/lib/concurrent/actor/behaviour/termination.rb +7 -7
- data/lib/concurrent/actor/core.rb +4 -5
- data/lib/concurrent/actor/envelope.rb +2 -2
- data/lib/concurrent/actor/errors.rb +1 -1
- data/lib/concurrent/actor/reference.rb +7 -7
- data/lib/concurrent/actor/utils/pool.rb +2 -2
- data/lib/concurrent/edge/cancellation.rb +137 -0
- data/lib/concurrent/edge/lock_free_linked_set.rb +1 -1
- data/lib/concurrent/edge/lock_free_queue.rb +116 -0
- data/lib/concurrent/edge/lock_free_stack.rb +90 -68
- data/lib/concurrent/edge/old_channel_integration.rb +54 -0
- data/lib/concurrent/edge/promises.rb +2080 -0
- data/lib/concurrent/edge/throttle.rb +185 -0
- metadata +13 -8
- data/lib/concurrent/edge/future.rb +0 -1427
@@ -0,0 +1,185 @@
|
|
1
|
+
module Concurrent
|
2
|
+
# @!macro [new] throttle.example.throttled_block
|
3
|
+
# @example
|
4
|
+
# max_two = Throttle.new 2
|
5
|
+
# 10.times.map do
|
6
|
+
# Thread.new do
|
7
|
+
# max_two.throttled_block do
|
8
|
+
# # Only 2 at the same time
|
9
|
+
# do_stuff
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# @!macro [new] throttle.example.throttled_future_chain
|
14
|
+
# @example
|
15
|
+
# throttle.throttled_future_chain do |trigger|
|
16
|
+
# trigger.
|
17
|
+
# # 2 throttled promises
|
18
|
+
# chain { 1 }.
|
19
|
+
# then(&:succ)
|
20
|
+
# end
|
21
|
+
# @!macro [new] throttle.example.then_throttled_by
|
22
|
+
# @example
|
23
|
+
# data = (1..5).to_a
|
24
|
+
# db = data.reduce({}) { |h, v| h.update v => v.to_s }
|
25
|
+
# max_two = Throttle.new 2
|
26
|
+
#
|
27
|
+
# futures = data.map do |data|
|
28
|
+
# Promises.future(data) do |data|
|
29
|
+
# # un-throttled, concurrency level equal data.size
|
30
|
+
# data + 1
|
31
|
+
# end.then_throttled_by(max_two, db) do |v, db|
|
32
|
+
# # throttled, only 2 tasks executed at the same time
|
33
|
+
# # e.g. limiting access to db
|
34
|
+
# db[v]
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# futures.map(&:value!) # => [2, 3, 4, 5, nil]
|
39
|
+
|
40
|
+
# A tool manage concurrency level of future tasks.
|
41
|
+
#
|
42
|
+
# @!macro throttle.example.then_throttled_by
|
43
|
+
# @!macro throttle.example.throttled_future_chain
|
44
|
+
# @!macro throttle.example.throttled_block
|
45
|
+
class Throttle < Synchronization::Object
|
46
|
+
# TODO (pitr-ch 21-Dec-2016): consider using sized channel for implementation instead when available
|
47
|
+
|
48
|
+
safe_initialization!
|
49
|
+
private *attr_atomic(:can_run)
|
50
|
+
|
51
|
+
# New throttle.
|
52
|
+
# @param [Integer] limit
|
53
|
+
def initialize(limit)
|
54
|
+
super()
|
55
|
+
@Limit = limit
|
56
|
+
self.can_run = limit
|
57
|
+
@Queue = LockFreeQueue.new
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Integer] The limit.
|
61
|
+
def limit
|
62
|
+
@Limit
|
63
|
+
end
|
64
|
+
|
65
|
+
# New event which will be resolved when depending tasks can execute.
|
66
|
+
# Has to be used and after the critical work is done {#release} must be called exactly once.
|
67
|
+
# @return [Promises::Event]
|
68
|
+
# @see #release
|
69
|
+
def trigger
|
70
|
+
while true
|
71
|
+
current_can_run = can_run
|
72
|
+
if compare_and_set_can_run current_can_run, current_can_run - 1
|
73
|
+
if current_can_run > 0
|
74
|
+
return Promises.resolved_event
|
75
|
+
else
|
76
|
+
event = Promises.resolvable_event
|
77
|
+
@Queue.push event
|
78
|
+
return event
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Has to be called once for each trigger after it is ok to execute another throttled task.
|
85
|
+
# @return [self]
|
86
|
+
# @see #trigger
|
87
|
+
def release
|
88
|
+
while true
|
89
|
+
current_can_run = can_run
|
90
|
+
if compare_and_set_can_run current_can_run, current_can_run + 1
|
91
|
+
if current_can_run < 0
|
92
|
+
Thread.pass until (trigger = @Queue.pop)
|
93
|
+
trigger.resolve
|
94
|
+
end
|
95
|
+
return self
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Blocks current thread until the block can be executed.
|
101
|
+
# @yield to throttled block
|
102
|
+
# @yieldreturn [Object] is used as a result of the method
|
103
|
+
# @return [Object] the result of the block
|
104
|
+
# @!macro throttle.example.throttled_block
|
105
|
+
def throttled_block(&block)
|
106
|
+
trigger.wait
|
107
|
+
block.call
|
108
|
+
ensure
|
109
|
+
release
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [String] Short string representation.
|
113
|
+
def to_s
|
114
|
+
format '<#%s:0x%x limit:%s can_run:%d>', self.class, object_id << 1, @Limit, can_run
|
115
|
+
end
|
116
|
+
|
117
|
+
alias_method :inspect, :to_s
|
118
|
+
|
119
|
+
module PromisesIntegration
|
120
|
+
|
121
|
+
# Allows to throttle a chain of promises.
|
122
|
+
# @yield [trigger] a trigger which has to be used to build up a chain of promises, the last one is result
|
123
|
+
# of the block. When the last one resolves, {Throttle#release} is called on the throttle.
|
124
|
+
# @yieldparam [Promises::Event, Promises::Future] trigger
|
125
|
+
# @yieldreturn [Promises::Event, Promises::Future] The final future of the throttled chain.
|
126
|
+
# @return [Promises::Event, Promises::Future] The final future of the throttled chain.
|
127
|
+
# @!macro throttle.example.throttled_future_chain
|
128
|
+
def throttled_future_chain(&throttled_futures)
|
129
|
+
throttled_futures.call(trigger).on_resolution! { release }
|
130
|
+
end
|
131
|
+
|
132
|
+
# Behaves as {Promises::FactoryMethods#future} but the future is throttled.
|
133
|
+
# @return [Promises::Future]
|
134
|
+
# @see Promises::FactoryMethods#future
|
135
|
+
def throttled_future(*args, &task)
|
136
|
+
trigger.chain(*args, &task).on_resolution! { release }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
include PromisesIntegration
|
141
|
+
end
|
142
|
+
|
143
|
+
module Promises
|
144
|
+
|
145
|
+
class AbstractEventFuture < Synchronization::Object
|
146
|
+
module ThrottleIntegration
|
147
|
+
def throttled_by(throttle, &throttled_futures)
|
148
|
+
a_trigger = self & self.chain { throttle.trigger }.flat_event
|
149
|
+
throttled_futures.call(a_trigger).on_resolution! { throttle.release }
|
150
|
+
end
|
151
|
+
|
152
|
+
# Behaves as {Promises::AbstractEventFuture#chain} but the it is throttled.
|
153
|
+
# @return [Promises::Future, Promises::Event]
|
154
|
+
# @see Promises::AbstractEventFuture#chain
|
155
|
+
def chain_throttled_by(throttle, *args, &block)
|
156
|
+
throttled_by(throttle) { |trigger| trigger.chain(*args, &block) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
include ThrottleIntegration
|
161
|
+
end
|
162
|
+
|
163
|
+
class Future < AbstractEventFuture
|
164
|
+
module ThrottleIntegration
|
165
|
+
|
166
|
+
# Behaves as {Promises::Future#then} but the it is throttled.
|
167
|
+
# @return [Promises::Future]
|
168
|
+
# @see Promises::Future#then
|
169
|
+
# @!macro throttle.example.then_throttled_by
|
170
|
+
def then_throttled_by(throttle, *args, &block)
|
171
|
+
throttled_by(throttle) { |trigger| trigger.then(*args, &block) }
|
172
|
+
end
|
173
|
+
|
174
|
+
# Behaves as {Promises::Future#rescue} but the it is throttled.
|
175
|
+
# @return [Promises::Future]
|
176
|
+
# @see Promises::Future#rescue
|
177
|
+
def rescue_throttled_by(throttle, *args, &block)
|
178
|
+
throttled_by(throttle) { |trigger| trigger.rescue(*args, &block) }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
include ThrottleIntegration
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent-ruby-edge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jerry D'Antonio
|
@@ -10,22 +10,22 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2016-12-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: concurrent-ruby
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - '='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.0.
|
21
|
+
version: 1.0.4
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
|
-
- -
|
26
|
+
- - '='
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: 1.0.
|
28
|
+
version: 1.0.4
|
29
29
|
description: |
|
30
30
|
These features are under active development and may change frequently. They are expected not to
|
31
31
|
keep backward compatibility (there may also lack tests and documentation). Semantic versions will
|
@@ -88,11 +88,15 @@ files:
|
|
88
88
|
- lib/concurrent/channel/selector/take_clause.rb
|
89
89
|
- lib/concurrent/channel/tick.rb
|
90
90
|
- lib/concurrent/edge/atomic_markable_reference.rb
|
91
|
-
- lib/concurrent/edge/
|
91
|
+
- lib/concurrent/edge/cancellation.rb
|
92
92
|
- lib/concurrent/edge/lock_free_linked_set.rb
|
93
93
|
- lib/concurrent/edge/lock_free_linked_set/node.rb
|
94
94
|
- lib/concurrent/edge/lock_free_linked_set/window.rb
|
95
|
+
- lib/concurrent/edge/lock_free_queue.rb
|
95
96
|
- lib/concurrent/edge/lock_free_stack.rb
|
97
|
+
- lib/concurrent/edge/old_channel_integration.rb
|
98
|
+
- lib/concurrent/edge/promises.rb
|
99
|
+
- lib/concurrent/edge/throttle.rb
|
96
100
|
homepage: http://www.concurrent-ruby.com
|
97
101
|
licenses:
|
98
102
|
- MIT
|
@@ -113,8 +117,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
117
|
version: '0'
|
114
118
|
requirements: []
|
115
119
|
rubyforge_project:
|
116
|
-
rubygems_version: 2.
|
120
|
+
rubygems_version: 2.5.1
|
117
121
|
signing_key:
|
118
122
|
specification_version: 4
|
119
123
|
summary: Edge features and additions to the concurrent-ruby gem.
|
120
124
|
test_files: []
|
125
|
+
has_rdoc:
|
@@ -1,1427 +0,0 @@
|
|
1
|
-
require 'concurrent' # TODO do not require whole concurrent gem
|
2
|
-
require 'concurrent/concern/deprecation'
|
3
|
-
require 'concurrent/edge/lock_free_stack'
|
4
|
-
|
5
|
-
|
6
|
-
# @note different name just not to collide for now
|
7
|
-
module Concurrent
|
8
|
-
module Edge
|
9
|
-
|
10
|
-
# Provides edge features, which will be added to or replace features in main gem.
|
11
|
-
#
|
12
|
-
# Contains new unified implementation of Futures and Promises which combines Features of previous `Future`,
|
13
|
-
# `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
|
14
|
-
# new synchronization layer to make all the paths lock-free with exception of blocking threads on `#wait`.
|
15
|
-
# It offers better performance and does not block threads (exception being #wait and similar methods where it's
|
16
|
-
# intended).
|
17
|
-
#
|
18
|
-
# ## Examples
|
19
|
-
# {include:file:examples/edge_futures.out.rb}
|
20
|
-
#
|
21
|
-
# @!macro edge_warning
|
22
|
-
module FutureShortcuts
|
23
|
-
# User is responsible for completing the event once by {Edge::CompletableEvent#complete}
|
24
|
-
# @return [CompletableEvent]
|
25
|
-
def event(default_executor = :io)
|
26
|
-
CompletableEventPromise.new(default_executor).future
|
27
|
-
end
|
28
|
-
|
29
|
-
# @overload future(default_executor = :io, &task)
|
30
|
-
# Constructs new Future which will be completed after block is evaluated on executor. Evaluation begins immediately.
|
31
|
-
# @return [Future]
|
32
|
-
# @overload future(default_executor = :io)
|
33
|
-
# User is responsible for completing the future once by {Edge::CompletableFuture#success} or {Edge::CompletableFuture#fail}
|
34
|
-
# @return [CompletableFuture]
|
35
|
-
def future(default_executor = :io, &task)
|
36
|
-
if task
|
37
|
-
ImmediateEventPromise.new(default_executor).future.then(&task)
|
38
|
-
else
|
39
|
-
CompletableFuturePromise.new(default_executor).future
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# @return [Future] which is already completed
|
44
|
-
def completed_future(success, value, reason, default_executor = :io)
|
45
|
-
ImmediateFuturePromise.new(default_executor, success, value, reason).future
|
46
|
-
end
|
47
|
-
|
48
|
-
# @return [Future] which is already completed in success state with value
|
49
|
-
def succeeded_future(value, default_executor = :io)
|
50
|
-
completed_future true, value, nil, default_executor
|
51
|
-
end
|
52
|
-
|
53
|
-
# @return [Future] which is already completed in failed state with reason
|
54
|
-
def failed_future(reason, default_executor = :io)
|
55
|
-
completed_future false, nil, reason, default_executor
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return [Event] which is already completed
|
59
|
-
def completed_event(default_executor = :io)
|
60
|
-
ImmediateEventPromise.new(default_executor).event
|
61
|
-
end
|
62
|
-
|
63
|
-
alias_method :async, :future
|
64
|
-
|
65
|
-
# Constructs new Future which will evaluate to the block after
|
66
|
-
# requested by calling `#wait`, `#value`, `#value!`, etc. on it or on any of the chained futures.
|
67
|
-
# @return [Future]
|
68
|
-
def delay(default_executor = :io, &task)
|
69
|
-
DelayPromise.new(default_executor).future.then(&task)
|
70
|
-
end
|
71
|
-
|
72
|
-
# Schedules the block to be executed on executor in given intended_time.
|
73
|
-
# @param [Numeric, Time] intended_time Numeric => run in `intended_time` seconds. Time => eun on time.
|
74
|
-
# @return [Future]
|
75
|
-
def schedule(intended_time, default_executor = :io, &task)
|
76
|
-
ScheduledPromise.new(default_executor, intended_time).future.then(&task)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Constructs new {Future} which is completed after all futures_and_or_events are complete. Its value is array
|
80
|
-
# of dependent future values. If there is an error it fails with the first one. Event does not
|
81
|
-
# have a value so it's represented by nil in the array of values.
|
82
|
-
# @param [Event] futures_and_or_events
|
83
|
-
# @return [Future]
|
84
|
-
def zip_futures(*futures_and_or_events)
|
85
|
-
ZipFuturesPromise.new(futures_and_or_events, :io).future
|
86
|
-
end
|
87
|
-
|
88
|
-
alias_method :zip, :zip_futures
|
89
|
-
|
90
|
-
# Constructs new {Event} which is completed after all futures_and_or_events are complete
|
91
|
-
# (Future is completed when Success or Failed).
|
92
|
-
# @param [Event] futures_and_or_events
|
93
|
-
# @return [Event]
|
94
|
-
def zip_events(*futures_and_or_events)
|
95
|
-
ZipEventsPromise.new(futures_and_or_events, :io).future
|
96
|
-
end
|
97
|
-
|
98
|
-
# Constructs new {Future} which is completed after first of the futures is complete.
|
99
|
-
# @param [Event] futures
|
100
|
-
# @return [Future]
|
101
|
-
def any_complete(*futures)
|
102
|
-
AnyCompletePromise.new(futures, :io).future
|
103
|
-
end
|
104
|
-
|
105
|
-
alias_method :any, :any_complete
|
106
|
-
|
107
|
-
# Constructs new {Future} which becomes succeeded after first of the futures succeedes or
|
108
|
-
# failed if all futures fail (reason is last error).
|
109
|
-
# @param [Event] futures
|
110
|
-
# @return [Future]
|
111
|
-
def any_successful(*futures)
|
112
|
-
AnySuccessfulPromise.new(futures, :io).future
|
113
|
-
end
|
114
|
-
|
115
|
-
# only proof of concept
|
116
|
-
# @return [Future]
|
117
|
-
def select(*channels)
|
118
|
-
future do
|
119
|
-
# noinspection RubyArgCount
|
120
|
-
Channel.select do |s|
|
121
|
-
channels.each do |ch|
|
122
|
-
s.take(ch) { |value| [value, ch] }
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# post job on :fast executor
|
129
|
-
# @return [true, false]
|
130
|
-
def post!(*args, &job)
|
131
|
-
post_on(:fast, *args, &job)
|
132
|
-
end
|
133
|
-
|
134
|
-
# post job on :io executor
|
135
|
-
# @return [true, false]
|
136
|
-
def post(*args, &job)
|
137
|
-
post_on(:io, *args, &job)
|
138
|
-
end
|
139
|
-
|
140
|
-
# post job on executor
|
141
|
-
# @return [true, false]
|
142
|
-
def post_on(executor, *args, &job)
|
143
|
-
Concurrent.executor(executor).post(*args, &job)
|
144
|
-
end
|
145
|
-
|
146
|
-
# TODO add first(futures, count=count)
|
147
|
-
# TODO allow to to have a zip point for many futures and process them in batches by 10
|
148
|
-
end
|
149
|
-
|
150
|
-
# Represents an event which will happen in future (will be completed). It has to always happen.
|
151
|
-
class Event < Synchronization::Object
|
152
|
-
safe_initialization!
|
153
|
-
private(*attr_atomic(:internal_state))
|
154
|
-
# @!visibility private
|
155
|
-
public :internal_state
|
156
|
-
include Concern::Deprecation
|
157
|
-
include Concern::Logging
|
158
|
-
|
159
|
-
# @!visibility private
|
160
|
-
class State
|
161
|
-
def completed?
|
162
|
-
raise NotImplementedError
|
163
|
-
end
|
164
|
-
|
165
|
-
def to_sym
|
166
|
-
raise NotImplementedError
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# @!visibility private
|
171
|
-
class Pending < State
|
172
|
-
def completed?
|
173
|
-
false
|
174
|
-
end
|
175
|
-
|
176
|
-
def to_sym
|
177
|
-
:pending
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
# @!visibility private
|
182
|
-
class Completed < State
|
183
|
-
def completed?
|
184
|
-
true
|
185
|
-
end
|
186
|
-
|
187
|
-
def to_sym
|
188
|
-
:completed
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# @!visibility private
|
193
|
-
PENDING = Pending.new
|
194
|
-
# @!visibility private
|
195
|
-
COMPLETED = Completed.new
|
196
|
-
|
197
|
-
def initialize(promise, default_executor)
|
198
|
-
super()
|
199
|
-
@Lock = Mutex.new
|
200
|
-
@Condition = ConditionVariable.new
|
201
|
-
@Promise = promise
|
202
|
-
@DefaultExecutor = default_executor
|
203
|
-
@Touched = AtomicBoolean.new false
|
204
|
-
@Callbacks = LockFreeStack.new
|
205
|
-
@Waiters = AtomicFixnum.new 0
|
206
|
-
self.internal_state = PENDING
|
207
|
-
end
|
208
|
-
|
209
|
-
# @return [:pending, :completed]
|
210
|
-
def state
|
211
|
-
internal_state.to_sym
|
212
|
-
end
|
213
|
-
|
214
|
-
# Is Event/Future pending?
|
215
|
-
# @return [Boolean]
|
216
|
-
def pending?(state = internal_state)
|
217
|
-
!state.completed?
|
218
|
-
end
|
219
|
-
|
220
|
-
def unscheduled?
|
221
|
-
raise 'unsupported'
|
222
|
-
end
|
223
|
-
|
224
|
-
alias_method :incomplete?, :pending?
|
225
|
-
|
226
|
-
# Has the Event been completed?
|
227
|
-
# @return [Boolean]
|
228
|
-
def completed?(state = internal_state)
|
229
|
-
state.completed?
|
230
|
-
end
|
231
|
-
|
232
|
-
alias_method :complete?, :completed?
|
233
|
-
|
234
|
-
# Wait until Event is #complete?
|
235
|
-
# @param [Numeric] timeout the maximum time in second to wait.
|
236
|
-
# @return [Event, true, false] self or true/false if timeout is used
|
237
|
-
# @!macro [attach] edge.periodical_wait
|
238
|
-
# @note a thread should wait only once! For repeated checking use faster `completed?` check.
|
239
|
-
# If thread waits periodically it will dangerously grow the waiters stack.
|
240
|
-
def wait(timeout = nil)
|
241
|
-
touch
|
242
|
-
result = wait_until_complete(timeout)
|
243
|
-
timeout ? result : self
|
244
|
-
end
|
245
|
-
|
246
|
-
# @!visibility private
|
247
|
-
def touch
|
248
|
-
# distribute touch to promise only once
|
249
|
-
@Promise.touch if @Touched.make_true
|
250
|
-
self
|
251
|
-
end
|
252
|
-
|
253
|
-
# @return [Executor] current default executor
|
254
|
-
# @see #with_default_executor
|
255
|
-
def default_executor
|
256
|
-
@DefaultExecutor
|
257
|
-
end
|
258
|
-
|
259
|
-
# @yield [success, value, reason] of the parent
|
260
|
-
def chain(executor = nil, &callback)
|
261
|
-
ChainPromise.new(self, @DefaultExecutor, executor || @DefaultExecutor, &callback).future
|
262
|
-
end
|
263
|
-
|
264
|
-
alias_method :then, :chain
|
265
|
-
|
266
|
-
def chain_completable(completable_event)
|
267
|
-
on_completion! { completable_event.complete_with COMPLETED }
|
268
|
-
end
|
269
|
-
|
270
|
-
alias_method :tangle, :chain_completable
|
271
|
-
|
272
|
-
# Zip with future producing new Future
|
273
|
-
# @return [Event]
|
274
|
-
def zip(other)
|
275
|
-
if other.is?(Future)
|
276
|
-
ZipFutureEventPromise.new(other, self, @DefaultExecutor).future
|
277
|
-
else
|
278
|
-
ZipEventEventPromise.new(self, other, @DefaultExecutor).future
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
alias_method :&, :zip
|
283
|
-
|
284
|
-
# Inserts delay into the chain of Futures making rest of it lazy evaluated.
|
285
|
-
# @return [Event]
|
286
|
-
def delay
|
287
|
-
ZipEventEventPromise.new(self, DelayPromise.new(@DefaultExecutor).event, @DefaultExecutor).event
|
288
|
-
end
|
289
|
-
|
290
|
-
# # Schedules rest of the chain for execution with specified time or on specified time
|
291
|
-
# # @return [Event]
|
292
|
-
# def schedule(intended_time)
|
293
|
-
# chain do
|
294
|
-
# ZipEventEventPromise.new(self,
|
295
|
-
# ScheduledPromise.new(@DefaultExecutor, intended_time).event,
|
296
|
-
# @DefaultExecutor).event
|
297
|
-
# end.flat
|
298
|
-
# end
|
299
|
-
|
300
|
-
# Zips with selected value form the suplied channels
|
301
|
-
# @return [Future]
|
302
|
-
def then_select(*channels)
|
303
|
-
ZipFutureEventPromise(Concurrent.select(*channels), self, @DefaultExecutor).future
|
304
|
-
end
|
305
|
-
|
306
|
-
# @yield [success, value, reason] executed async on `executor` when completed
|
307
|
-
# @return self
|
308
|
-
def on_completion(executor = nil, &callback)
|
309
|
-
add_callback :async_callback_on_completion, executor || @DefaultExecutor, callback
|
310
|
-
end
|
311
|
-
|
312
|
-
# @yield [success, value, reason] executed sync when completed
|
313
|
-
# @return self
|
314
|
-
def on_completion!(&callback)
|
315
|
-
add_callback :callback_on_completion, callback
|
316
|
-
end
|
317
|
-
|
318
|
-
# Changes default executor for rest of the chain
|
319
|
-
# @return [Event]
|
320
|
-
def with_default_executor(executor)
|
321
|
-
EventWrapperPromise.new(self, executor).future
|
322
|
-
end
|
323
|
-
|
324
|
-
def to_s
|
325
|
-
"<##{self.class}:0x#{'%x' % (object_id << 1)} #{state.to_sym}>"
|
326
|
-
end
|
327
|
-
|
328
|
-
def inspect
|
329
|
-
"#{to_s[0..-2]} blocks:[#{blocks.map(&:to_s).join(', ')}]>"
|
330
|
-
end
|
331
|
-
|
332
|
-
def set(*args, &block)
|
333
|
-
raise 'Use CompletableEvent#complete or CompletableFuture#complete instead, ' +
|
334
|
-
'constructed by Concurrent.event or Concurrent.future respectively.'
|
335
|
-
end
|
336
|
-
|
337
|
-
# @!visibility private
|
338
|
-
def complete_with(state, raise_on_reassign = true)
|
339
|
-
if compare_and_set_internal_state(PENDING, state)
|
340
|
-
# go to synchronized block only if there were waiting threads
|
341
|
-
@Lock.synchronize { @Condition.broadcast } unless @Waiters.value == 0
|
342
|
-
call_callbacks
|
343
|
-
else
|
344
|
-
Concurrent::MultipleAssignmentError.new('Event can be completed only once') if raise_on_reassign
|
345
|
-
return false
|
346
|
-
end
|
347
|
-
self
|
348
|
-
end
|
349
|
-
|
350
|
-
# @!visibility private
|
351
|
-
# just for inspection
|
352
|
-
# @return [Array<AbstractPromise>]
|
353
|
-
def blocks
|
354
|
-
@Callbacks.each_with_object([]) do |callback, promises|
|
355
|
-
promises.push(*(callback.select { |v| v.is_a? AbstractPromise }))
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
# @!visibility private
|
360
|
-
# just for inspection
|
361
|
-
def callbacks
|
362
|
-
@Callbacks.each.to_a
|
363
|
-
end
|
364
|
-
|
365
|
-
# @!visibility private
|
366
|
-
def add_callback(method, *args)
|
367
|
-
if completed?
|
368
|
-
call_callback method, *args
|
369
|
-
else
|
370
|
-
@Callbacks.push [method, *args]
|
371
|
-
call_callbacks if completed?
|
372
|
-
end
|
373
|
-
self
|
374
|
-
end
|
375
|
-
|
376
|
-
# @!visibility private
|
377
|
-
# only for inspection
|
378
|
-
def promise
|
379
|
-
@Promise
|
380
|
-
end
|
381
|
-
|
382
|
-
# @!visibility private
|
383
|
-
# only for inspection
|
384
|
-
def touched
|
385
|
-
@Touched.value
|
386
|
-
end
|
387
|
-
|
388
|
-
# @!visibility private
|
389
|
-
# only for debugging inspection
|
390
|
-
def waiting_threads
|
391
|
-
@Waiters.each.to_a
|
392
|
-
end
|
393
|
-
|
394
|
-
private
|
395
|
-
|
396
|
-
# @return [true, false]
|
397
|
-
def wait_until_complete(timeout)
|
398
|
-
return true if completed?
|
399
|
-
|
400
|
-
@Lock.synchronize do
|
401
|
-
@Waiters.increment
|
402
|
-
begin
|
403
|
-
unless completed?
|
404
|
-
@Condition.wait @Lock, timeout
|
405
|
-
end
|
406
|
-
ensure
|
407
|
-
# JRuby may raise ConcurrencyError
|
408
|
-
@Waiters.decrement
|
409
|
-
end
|
410
|
-
end
|
411
|
-
completed?
|
412
|
-
end
|
413
|
-
|
414
|
-
def with_async(executor, *args, &block)
|
415
|
-
Concurrent.post_on(executor, *args, &block)
|
416
|
-
end
|
417
|
-
|
418
|
-
def async_callback_on_completion(executor, callback)
|
419
|
-
with_async(executor) { callback_on_completion callback }
|
420
|
-
end
|
421
|
-
|
422
|
-
def callback_on_completion(callback)
|
423
|
-
callback.call
|
424
|
-
end
|
425
|
-
|
426
|
-
def callback_notify_blocked(promise)
|
427
|
-
promise.on_done self
|
428
|
-
end
|
429
|
-
|
430
|
-
def call_callback(method, *args)
|
431
|
-
self.send method, *args
|
432
|
-
end
|
433
|
-
|
434
|
-
def call_callbacks
|
435
|
-
method, *args = @Callbacks.pop
|
436
|
-
while method
|
437
|
-
call_callback method, *args
|
438
|
-
method, *args = @Callbacks.pop
|
439
|
-
end
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
# Represents a value which will become available in future. May fail with a reason instead.
|
444
|
-
class Future < Event
|
445
|
-
# @!visibility private
|
446
|
-
class CompletedWithResult < Completed
|
447
|
-
def result
|
448
|
-
[success?, value, reason]
|
449
|
-
end
|
450
|
-
|
451
|
-
def success?
|
452
|
-
raise NotImplementedError
|
453
|
-
end
|
454
|
-
|
455
|
-
def value
|
456
|
-
raise NotImplementedError
|
457
|
-
end
|
458
|
-
|
459
|
-
def reason
|
460
|
-
raise NotImplementedError
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
# @!visibility private
|
465
|
-
class Success < CompletedWithResult
|
466
|
-
def initialize(value)
|
467
|
-
@Value = value
|
468
|
-
end
|
469
|
-
|
470
|
-
def success?
|
471
|
-
true
|
472
|
-
end
|
473
|
-
|
474
|
-
def apply(block)
|
475
|
-
block.call value
|
476
|
-
end
|
477
|
-
|
478
|
-
def value
|
479
|
-
@Value
|
480
|
-
end
|
481
|
-
|
482
|
-
def reason
|
483
|
-
nil
|
484
|
-
end
|
485
|
-
|
486
|
-
def to_sym
|
487
|
-
:success
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
# @!visibility private
|
492
|
-
class SuccessArray < Success
|
493
|
-
def apply(block)
|
494
|
-
block.call(*value)
|
495
|
-
end
|
496
|
-
end
|
497
|
-
|
498
|
-
# @!visibility private
|
499
|
-
class Failed < CompletedWithResult
|
500
|
-
def initialize(reason)
|
501
|
-
@Reason = reason
|
502
|
-
end
|
503
|
-
|
504
|
-
def success?
|
505
|
-
false
|
506
|
-
end
|
507
|
-
|
508
|
-
def value
|
509
|
-
nil
|
510
|
-
end
|
511
|
-
|
512
|
-
def reason
|
513
|
-
@Reason
|
514
|
-
end
|
515
|
-
|
516
|
-
def to_sym
|
517
|
-
:failed
|
518
|
-
end
|
519
|
-
|
520
|
-
def apply(block)
|
521
|
-
block.call reason
|
522
|
-
end
|
523
|
-
end
|
524
|
-
|
525
|
-
# @!visibility private
|
526
|
-
class PartiallyFailed < CompletedWithResult
|
527
|
-
def initialize(value, reason)
|
528
|
-
super()
|
529
|
-
@Value = value
|
530
|
-
@Reason = reason
|
531
|
-
end
|
532
|
-
|
533
|
-
def success?
|
534
|
-
false
|
535
|
-
end
|
536
|
-
|
537
|
-
def to_sym
|
538
|
-
:failed
|
539
|
-
end
|
540
|
-
|
541
|
-
def value
|
542
|
-
@Value
|
543
|
-
end
|
544
|
-
|
545
|
-
def reason
|
546
|
-
@Reason
|
547
|
-
end
|
548
|
-
|
549
|
-
def apply(block)
|
550
|
-
block.call(*reason)
|
551
|
-
end
|
552
|
-
end
|
553
|
-
|
554
|
-
# @!method state
|
555
|
-
# @return [:pending, :success, :failed]
|
556
|
-
|
557
|
-
# Has Future been success?
|
558
|
-
# @return [Boolean]
|
559
|
-
def success?(state = internal_state)
|
560
|
-
state.completed? && state.success?
|
561
|
-
end
|
562
|
-
|
563
|
-
def fulfilled?
|
564
|
-
deprecated_method 'fulfilled?', 'success?'
|
565
|
-
success?
|
566
|
-
end
|
567
|
-
|
568
|
-
# Has Future been failed?
|
569
|
-
# @return [Boolean]
|
570
|
-
def failed?(state = internal_state)
|
571
|
-
state.completed? && !state.success?
|
572
|
-
end
|
573
|
-
|
574
|
-
def rejected?
|
575
|
-
deprecated_method 'rejected?', 'failed?'
|
576
|
-
failed?
|
577
|
-
end
|
578
|
-
|
579
|
-
# @return [Object, nil] the value of the Future when success, nil on timeout
|
580
|
-
# @!macro [attach] edge.timeout_nil
|
581
|
-
# @note If the Future can have value `nil` then it cannot be distinquished from `nil` returned on timeout.
|
582
|
-
# In this case is better to use first `wait` then `value` (or similar).
|
583
|
-
# @!macro edge.periodical_wait
|
584
|
-
def value(timeout = nil)
|
585
|
-
touch
|
586
|
-
internal_state.value if wait_until_complete timeout
|
587
|
-
end
|
588
|
-
|
589
|
-
# @return [Exception, nil] the reason of the Future's failure
|
590
|
-
# @!macro edge.timeout_nil
|
591
|
-
# @!macro edge.periodical_wait
|
592
|
-
def reason(timeout = nil)
|
593
|
-
touch
|
594
|
-
internal_state.reason if wait_until_complete timeout
|
595
|
-
end
|
596
|
-
|
597
|
-
# @return [Array(Boolean, Object, Exception), nil] triplet of success, value, reason
|
598
|
-
# @!macro edge.timeout_nil
|
599
|
-
# @!macro edge.periodical_wait
|
600
|
-
def result(timeout = nil)
|
601
|
-
touch
|
602
|
-
internal_state.result if wait_until_complete timeout
|
603
|
-
end
|
604
|
-
|
605
|
-
# Wait until Future is #complete?
|
606
|
-
# @param [Numeric] timeout the maximum time in second to wait.
|
607
|
-
# @raise reason on failure
|
608
|
-
# @return [Event, true, false] self or true/false if timeout is used
|
609
|
-
# @!macro edge.periodical_wait
|
610
|
-
def wait!(timeout = nil)
|
611
|
-
touch
|
612
|
-
result = wait_until_complete!(timeout)
|
613
|
-
timeout ? result : self
|
614
|
-
end
|
615
|
-
|
616
|
-
# Wait until Future is #complete?
|
617
|
-
# @param [Numeric] timeout the maximum time in second to wait.
|
618
|
-
# @raise reason on failure
|
619
|
-
# @return [Object, nil]
|
620
|
-
# @!macro edge.timeout_nil
|
621
|
-
# @!macro edge.periodical_wait
|
622
|
-
def value!(timeout = nil)
|
623
|
-
touch
|
624
|
-
internal_state.value if wait_until_complete! timeout
|
625
|
-
end
|
626
|
-
|
627
|
-
# @example allows failed Future to be risen
|
628
|
-
# raise Concurrent.future.fail
|
629
|
-
def exception(*args)
|
630
|
-
raise 'obligation is not failed' unless failed?
|
631
|
-
reason = internal_state.reason
|
632
|
-
if reason.is_a?(::Array)
|
633
|
-
reason.each { |e| log ERROR, 'Edge::Future', e }
|
634
|
-
Concurrent::Error.new 'multiple exceptions, inspect log'
|
635
|
-
else
|
636
|
-
reason.exception(*args)
|
637
|
-
end
|
638
|
-
end
|
639
|
-
|
640
|
-
# @yield [value] executed only on parent success
|
641
|
-
# @return [Future]
|
642
|
-
def then(executor = nil, &callback)
|
643
|
-
ThenPromise.new(self, @DefaultExecutor, executor || @DefaultExecutor, &callback).future
|
644
|
-
end
|
645
|
-
|
646
|
-
# Asks the actor with its value.
|
647
|
-
# @return [Future] new future with the response form the actor
|
648
|
-
def then_ask(actor)
|
649
|
-
self.then { |v| actor.ask(v) }.flat
|
650
|
-
end
|
651
|
-
|
652
|
-
def chain_completable(completable_future)
|
653
|
-
on_completion! { completable_future.complete_with internal_state }
|
654
|
-
end
|
655
|
-
|
656
|
-
alias_method :tangle, :chain_completable
|
657
|
-
|
658
|
-
# @yield [reason] executed only on parent failure
|
659
|
-
# @return [Future]
|
660
|
-
def rescue(executor = nil, &callback)
|
661
|
-
RescuePromise.new(self, @DefaultExecutor, executor || @DefaultExecutor, &callback).future
|
662
|
-
end
|
663
|
-
|
664
|
-
# zips with the Future in the value
|
665
|
-
# @example
|
666
|
-
# Concurrent.future { Concurrent.future { 1 } }.flat.value # => 1
|
667
|
-
def flat(level = 1)
|
668
|
-
FlatPromise.new(self, level, @DefaultExecutor).future
|
669
|
-
end
|
670
|
-
|
671
|
-
# @return [Future] which has first completed value from futures
|
672
|
-
def any(*futures)
|
673
|
-
AnyCompletePromise.new([self, *futures], @DefaultExecutor).future
|
674
|
-
end
|
675
|
-
|
676
|
-
# Inserts delay into the chain of Futures making rest of it lazy evaluated.
|
677
|
-
# @return [Future]
|
678
|
-
def delay
|
679
|
-
ZipFutureEventPromise.new(self, DelayPromise.new(@DefaultExecutor).future, @DefaultExecutor).future
|
680
|
-
end
|
681
|
-
|
682
|
-
# Schedules rest of the chain for execution with specified time or on specified time
|
683
|
-
# @return [Future]
|
684
|
-
def schedule(intended_time)
|
685
|
-
chain do
|
686
|
-
ZipFutureEventPromise.new(self,
|
687
|
-
ScheduledPromise.new(@DefaultExecutor, intended_time).event,
|
688
|
-
@DefaultExecutor).future
|
689
|
-
end.flat
|
690
|
-
end
|
691
|
-
|
692
|
-
# Zips with selected value form the suplied channels
|
693
|
-
# @return [Future]
|
694
|
-
def then_select(*channels)
|
695
|
-
ZipFuturesPromise.new([self, Concurrent.select(*channels)], @DefaultExecutor).future
|
696
|
-
end
|
697
|
-
|
698
|
-
# Changes default executor for rest of the chain
|
699
|
-
# @return [Future]
|
700
|
-
def with_default_executor(executor)
|
701
|
-
FutureWrapperPromise.new(self, executor).future
|
702
|
-
end
|
703
|
-
|
704
|
-
# Zip with future producing new Future
|
705
|
-
# @return [Future]
|
706
|
-
def zip(other)
|
707
|
-
if other.is_a?(Future)
|
708
|
-
ZipFutureFuturePromise.new(self, other, @DefaultExecutor).future
|
709
|
-
else
|
710
|
-
ZipFutureEventPromise.new(self, other, @DefaultExecutor).future
|
711
|
-
end
|
712
|
-
end
|
713
|
-
|
714
|
-
alias_method :&, :zip
|
715
|
-
|
716
|
-
alias_method :|, :any
|
717
|
-
|
718
|
-
# @note may block
|
719
|
-
# @note only proof of concept
|
720
|
-
def then_put(channel)
|
721
|
-
on_success(:io) { |value| channel.put value }
|
722
|
-
end
|
723
|
-
|
724
|
-
# @yield [value] executed async on `executor` when success
|
725
|
-
# @return self
|
726
|
-
def on_success(executor = nil, &callback)
|
727
|
-
add_callback :async_callback_on_success, executor || @DefaultExecutor, callback
|
728
|
-
end
|
729
|
-
|
730
|
-
# @yield [reason] executed async on `executor` when failed?
|
731
|
-
# @return self
|
732
|
-
def on_failure(executor = nil, &callback)
|
733
|
-
add_callback :async_callback_on_failure, executor || @DefaultExecutor, callback
|
734
|
-
end
|
735
|
-
|
736
|
-
# @yield [value] executed sync when success
|
737
|
-
# @return self
|
738
|
-
def on_success!(&callback)
|
739
|
-
add_callback :callback_on_success, callback
|
740
|
-
end
|
741
|
-
|
742
|
-
# @yield [reason] executed sync when failed?
|
743
|
-
# @return self
|
744
|
-
def on_failure!(&callback)
|
745
|
-
add_callback :callback_on_failure, callback
|
746
|
-
end
|
747
|
-
|
748
|
-
# @!visibility private
|
749
|
-
def complete_with(state, raise_on_reassign = true)
|
750
|
-
if compare_and_set_internal_state(PENDING, state)
|
751
|
-
# go to synchronized block only if there were waiting threads
|
752
|
-
@Lock.synchronize { @Condition.broadcast } unless @Waiters.value == 0
|
753
|
-
call_callbacks state
|
754
|
-
else
|
755
|
-
if raise_on_reassign
|
756
|
-
log ERROR, 'Edge::Future', reason if reason # print otherwise hidden error
|
757
|
-
raise(Concurrent::MultipleAssignmentError.new(
|
758
|
-
"Future can be completed only once. Current result is #{result}, " +
|
759
|
-
"trying to set #{state.result}"))
|
760
|
-
end
|
761
|
-
return false
|
762
|
-
end
|
763
|
-
self
|
764
|
-
end
|
765
|
-
|
766
|
-
# @!visibility private
|
767
|
-
def add_callback(method, *args)
|
768
|
-
state = internal_state
|
769
|
-
if completed?(state)
|
770
|
-
call_callback method, state, *args
|
771
|
-
else
|
772
|
-
@Callbacks.push [method, *args]
|
773
|
-
state = internal_state
|
774
|
-
# take back if it was completed in the meanwhile
|
775
|
-
call_callbacks state if completed?(state)
|
776
|
-
end
|
777
|
-
self
|
778
|
-
end
|
779
|
-
|
780
|
-
# @!visibility private
|
781
|
-
def apply(block)
|
782
|
-
internal_state.apply block
|
783
|
-
end
|
784
|
-
|
785
|
-
private
|
786
|
-
|
787
|
-
def wait_until_complete!(timeout = nil)
|
788
|
-
result = wait_until_complete(timeout)
|
789
|
-
raise self if failed?
|
790
|
-
result
|
791
|
-
end
|
792
|
-
|
793
|
-
def call_callbacks(state)
|
794
|
-
method, *args = @Callbacks.pop
|
795
|
-
while method
|
796
|
-
call_callback method, state, *args
|
797
|
-
method, *args = @Callbacks.pop
|
798
|
-
end
|
799
|
-
end
|
800
|
-
|
801
|
-
def call_callback(method, state, *args)
|
802
|
-
self.send method, state, *args
|
803
|
-
end
|
804
|
-
|
805
|
-
def async_callback_on_success(state, executor, callback)
|
806
|
-
with_async(executor, state, callback) do |st, cb|
|
807
|
-
callback_on_success st, cb
|
808
|
-
end
|
809
|
-
end
|
810
|
-
|
811
|
-
def async_callback_on_failure(state, executor, callback)
|
812
|
-
with_async(executor, state, callback) do |st, cb|
|
813
|
-
callback_on_failure st, cb
|
814
|
-
end
|
815
|
-
end
|
816
|
-
|
817
|
-
def callback_on_success(state, callback)
|
818
|
-
state.apply callback if state.success?
|
819
|
-
end
|
820
|
-
|
821
|
-
def callback_on_failure(state, callback)
|
822
|
-
state.apply callback unless state.success?
|
823
|
-
end
|
824
|
-
|
825
|
-
def callback_on_completion(state, callback)
|
826
|
-
callback.call state.result
|
827
|
-
end
|
828
|
-
|
829
|
-
def callback_notify_blocked(state, promise)
|
830
|
-
super(promise)
|
831
|
-
end
|
832
|
-
|
833
|
-
def async_callback_on_completion(state, executor, callback)
|
834
|
-
with_async(executor, state, callback) do |st, cb|
|
835
|
-
callback_on_completion st, cb
|
836
|
-
end
|
837
|
-
end
|
838
|
-
|
839
|
-
end
|
840
|
-
|
841
|
-
# A Event which can be completed by user.
|
842
|
-
class CompletableEvent < Event
|
843
|
-
# Complete the Event, `raise` if already completed
|
844
|
-
def complete(raise_on_reassign = true)
|
845
|
-
complete_with COMPLETED, raise_on_reassign
|
846
|
-
end
|
847
|
-
|
848
|
-
def hide_completable
|
849
|
-
EventWrapperPromise.new(self, @DefaultExecutor).event
|
850
|
-
end
|
851
|
-
end
|
852
|
-
|
853
|
-
# A Future which can be completed by user.
|
854
|
-
class CompletableFuture < Future
|
855
|
-
# Complete the future with triplet od `success`, `value`, `reason`
|
856
|
-
# `raise` if already completed
|
857
|
-
# return [self]
|
858
|
-
def complete(success, value, reason, raise_on_reassign = true)
|
859
|
-
complete_with(success ? Success.new(value) : Failed.new(reason), raise_on_reassign)
|
860
|
-
end
|
861
|
-
|
862
|
-
# Complete the future with value
|
863
|
-
# return [self]
|
864
|
-
def success(value)
|
865
|
-
promise.success(value)
|
866
|
-
end
|
867
|
-
|
868
|
-
# Try to complete the future with value
|
869
|
-
# return [self]
|
870
|
-
def try_success(value)
|
871
|
-
promise.try_success(value)
|
872
|
-
end
|
873
|
-
|
874
|
-
# Fail the future with reason
|
875
|
-
# return [self]
|
876
|
-
def fail(reason = StandardError.new)
|
877
|
-
promise.fail(reason)
|
878
|
-
end
|
879
|
-
|
880
|
-
# Try to fail the future with reason
|
881
|
-
# return [self]
|
882
|
-
def try_fail(reason = StandardError.new)
|
883
|
-
promise.try_fail(reason)
|
884
|
-
end
|
885
|
-
|
886
|
-
# Evaluate the future to value if there is an exception the future fails with it
|
887
|
-
# return [self]
|
888
|
-
def evaluate_to(*args, &block)
|
889
|
-
promise.evaluate_to(*args, block)
|
890
|
-
end
|
891
|
-
|
892
|
-
# Evaluate the future to value if there is an exception the future fails with it
|
893
|
-
# @raise the exception
|
894
|
-
# return [self]
|
895
|
-
def evaluate_to!(*args, &block)
|
896
|
-
promise.evaluate_to!(*args, block)
|
897
|
-
end
|
898
|
-
|
899
|
-
def hide_completable
|
900
|
-
FutureWrapperPromise.new(self, @DefaultExecutor).future
|
901
|
-
end
|
902
|
-
end
|
903
|
-
|
904
|
-
# @abstract
|
905
|
-
# @!visibility private
|
906
|
-
class AbstractPromise < Synchronization::Object
|
907
|
-
safe_initialization!
|
908
|
-
include Concern::Logging
|
909
|
-
|
910
|
-
def initialize(future)
|
911
|
-
super()
|
912
|
-
@Future = future
|
913
|
-
end
|
914
|
-
|
915
|
-
def future
|
916
|
-
@Future
|
917
|
-
end
|
918
|
-
|
919
|
-
alias_method :event, :future
|
920
|
-
|
921
|
-
def default_executor
|
922
|
-
future.default_executor
|
923
|
-
end
|
924
|
-
|
925
|
-
def state
|
926
|
-
future.state
|
927
|
-
end
|
928
|
-
|
929
|
-
def touch
|
930
|
-
end
|
931
|
-
|
932
|
-
def to_s
|
933
|
-
"<##{self.class}:0x#{'%x' % (object_id << 1)} #{state}>"
|
934
|
-
end
|
935
|
-
|
936
|
-
def inspect
|
937
|
-
to_s
|
938
|
-
end
|
939
|
-
|
940
|
-
private
|
941
|
-
|
942
|
-
def complete_with(new_state, raise_on_reassign = true)
|
943
|
-
@Future.complete_with(new_state, raise_on_reassign)
|
944
|
-
end
|
945
|
-
|
946
|
-
# @return [Future]
|
947
|
-
def evaluate_to(*args, block)
|
948
|
-
complete_with Future::Success.new(block.call(*args))
|
949
|
-
rescue StandardError => error
|
950
|
-
complete_with Future::Failed.new(error)
|
951
|
-
rescue Exception => error
|
952
|
-
log(ERROR, 'Edge::Future', error)
|
953
|
-
complete_with Future::Failed.new(error)
|
954
|
-
end
|
955
|
-
end
|
956
|
-
|
957
|
-
# @!visibility private
|
958
|
-
class CompletableEventPromise < AbstractPromise
|
959
|
-
def initialize(default_executor)
|
960
|
-
super CompletableEvent.new(self, default_executor)
|
961
|
-
end
|
962
|
-
end
|
963
|
-
|
964
|
-
# @!visibility private
|
965
|
-
class CompletableFuturePromise < AbstractPromise
|
966
|
-
def initialize(default_executor)
|
967
|
-
super CompletableFuture.new(self, default_executor)
|
968
|
-
end
|
969
|
-
|
970
|
-
# Set the `Future` to a value and wake or notify all threads waiting on it.
|
971
|
-
#
|
972
|
-
# @param [Object] value the value to store in the `Future`
|
973
|
-
# @raise [Concurrent::MultipleAssignmentError] if the `Future` has already been set or otherwise completed
|
974
|
-
# @return [Future]
|
975
|
-
def success(value)
|
976
|
-
complete_with Future::Success.new(value)
|
977
|
-
end
|
978
|
-
|
979
|
-
def try_success(value)
|
980
|
-
!!complete_with(Future::Success.new(value), false)
|
981
|
-
end
|
982
|
-
|
983
|
-
# Set the `Future` to failed due to some error and wake or notify all threads waiting on it.
|
984
|
-
#
|
985
|
-
# @param [Object] reason for the failure
|
986
|
-
# @raise [Concurrent::MultipleAssignmentError] if the `Future` has already been set or otherwise completed
|
987
|
-
# @return [Future]
|
988
|
-
def fail(reason = StandardError.new)
|
989
|
-
complete_with Future::Failed.new(reason)
|
990
|
-
end
|
991
|
-
|
992
|
-
def try_fail(reason = StandardError.new)
|
993
|
-
!!complete_with(Future::Failed.new(reason), false)
|
994
|
-
end
|
995
|
-
|
996
|
-
public :evaluate_to
|
997
|
-
|
998
|
-
# @return [Future]
|
999
|
-
def evaluate_to!(*args, block)
|
1000
|
-
evaluate_to(*args, block).wait!
|
1001
|
-
end
|
1002
|
-
end
|
1003
|
-
|
1004
|
-
# @abstract
|
1005
|
-
# @!visibility private
|
1006
|
-
class InnerPromise < AbstractPromise
|
1007
|
-
end
|
1008
|
-
|
1009
|
-
# @abstract
|
1010
|
-
# @!visibility private
|
1011
|
-
class BlockedPromise < InnerPromise
|
1012
|
-
def self.new(*args, &block)
|
1013
|
-
promise = super(*args, &block)
|
1014
|
-
promise.blocked_by.each { |f| f.add_callback :callback_notify_blocked, promise }
|
1015
|
-
promise
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
def initialize(future, blocked_by_futures, countdown)
|
1019
|
-
super(future)
|
1020
|
-
initialize_blocked_by(blocked_by_futures)
|
1021
|
-
@Countdown = AtomicFixnum.new countdown
|
1022
|
-
end
|
1023
|
-
|
1024
|
-
# @api private
|
1025
|
-
def on_done(future)
|
1026
|
-
countdown = process_on_done(future)
|
1027
|
-
completable = completable?(countdown, future)
|
1028
|
-
|
1029
|
-
if completable
|
1030
|
-
on_completable(future)
|
1031
|
-
# futures could be deleted from blocked_by one by one here, but that would be too expensive,
|
1032
|
-
# it's done once when all are done to free the reference
|
1033
|
-
clear_blocked_by!
|
1034
|
-
end
|
1035
|
-
end
|
1036
|
-
|
1037
|
-
def touch
|
1038
|
-
blocked_by.each(&:touch)
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
# !visibility private
|
1042
|
-
# for inspection only
|
1043
|
-
def blocked_by
|
1044
|
-
@BlockedBy
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
def inspect
|
1048
|
-
"#{to_s[0..-2]} blocked_by:[#{ blocked_by.map(&:to_s).join(', ')}]>"
|
1049
|
-
end
|
1050
|
-
|
1051
|
-
private
|
1052
|
-
|
1053
|
-
def initialize_blocked_by(blocked_by_futures)
|
1054
|
-
@BlockedBy = [blocked_by_futures].flatten
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
def clear_blocked_by!
|
1058
|
-
# not synchronized because we do not care when this change propagates
|
1059
|
-
@BlockedBy = []
|
1060
|
-
nil
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
# @return [true,false] if completable
|
1064
|
-
def completable?(countdown, future)
|
1065
|
-
countdown.zero?
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
def process_on_done(future)
|
1069
|
-
@Countdown.decrement
|
1070
|
-
end
|
1071
|
-
|
1072
|
-
def on_completable(done_future)
|
1073
|
-
raise NotImplementedError
|
1074
|
-
end
|
1075
|
-
end
|
1076
|
-
|
1077
|
-
# @abstract
|
1078
|
-
# @!visibility private
|
1079
|
-
class BlockedTaskPromise < BlockedPromise
|
1080
|
-
def initialize(blocked_by_future, default_executor, executor, &task)
|
1081
|
-
raise ArgumentError, 'no block given' unless block_given?
|
1082
|
-
super Future.new(self, default_executor), blocked_by_future, 1
|
1083
|
-
@Executor = executor
|
1084
|
-
@Task = task
|
1085
|
-
end
|
1086
|
-
|
1087
|
-
def executor
|
1088
|
-
@Executor
|
1089
|
-
end
|
1090
|
-
end
|
1091
|
-
|
1092
|
-
# @!visibility private
|
1093
|
-
class ThenPromise < BlockedTaskPromise
|
1094
|
-
private
|
1095
|
-
|
1096
|
-
def initialize(blocked_by_future, default_executor, executor, &task)
|
1097
|
-
raise ArgumentError, 'only Future can be appended with then' unless blocked_by_future.is_a? Future
|
1098
|
-
super blocked_by_future, default_executor, executor, &task
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
def on_completable(done_future)
|
1102
|
-
if done_future.success?
|
1103
|
-
Concurrent.post_on(@Executor, done_future, @Task) do |future, task|
|
1104
|
-
evaluate_to lambda { future.apply task }
|
1105
|
-
end
|
1106
|
-
else
|
1107
|
-
complete_with done_future.internal_state
|
1108
|
-
end
|
1109
|
-
end
|
1110
|
-
end
|
1111
|
-
|
1112
|
-
# @!visibility private
|
1113
|
-
class RescuePromise < BlockedTaskPromise
|
1114
|
-
private
|
1115
|
-
|
1116
|
-
def initialize(blocked_by_future, default_executor, executor, &task)
|
1117
|
-
super blocked_by_future, default_executor, executor, &task
|
1118
|
-
end
|
1119
|
-
|
1120
|
-
def on_completable(done_future)
|
1121
|
-
if done_future.failed?
|
1122
|
-
Concurrent.post_on(@Executor, done_future, @Task) do |future, task|
|
1123
|
-
evaluate_to lambda { future.apply task }
|
1124
|
-
end
|
1125
|
-
else
|
1126
|
-
complete_with done_future.internal_state
|
1127
|
-
end
|
1128
|
-
end
|
1129
|
-
end
|
1130
|
-
|
1131
|
-
# @!visibility private
|
1132
|
-
class ChainPromise < BlockedTaskPromise
|
1133
|
-
private
|
1134
|
-
|
1135
|
-
def on_completable(done_future)
|
1136
|
-
if Future === done_future
|
1137
|
-
Concurrent.post_on(@Executor, done_future, @Task) { |future, task| evaluate_to(*future.result, task) }
|
1138
|
-
else
|
1139
|
-
Concurrent.post_on(@Executor, @Task) { |task| evaluate_to task }
|
1140
|
-
end
|
1141
|
-
end
|
1142
|
-
end
|
1143
|
-
|
1144
|
-
# will be immediately completed
|
1145
|
-
# @!visibility private
|
1146
|
-
class ImmediateEventPromise < InnerPromise
|
1147
|
-
def initialize(default_executor)
|
1148
|
-
super Event.new(self, default_executor).complete_with(Event::COMPLETED)
|
1149
|
-
end
|
1150
|
-
end
|
1151
|
-
|
1152
|
-
# @!visibility private
|
1153
|
-
class ImmediateFuturePromise < InnerPromise
|
1154
|
-
def initialize(default_executor, success, value, reason)
|
1155
|
-
super Future.new(self, default_executor).
|
1156
|
-
complete_with(success ? Future::Success.new(value) : Future::Failed.new(reason))
|
1157
|
-
end
|
1158
|
-
end
|
1159
|
-
|
1160
|
-
# @!visibility private
|
1161
|
-
class FlatPromise < BlockedPromise
|
1162
|
-
|
1163
|
-
# !visibility private
|
1164
|
-
def blocked_by
|
1165
|
-
@BlockedBy.each.to_a
|
1166
|
-
end
|
1167
|
-
|
1168
|
-
private
|
1169
|
-
|
1170
|
-
def process_on_done(future)
|
1171
|
-
countdown = super(future)
|
1172
|
-
if countdown.nonzero?
|
1173
|
-
internal_state = future.internal_state
|
1174
|
-
|
1175
|
-
unless internal_state.success?
|
1176
|
-
complete_with internal_state
|
1177
|
-
return countdown
|
1178
|
-
end
|
1179
|
-
|
1180
|
-
value = internal_state.value
|
1181
|
-
case value
|
1182
|
-
when Future
|
1183
|
-
value.touch if self.future.touched
|
1184
|
-
@BlockedBy.push value
|
1185
|
-
value.add_callback :callback_notify_blocked, self
|
1186
|
-
@Countdown.value
|
1187
|
-
when Event
|
1188
|
-
evaluate_to(lambda { raise TypeError, 'cannot flatten to Event' })
|
1189
|
-
else
|
1190
|
-
evaluate_to(lambda { raise TypeError, "returned value #{value.inspect} is not a Future" })
|
1191
|
-
end
|
1192
|
-
end
|
1193
|
-
countdown
|
1194
|
-
end
|
1195
|
-
|
1196
|
-
def initialize(blocked_by_future, levels, default_executor)
|
1197
|
-
raise ArgumentError, 'levels has to be higher than 0' if levels < 1
|
1198
|
-
super Future.new(self, default_executor), blocked_by_future, 1 + levels
|
1199
|
-
end
|
1200
|
-
|
1201
|
-
def initialize_blocked_by(blocked_by_future)
|
1202
|
-
@BlockedBy = LockFreeStack.new.push(blocked_by_future)
|
1203
|
-
end
|
1204
|
-
|
1205
|
-
def on_completable(done_future)
|
1206
|
-
complete_with done_future.internal_state
|
1207
|
-
end
|
1208
|
-
|
1209
|
-
def clear_blocked_by!
|
1210
|
-
@BlockedBy.clear
|
1211
|
-
nil
|
1212
|
-
end
|
1213
|
-
|
1214
|
-
def completable?(countdown, future)
|
1215
|
-
!@Future.internal_state.completed? && super(countdown, future)
|
1216
|
-
end
|
1217
|
-
end
|
1218
|
-
|
1219
|
-
# @!visibility private
|
1220
|
-
class ZipEventEventPromise < BlockedPromise
|
1221
|
-
def initialize(event1, event2, default_executor)
|
1222
|
-
super Event.new(self, default_executor), [event1, event2], 2
|
1223
|
-
end
|
1224
|
-
|
1225
|
-
def on_completable(done_future)
|
1226
|
-
complete_with Event::COMPLETED
|
1227
|
-
end
|
1228
|
-
end
|
1229
|
-
|
1230
|
-
# @!visibility private
|
1231
|
-
class ZipFutureEventPromise < BlockedPromise
|
1232
|
-
def initialize(future, event, default_executor)
|
1233
|
-
super Future.new(self, default_executor), [future, event], 2
|
1234
|
-
@FutureResult = future
|
1235
|
-
end
|
1236
|
-
|
1237
|
-
def on_completable(done_future)
|
1238
|
-
complete_with @FutureResult.internal_state
|
1239
|
-
end
|
1240
|
-
end
|
1241
|
-
|
1242
|
-
# @!visibility private
|
1243
|
-
class ZipFutureFuturePromise < BlockedPromise
|
1244
|
-
def initialize(future1, future2, default_executor)
|
1245
|
-
super Future.new(self, default_executor), [future1, future2], 2
|
1246
|
-
@Future1Result = future1
|
1247
|
-
@Future2Result = future2
|
1248
|
-
end
|
1249
|
-
|
1250
|
-
def on_completable(done_future)
|
1251
|
-
success1, value1, reason1 = @Future1Result.result
|
1252
|
-
success2, value2, reason2 = @Future2Result.result
|
1253
|
-
success = success1 && success2
|
1254
|
-
new_state = if success
|
1255
|
-
Future::SuccessArray.new([value1, value2])
|
1256
|
-
else
|
1257
|
-
Future::PartiallyFailed.new([value1, value2], [reason1, reason2])
|
1258
|
-
end
|
1259
|
-
complete_with new_state
|
1260
|
-
end
|
1261
|
-
end
|
1262
|
-
|
1263
|
-
# @!visibility private
|
1264
|
-
class EventWrapperPromise < BlockedPromise
|
1265
|
-
def initialize(event, default_executor)
|
1266
|
-
super Event.new(self, default_executor), event, 1
|
1267
|
-
end
|
1268
|
-
|
1269
|
-
def on_completable(done_future)
|
1270
|
-
complete_with Event::COMPLETED
|
1271
|
-
end
|
1272
|
-
end
|
1273
|
-
|
1274
|
-
# @!visibility private
|
1275
|
-
class FutureWrapperPromise < BlockedPromise
|
1276
|
-
def initialize(future, default_executor)
|
1277
|
-
super Future.new(self, default_executor), future, 1
|
1278
|
-
end
|
1279
|
-
|
1280
|
-
def on_completable(done_future)
|
1281
|
-
complete_with done_future.internal_state
|
1282
|
-
end
|
1283
|
-
end
|
1284
|
-
|
1285
|
-
# @!visibility private
|
1286
|
-
class ZipFuturesPromise < BlockedPromise
|
1287
|
-
|
1288
|
-
private
|
1289
|
-
|
1290
|
-
def initialize(blocked_by_futures, default_executor)
|
1291
|
-
super(Future.new(self, default_executor), blocked_by_futures, blocked_by_futures.size)
|
1292
|
-
|
1293
|
-
on_completable nil if blocked_by_futures.empty?
|
1294
|
-
end
|
1295
|
-
|
1296
|
-
def on_completable(done_future)
|
1297
|
-
all_success = true
|
1298
|
-
values = Array.new(blocked_by.size)
|
1299
|
-
reasons = Array.new(blocked_by.size)
|
1300
|
-
|
1301
|
-
blocked_by.each_with_index do |future, i|
|
1302
|
-
if future.is_a?(Future)
|
1303
|
-
success, values[i], reasons[i] = future.result
|
1304
|
-
all_success &&= success
|
1305
|
-
else
|
1306
|
-
values[i] = reasons[i] = nil
|
1307
|
-
end
|
1308
|
-
end
|
1309
|
-
|
1310
|
-
if all_success
|
1311
|
-
complete_with Future::SuccessArray.new(values)
|
1312
|
-
else
|
1313
|
-
complete_with Future::PartiallyFailed.new(values, reasons)
|
1314
|
-
end
|
1315
|
-
end
|
1316
|
-
end
|
1317
|
-
|
1318
|
-
# @!visibility private
|
1319
|
-
class ZipEventsPromise < BlockedPromise
|
1320
|
-
|
1321
|
-
private
|
1322
|
-
|
1323
|
-
def initialize(blocked_by_futures, default_executor)
|
1324
|
-
super(Event.new(self, default_executor), blocked_by_futures, blocked_by_futures.size)
|
1325
|
-
|
1326
|
-
on_completable nil if blocked_by_futures.empty?
|
1327
|
-
end
|
1328
|
-
|
1329
|
-
def on_completable(done_future)
|
1330
|
-
complete_with Event::COMPLETED
|
1331
|
-
end
|
1332
|
-
end
|
1333
|
-
|
1334
|
-
# @!visibility private
|
1335
|
-
class AnyCompletePromise < BlockedPromise
|
1336
|
-
|
1337
|
-
private
|
1338
|
-
|
1339
|
-
def initialize(blocked_by_futures, default_executor)
|
1340
|
-
blocked_by_futures.all? { |f| f.is_a? Future } or
|
1341
|
-
raise ArgumentError, 'accepts only Futures not Events'
|
1342
|
-
super(Future.new(self, default_executor), blocked_by_futures, blocked_by_futures.size)
|
1343
|
-
end
|
1344
|
-
|
1345
|
-
def completable?(countdown, future)
|
1346
|
-
true
|
1347
|
-
end
|
1348
|
-
|
1349
|
-
def on_completable(done_future)
|
1350
|
-
complete_with done_future.internal_state, false
|
1351
|
-
end
|
1352
|
-
end
|
1353
|
-
|
1354
|
-
# @!visibility private
|
1355
|
-
class AnySuccessfulPromise < BlockedPromise
|
1356
|
-
|
1357
|
-
private
|
1358
|
-
|
1359
|
-
def initialize(blocked_by_futures, default_executor)
|
1360
|
-
blocked_by_futures.all? { |f| f.is_a? Future } or
|
1361
|
-
raise ArgumentError, 'accepts only Futures not Events'
|
1362
|
-
super(Future.new(self, default_executor), blocked_by_futures, blocked_by_futures.size)
|
1363
|
-
end
|
1364
|
-
|
1365
|
-
def completable?(countdown, future)
|
1366
|
-
future.success? || super(countdown, future)
|
1367
|
-
end
|
1368
|
-
|
1369
|
-
def on_completable(done_future)
|
1370
|
-
complete_with done_future.internal_state, false
|
1371
|
-
end
|
1372
|
-
end
|
1373
|
-
|
1374
|
-
# @!visibility private
|
1375
|
-
class DelayPromise < InnerPromise
|
1376
|
-
def touch
|
1377
|
-
@Future.complete_with Event::COMPLETED
|
1378
|
-
end
|
1379
|
-
|
1380
|
-
private
|
1381
|
-
|
1382
|
-
def initialize(default_executor)
|
1383
|
-
super Event.new(self, default_executor)
|
1384
|
-
end
|
1385
|
-
end
|
1386
|
-
|
1387
|
-
# will be evaluated to task in intended_time
|
1388
|
-
# @!visibility private
|
1389
|
-
class ScheduledPromise < InnerPromise
|
1390
|
-
def intended_time
|
1391
|
-
@IntendedTime
|
1392
|
-
end
|
1393
|
-
|
1394
|
-
def inspect
|
1395
|
-
"#{to_s[0..-2]} intended_time:[#{@IntendedTime}}>"
|
1396
|
-
end
|
1397
|
-
|
1398
|
-
private
|
1399
|
-
|
1400
|
-
def initialize(default_executor, intended_time)
|
1401
|
-
super Event.new(self, default_executor)
|
1402
|
-
|
1403
|
-
@IntendedTime = intended_time
|
1404
|
-
|
1405
|
-
in_seconds = begin
|
1406
|
-
now = Time.now
|
1407
|
-
schedule_time = if @IntendedTime.is_a? Time
|
1408
|
-
@IntendedTime
|
1409
|
-
else
|
1410
|
-
now + @IntendedTime
|
1411
|
-
end
|
1412
|
-
[0, schedule_time.to_f - now.to_f].max
|
1413
|
-
end
|
1414
|
-
|
1415
|
-
Concurrent.global_timer_set.post(in_seconds) do
|
1416
|
-
@Future.complete_with Event::COMPLETED
|
1417
|
-
end
|
1418
|
-
end
|
1419
|
-
end
|
1420
|
-
end
|
1421
|
-
end
|
1422
|
-
|
1423
|
-
Concurrent::Edge.send :extend, Concurrent::Edge::FutureShortcuts
|
1424
|
-
Concurrent::Edge.send :include, Concurrent::Edge::FutureShortcuts
|
1425
|
-
|
1426
|
-
Concurrent.send :extend, Concurrent::Edge::FutureShortcuts
|
1427
|
-
Concurrent.send :include, Concurrent::Edge::FutureShortcuts
|