concurrent-ruby 1.0.5 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +65 -0
- data/Gemfile +39 -0
- data/{LICENSE.txt → LICENSE.md} +2 -0
- data/README.md +207 -105
- data/Rakefile +314 -0
- data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +306 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
- data/lib/concurrent/agent.rb +7 -7
- data/lib/concurrent/array.rb +59 -32
- data/lib/concurrent/async.rb +4 -4
- data/lib/concurrent/atom.rb +9 -9
- data/lib/concurrent/atomic/atomic_boolean.rb +24 -20
- data/lib/concurrent/atomic/atomic_fixnum.rb +27 -23
- data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent/atomic/atomic_reference.rb +185 -32
- data/lib/concurrent/atomic/count_down_latch.rb +6 -6
- data/lib/concurrent/atomic/cyclic_barrier.rb +1 -1
- data/lib/concurrent/atomic/event.rb +1 -1
- data/lib/concurrent/atomic/java_count_down_latch.rb +9 -6
- data/lib/concurrent/atomic/mutex_atomic_boolean.rb +2 -0
- data/lib/concurrent/atomic/mutex_count_down_latch.rb +1 -0
- data/lib/concurrent/atomic/read_write_lock.rb +2 -1
- data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
- data/lib/concurrent/atomic/semaphore.rb +8 -8
- data/lib/concurrent/atomic/thread_local_var.rb +7 -7
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +3 -8
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +1 -1
- data/lib/concurrent/atomics.rb +0 -43
- data/lib/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +3 -3
- data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +1 -2
- data/lib/concurrent/collection/non_concurrent_priority_queue.rb +29 -29
- data/lib/concurrent/concern/dereferenceable.rb +1 -1
- data/lib/concurrent/concern/logging.rb +6 -1
- data/lib/concurrent/concern/observable.rb +7 -7
- data/lib/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent/configuration.rb +1 -6
- data/lib/concurrent/constants.rb +1 -1
- data/lib/concurrent/dataflow.rb +2 -1
- data/lib/concurrent/delay.rb +9 -7
- data/lib/concurrent/exchanger.rb +21 -25
- data/lib/concurrent/executor/abstract_executor_service.rb +2 -2
- data/lib/concurrent/executor/cached_thread_pool.rb +1 -1
- data/lib/concurrent/executor/executor_service.rb +15 -15
- data/lib/concurrent/executor/fixed_thread_pool.rb +18 -18
- data/lib/concurrent/executor/java_thread_pool_executor.rb +10 -7
- data/lib/concurrent/executor/single_thread_executor.rb +2 -2
- data/lib/concurrent/executor/thread_pool_executor.rb +6 -6
- data/lib/concurrent/executor/timer_set.rb +1 -1
- data/lib/concurrent/future.rb +4 -1
- data/lib/concurrent/hash.rb +53 -30
- data/lib/concurrent/ivar.rb +5 -6
- data/lib/concurrent/map.rb +178 -81
- data/lib/concurrent/maybe.rb +1 -1
- data/lib/concurrent/mutable_struct.rb +15 -14
- data/lib/concurrent/mvar.rb +2 -2
- data/lib/concurrent/promise.rb +53 -21
- data/lib/concurrent/promises.rb +1936 -0
- data/lib/concurrent/re_include.rb +58 -0
- data/lib/concurrent/set.rb +66 -0
- data/lib/concurrent/settable_struct.rb +1 -0
- data/lib/concurrent/synchronization/abstract_lockable_object.rb +5 -5
- data/lib/concurrent/synchronization/abstract_struct.rb +6 -4
- data/lib/concurrent/synchronization/lockable_object.rb +6 -6
- data/lib/concurrent/synchronization/{mri_lockable_object.rb → mutex_lockable_object.rb} +19 -14
- data/lib/concurrent/synchronization/object.rb +8 -4
- data/lib/concurrent/synchronization/truffleruby_object.rb +46 -0
- data/lib/concurrent/synchronization/volatile.rb +11 -9
- data/lib/concurrent/synchronization.rb +4 -5
- data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
- data/lib/concurrent/thread_safe/util/striped64.rb +9 -4
- data/lib/concurrent/timer_task.rb +5 -2
- data/lib/concurrent/tuple.rb +1 -1
- data/lib/concurrent/tvar.rb +2 -2
- data/lib/concurrent/utility/193.rb +17 -0
- data/lib/concurrent/utility/at_exit.rb +1 -1
- data/lib/concurrent/utility/engine.rb +4 -4
- data/lib/concurrent/utility/monotonic_time.rb +3 -3
- data/lib/concurrent/utility/native_extension_loader.rb +31 -33
- data/lib/concurrent/utility/processor_counter.rb +0 -2
- data/lib/concurrent/version.rb +2 -2
- data/lib/concurrent-ruby.rb +1 -0
- data/lib/concurrent.rb +26 -20
- metadata +33 -18
- data/lib/concurrent/atomic_reference/concurrent_update_error.rb +0 -8
- data/lib/concurrent/atomic_reference/direct_update.rb +0 -81
- data/lib/concurrent/atomic_reference/jruby+truffle.rb +0 -2
- data/lib/concurrent/atomic_reference/jruby.rb +0 -16
- data/lib/concurrent/atomic_reference/rbx.rb +0 -22
- data/lib/concurrent/atomic_reference/ruby.rb +0 -32
- data/lib/concurrent/edge.rb +0 -26
- data/lib/concurrent/lazy_register.rb +0 -81
- data/lib/concurrent/synchronization/truffle_lockable_object.rb +0 -9
- data/lib/concurrent/synchronization/truffle_object.rb +0 -31
- data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +0 -30
@@ -0,0 +1,1936 @@
|
|
1
|
+
require 'concurrent/synchronization'
|
2
|
+
require 'concurrent/atomic/atomic_boolean'
|
3
|
+
require 'concurrent/atomic/atomic_fixnum'
|
4
|
+
require 'concurrent/collection/lock_free_stack'
|
5
|
+
require 'concurrent/errors'
|
6
|
+
require 'concurrent/re_include'
|
7
|
+
|
8
|
+
module Concurrent
|
9
|
+
|
10
|
+
# {include:file:docs-source/promises-main.md}
|
11
|
+
module Promises
|
12
|
+
|
13
|
+
# @!macro promises.param.default_executor
|
14
|
+
# @param [Executor, :io, :fast] default_executor Instance of an executor or a name of the
|
15
|
+
# global executor. Default executor propagates to chained futures unless overridden with
|
16
|
+
# executor parameter or changed with {AbstractEventFuture#with_default_executor}.
|
17
|
+
#
|
18
|
+
# @!macro promises.param.executor
|
19
|
+
# @param [Executor, :io, :fast] executor Instance of an executor or a name of the
|
20
|
+
# global executor. The task is executed on it, default executor remains unchanged.
|
21
|
+
#
|
22
|
+
# @!macro promises.param.args
|
23
|
+
# @param [Object] args arguments which are passed to the task when it's executed.
|
24
|
+
# (It might be prepended with other arguments, see the @yeild section).
|
25
|
+
#
|
26
|
+
# @!macro promises.shortcut.on
|
27
|
+
# Shortcut of {#$0_on} with default `:io` executor supplied.
|
28
|
+
# @see #$0_on
|
29
|
+
#
|
30
|
+
# @!macro promises.shortcut.using
|
31
|
+
# Shortcut of {#$0_using} with default `:io` executor supplied.
|
32
|
+
# @see #$0_using
|
33
|
+
#
|
34
|
+
# @!macro promise.param.task-future
|
35
|
+
# @yieldreturn will become result of the returned Future.
|
36
|
+
# Its returned value becomes {Future#value} fulfilling it,
|
37
|
+
# raised exception becomes {Future#reason} rejecting it.
|
38
|
+
#
|
39
|
+
# @!macro promise.param.callback
|
40
|
+
# @yieldreturn is forgotten.
|
41
|
+
|
42
|
+
# Container of all {Future}, {Event} factory methods. They are never constructed directly with
|
43
|
+
# new.
|
44
|
+
module FactoryMethods
|
45
|
+
extend ReInclude
|
46
|
+
|
47
|
+
module Configuration
|
48
|
+
# @return [Executor, :io, :fast] the executor which is used when none is supplied
|
49
|
+
# to a factory method. The method can be overridden in the receivers of
|
50
|
+
# `include FactoryMethod`
|
51
|
+
def default_executor
|
52
|
+
:io
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
include Configuration
|
57
|
+
|
58
|
+
# @!macro promises.shortcut.on
|
59
|
+
# @return [ResolvableEvent]
|
60
|
+
def resolvable_event
|
61
|
+
resolvable_event_on default_executor
|
62
|
+
end
|
63
|
+
|
64
|
+
# Created resolvable event, user is responsible for resolving the event once by
|
65
|
+
# {Promises::ResolvableEvent#resolve}.
|
66
|
+
#
|
67
|
+
# @!macro promises.param.default_executor
|
68
|
+
# @return [ResolvableEvent]
|
69
|
+
def resolvable_event_on(default_executor = self.default_executor)
|
70
|
+
ResolvableEventPromise.new(default_executor).future
|
71
|
+
end
|
72
|
+
|
73
|
+
# @!macro promises.shortcut.on
|
74
|
+
# @return [ResolvableFuture]
|
75
|
+
def resolvable_future
|
76
|
+
resolvable_future_on default_executor
|
77
|
+
end
|
78
|
+
|
79
|
+
# Creates resolvable future, user is responsible for resolving the future once by
|
80
|
+
# {Promises::ResolvableFuture#resolve}, {Promises::ResolvableFuture#fulfill},
|
81
|
+
# or {Promises::ResolvableFuture#reject}
|
82
|
+
#
|
83
|
+
# @!macro promises.param.default_executor
|
84
|
+
# @return [ResolvableFuture]
|
85
|
+
def resolvable_future_on(default_executor = self.default_executor)
|
86
|
+
ResolvableFuturePromise.new(default_executor).future
|
87
|
+
end
|
88
|
+
|
89
|
+
# @!macro promises.shortcut.on
|
90
|
+
# @return [Future]
|
91
|
+
def future(*args, &task)
|
92
|
+
future_on(default_executor, *args, &task)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!macro promises.future-on1
|
96
|
+
# Constructs new Future which will be resolved after block is evaluated on default executor.
|
97
|
+
# Evaluation begins immediately.
|
98
|
+
#
|
99
|
+
# @!macro promises.future-on2
|
100
|
+
# @!macro promises.param.default_executor
|
101
|
+
# @!macro promises.param.args
|
102
|
+
# @yield [*args] to the task.
|
103
|
+
# @!macro promise.param.task-future
|
104
|
+
# @return [Future]
|
105
|
+
def future_on(default_executor, *args, &task)
|
106
|
+
ImmediateEventPromise.new(default_executor).future.then(*args, &task)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Creates resolved future with will be either fulfilled with the given value or rejection with
|
110
|
+
# the given reason.
|
111
|
+
#
|
112
|
+
# @!macro promises.param.default_executor
|
113
|
+
# @return [Future]
|
114
|
+
def resolved_future(fulfilled, value, reason, default_executor = self.default_executor)
|
115
|
+
ImmediateFuturePromise.new(default_executor, fulfilled, value, reason).future
|
116
|
+
end
|
117
|
+
|
118
|
+
# Creates resolved future with will be fulfilled with the given value.
|
119
|
+
#
|
120
|
+
# @!macro promises.param.default_executor
|
121
|
+
# @return [Future]
|
122
|
+
def fulfilled_future(value, default_executor = self.default_executor)
|
123
|
+
resolved_future true, value, nil, default_executor
|
124
|
+
end
|
125
|
+
|
126
|
+
# Creates resolved future with will be rejected with the given reason.
|
127
|
+
#
|
128
|
+
# @!macro promises.param.default_executor
|
129
|
+
# @return [Future]
|
130
|
+
def rejected_future(reason, default_executor = self.default_executor)
|
131
|
+
resolved_future false, nil, reason, default_executor
|
132
|
+
end
|
133
|
+
|
134
|
+
# Creates resolved event.
|
135
|
+
#
|
136
|
+
# @!macro promises.param.default_executor
|
137
|
+
# @return [Event]
|
138
|
+
def resolved_event(default_executor = self.default_executor)
|
139
|
+
ImmediateEventPromise.new(default_executor).event
|
140
|
+
end
|
141
|
+
|
142
|
+
# General constructor. Behaves differently based on the argument's type. It's provided for convenience
|
143
|
+
# but it's better to be explicit.
|
144
|
+
#
|
145
|
+
# @see rejected_future, resolved_event, fulfilled_future
|
146
|
+
# @!macro promises.param.default_executor
|
147
|
+
# @return [Event, Future]
|
148
|
+
#
|
149
|
+
# @overload create(nil, default_executor = self.default_executor)
|
150
|
+
# @param [nil] nil
|
151
|
+
# @return [Event] resolved event.
|
152
|
+
#
|
153
|
+
# @overload create(a_future, default_executor = self.default_executor)
|
154
|
+
# @param [Future] a_future
|
155
|
+
# @return [Future] a future which will be resolved when a_future is.
|
156
|
+
#
|
157
|
+
# @overload create(an_event, default_executor = self.default_executor)
|
158
|
+
# @param [Event] an_event
|
159
|
+
# @return [Event] an event which will be resolved when an_event is.
|
160
|
+
#
|
161
|
+
# @overload create(exception, default_executor = self.default_executor)
|
162
|
+
# @param [Exception] exception
|
163
|
+
# @return [Future] a rejected future with the exception as its reason.
|
164
|
+
#
|
165
|
+
# @overload create(value, default_executor = self.default_executor)
|
166
|
+
# @param [Object] value when none of the above overloads fits
|
167
|
+
# @return [Future] a fulfilled future with the value.
|
168
|
+
def make_future(argument = nil, default_executor = self.default_executor)
|
169
|
+
case argument
|
170
|
+
when AbstractEventFuture
|
171
|
+
# returning wrapper would change nothing
|
172
|
+
argument
|
173
|
+
when Exception
|
174
|
+
rejected_future argument, default_executor
|
175
|
+
when nil
|
176
|
+
resolved_event default_executor
|
177
|
+
else
|
178
|
+
fulfilled_future argument, default_executor
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# @!macro promises.shortcut.on
|
183
|
+
# @return [Future]
|
184
|
+
def delay(*args, &task)
|
185
|
+
delay_on default_executor, *args, &task
|
186
|
+
end
|
187
|
+
|
188
|
+
# @!macro promises.future-on1
|
189
|
+
# The task will be evaluated only after the future is touched, see {AbstractEventFuture#touch}
|
190
|
+
#
|
191
|
+
# @!macro promises.future-on2
|
192
|
+
def delay_on(default_executor, *args, &task)
|
193
|
+
DelayPromise.new(default_executor).event.chain(*args, &task)
|
194
|
+
end
|
195
|
+
|
196
|
+
# @!macro promises.shortcut.on
|
197
|
+
# @return [Future]
|
198
|
+
def schedule(intended_time, *args, &task)
|
199
|
+
schedule_on default_executor, intended_time, *args, &task
|
200
|
+
end
|
201
|
+
|
202
|
+
# @!macro promises.future-on1
|
203
|
+
# The task is planned for execution in intended_time.
|
204
|
+
#
|
205
|
+
# @!macro promises.future-on2
|
206
|
+
# @!macro promises.param.intended_time
|
207
|
+
# @param [Numeric, Time] intended_time `Numeric` means to run in `intended_time` seconds.
|
208
|
+
# `Time` means to run on `intended_time`.
|
209
|
+
def schedule_on(default_executor, intended_time, *args, &task)
|
210
|
+
ScheduledPromise.new(default_executor, intended_time).event.chain(*args, &task)
|
211
|
+
end
|
212
|
+
|
213
|
+
# @!macro promises.shortcut.on
|
214
|
+
# @return [Future]
|
215
|
+
def zip_futures(*futures_and_or_events)
|
216
|
+
zip_futures_on default_executor, *futures_and_or_events
|
217
|
+
end
|
218
|
+
|
219
|
+
# Creates new future which is resolved after all futures_and_or_events are resolved.
|
220
|
+
# Its value is array of zipped future values. Its reason is array of reasons for rejection.
|
221
|
+
# If there is an error it rejects.
|
222
|
+
# @!macro promises.event-conversion
|
223
|
+
# If event is supplied, which does not have value and can be only resolved, it's
|
224
|
+
# represented as `:fulfilled` with value `nil`.
|
225
|
+
#
|
226
|
+
# @!macro promises.param.default_executor
|
227
|
+
# @param [AbstractEventFuture] futures_and_or_events
|
228
|
+
# @return [Future]
|
229
|
+
def zip_futures_on(default_executor, *futures_and_or_events)
|
230
|
+
ZipFuturesPromise.new_blocked_by(futures_and_or_events, default_executor).future
|
231
|
+
end
|
232
|
+
|
233
|
+
alias_method :zip, :zip_futures
|
234
|
+
|
235
|
+
# @!macro promises.shortcut.on
|
236
|
+
# @return [Event]
|
237
|
+
def zip_events(*futures_and_or_events)
|
238
|
+
zip_events_on default_executor, *futures_and_or_events
|
239
|
+
end
|
240
|
+
|
241
|
+
# Creates new event which is resolved after all futures_and_or_events are resolved.
|
242
|
+
# (Future is resolved when fulfilled or rejected.)
|
243
|
+
#
|
244
|
+
# @!macro promises.param.default_executor
|
245
|
+
# @param [AbstractEventFuture] futures_and_or_events
|
246
|
+
# @return [Event]
|
247
|
+
def zip_events_on(default_executor, *futures_and_or_events)
|
248
|
+
ZipEventsPromise.new_blocked_by(futures_and_or_events, default_executor).event
|
249
|
+
end
|
250
|
+
|
251
|
+
# @!macro promises.shortcut.on
|
252
|
+
# @return [Future]
|
253
|
+
def any_resolved_future(*futures_and_or_events)
|
254
|
+
any_resolved_future_on default_executor, *futures_and_or_events
|
255
|
+
end
|
256
|
+
|
257
|
+
alias_method :any, :any_resolved_future
|
258
|
+
|
259
|
+
# Creates new future which is resolved after first futures_and_or_events is resolved.
|
260
|
+
# Its result equals result of the first resolved future.
|
261
|
+
# @!macro promises.any-touch
|
262
|
+
# If resolved it does not propagate {AbstractEventFuture#touch}, leaving delayed
|
263
|
+
# futures un-executed if they are not required any more.
|
264
|
+
# @!macro promises.event-conversion
|
265
|
+
#
|
266
|
+
# @!macro promises.param.default_executor
|
267
|
+
# @param [AbstractEventFuture] futures_and_or_events
|
268
|
+
# @return [Future]
|
269
|
+
def any_resolved_future_on(default_executor, *futures_and_or_events)
|
270
|
+
AnyResolvedFuturePromise.new_blocked_by(futures_and_or_events, default_executor).future
|
271
|
+
end
|
272
|
+
|
273
|
+
# @!macro promises.shortcut.on
|
274
|
+
# @return [Future]
|
275
|
+
def any_fulfilled_future(*futures_and_or_events)
|
276
|
+
any_fulfilled_future_on default_executor, *futures_and_or_events
|
277
|
+
end
|
278
|
+
|
279
|
+
# Creates new future which is resolved after first of futures_and_or_events is fulfilled.
|
280
|
+
# Its result equals result of the first resolved future or if all futures_and_or_events reject,
|
281
|
+
# it has reason of the last resolved future.
|
282
|
+
# @!macro promises.any-touch
|
283
|
+
# @!macro promises.event-conversion
|
284
|
+
#
|
285
|
+
# @!macro promises.param.default_executor
|
286
|
+
# @param [AbstractEventFuture] futures_and_or_events
|
287
|
+
# @return [Future]
|
288
|
+
def any_fulfilled_future_on(default_executor, *futures_and_or_events)
|
289
|
+
AnyFulfilledFuturePromise.new_blocked_by(futures_and_or_events, default_executor).future
|
290
|
+
end
|
291
|
+
|
292
|
+
# @!macro promises.shortcut.on
|
293
|
+
# @return [Future]
|
294
|
+
def any_event(*futures_and_or_events)
|
295
|
+
any_event_on default_executor, *futures_and_or_events
|
296
|
+
end
|
297
|
+
|
298
|
+
# Creates new event which becomes resolved after first of the futures_and_or_events resolves.
|
299
|
+
# @!macro promises.any-touch
|
300
|
+
#
|
301
|
+
# @!macro promises.param.default_executor
|
302
|
+
# @param [AbstractEventFuture] futures_and_or_events
|
303
|
+
# @return [Event]
|
304
|
+
def any_event_on(default_executor, *futures_and_or_events)
|
305
|
+
AnyResolvedEventPromise.new_blocked_by(futures_and_or_events, default_executor).event
|
306
|
+
end
|
307
|
+
|
308
|
+
# TODO consider adding first(count, *futures)
|
309
|
+
# TODO consider adding zip_by(slice, *futures) processing futures in slices
|
310
|
+
# TODO or rather a generic aggregator taking a function
|
311
|
+
end
|
312
|
+
|
313
|
+
module InternalStates
|
314
|
+
# @private
|
315
|
+
class State
|
316
|
+
def resolved?
|
317
|
+
raise NotImplementedError
|
318
|
+
end
|
319
|
+
|
320
|
+
def to_sym
|
321
|
+
raise NotImplementedError
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
private_constant :State
|
326
|
+
|
327
|
+
# @private
|
328
|
+
class Pending < State
|
329
|
+
def resolved?
|
330
|
+
false
|
331
|
+
end
|
332
|
+
|
333
|
+
def to_sym
|
334
|
+
:pending
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
private_constant :Pending
|
339
|
+
|
340
|
+
# @private
|
341
|
+
class ResolvedWithResult < State
|
342
|
+
def resolved?
|
343
|
+
true
|
344
|
+
end
|
345
|
+
|
346
|
+
def to_sym
|
347
|
+
:resolved
|
348
|
+
end
|
349
|
+
|
350
|
+
def result
|
351
|
+
[fulfilled?, value, reason]
|
352
|
+
end
|
353
|
+
|
354
|
+
def fulfilled?
|
355
|
+
raise NotImplementedError
|
356
|
+
end
|
357
|
+
|
358
|
+
def value
|
359
|
+
raise NotImplementedError
|
360
|
+
end
|
361
|
+
|
362
|
+
def reason
|
363
|
+
raise NotImplementedError
|
364
|
+
end
|
365
|
+
|
366
|
+
def apply
|
367
|
+
raise NotImplementedError
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
private_constant :ResolvedWithResult
|
372
|
+
|
373
|
+
# @private
|
374
|
+
class Fulfilled < ResolvedWithResult
|
375
|
+
|
376
|
+
def initialize(value)
|
377
|
+
@Value = value
|
378
|
+
end
|
379
|
+
|
380
|
+
def fulfilled?
|
381
|
+
true
|
382
|
+
end
|
383
|
+
|
384
|
+
def apply(args, block)
|
385
|
+
block.call value, *args
|
386
|
+
end
|
387
|
+
|
388
|
+
def value
|
389
|
+
@Value
|
390
|
+
end
|
391
|
+
|
392
|
+
def reason
|
393
|
+
nil
|
394
|
+
end
|
395
|
+
|
396
|
+
def to_sym
|
397
|
+
:fulfilled
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
private_constant :Fulfilled
|
402
|
+
|
403
|
+
# @private
|
404
|
+
class FulfilledArray < Fulfilled
|
405
|
+
def apply(args, block)
|
406
|
+
block.call(*value, *args)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
private_constant :FulfilledArray
|
411
|
+
|
412
|
+
# @private
|
413
|
+
class Rejected < ResolvedWithResult
|
414
|
+
def initialize(reason)
|
415
|
+
@Reason = reason
|
416
|
+
end
|
417
|
+
|
418
|
+
def fulfilled?
|
419
|
+
false
|
420
|
+
end
|
421
|
+
|
422
|
+
def value
|
423
|
+
nil
|
424
|
+
end
|
425
|
+
|
426
|
+
def reason
|
427
|
+
@Reason
|
428
|
+
end
|
429
|
+
|
430
|
+
def to_sym
|
431
|
+
:rejected
|
432
|
+
end
|
433
|
+
|
434
|
+
def apply(args, block)
|
435
|
+
block.call reason, *args
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
private_constant :Rejected
|
440
|
+
|
441
|
+
# @private
|
442
|
+
class PartiallyRejected < ResolvedWithResult
|
443
|
+
def initialize(value, reason)
|
444
|
+
super()
|
445
|
+
@Value = value
|
446
|
+
@Reason = reason
|
447
|
+
end
|
448
|
+
|
449
|
+
def fulfilled?
|
450
|
+
false
|
451
|
+
end
|
452
|
+
|
453
|
+
def to_sym
|
454
|
+
:rejected
|
455
|
+
end
|
456
|
+
|
457
|
+
def value
|
458
|
+
@Value
|
459
|
+
end
|
460
|
+
|
461
|
+
def reason
|
462
|
+
@Reason
|
463
|
+
end
|
464
|
+
|
465
|
+
def apply(args, block)
|
466
|
+
block.call(*reason, *args)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
private_constant :PartiallyRejected
|
471
|
+
|
472
|
+
PENDING = Pending.new
|
473
|
+
RESOLVED = Fulfilled.new(nil)
|
474
|
+
|
475
|
+
def RESOLVED.to_sym
|
476
|
+
:resolved
|
477
|
+
end
|
478
|
+
|
479
|
+
private_constant :PENDING, :RESOLVED
|
480
|
+
end
|
481
|
+
|
482
|
+
private_constant :InternalStates
|
483
|
+
|
484
|
+
# Common ancestor of {Event} and {Future} classes, many shared methods are defined here.
|
485
|
+
class AbstractEventFuture < Synchronization::Object
|
486
|
+
safe_initialization!
|
487
|
+
private(*attr_atomic(:internal_state) - [:internal_state])
|
488
|
+
|
489
|
+
include InternalStates
|
490
|
+
|
491
|
+
def initialize(promise, default_executor)
|
492
|
+
super()
|
493
|
+
@Lock = Mutex.new
|
494
|
+
@Condition = ConditionVariable.new
|
495
|
+
@Promise = promise
|
496
|
+
@DefaultExecutor = default_executor
|
497
|
+
@Callbacks = LockFreeStack.new
|
498
|
+
@Waiters = AtomicFixnum.new 0
|
499
|
+
self.internal_state = PENDING
|
500
|
+
end
|
501
|
+
|
502
|
+
private :initialize
|
503
|
+
|
504
|
+
# @!macro promises.shortcut.event-future
|
505
|
+
# @see Event#$0
|
506
|
+
# @see Future#$0
|
507
|
+
|
508
|
+
# @!macro promises.param.timeout
|
509
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
510
|
+
|
511
|
+
# @!macro promises.warn.blocks
|
512
|
+
# @note This function potentially blocks current thread until the Future is resolved.
|
513
|
+
# Be careful it can deadlock. Try to chain instead.
|
514
|
+
|
515
|
+
# Returns its state.
|
516
|
+
# @return [Symbol]
|
517
|
+
#
|
518
|
+
# @overload an_event.state
|
519
|
+
# @return [:pending, :resolved]
|
520
|
+
# @overload a_future.state
|
521
|
+
# Both :fulfilled, :rejected implies :resolved.
|
522
|
+
# @return [:pending, :fulfilled, :rejected]
|
523
|
+
def state
|
524
|
+
internal_state.to_sym
|
525
|
+
end
|
526
|
+
|
527
|
+
# Is it in pending state?
|
528
|
+
# @return [Boolean]
|
529
|
+
def pending?
|
530
|
+
!internal_state.resolved?
|
531
|
+
end
|
532
|
+
|
533
|
+
# Is it in resolved state?
|
534
|
+
# @return [Boolean]
|
535
|
+
def resolved?
|
536
|
+
internal_state.resolved?
|
537
|
+
end
|
538
|
+
|
539
|
+
# Propagates touch. Requests all the delayed futures, which it depends on, to be
|
540
|
+
# executed. This method is called by any other method requiring resolved state, like {#wait}.
|
541
|
+
# @return [self]
|
542
|
+
def touch
|
543
|
+
@Promise.touch
|
544
|
+
self
|
545
|
+
end
|
546
|
+
|
547
|
+
# @!macro promises.touches
|
548
|
+
# Calls {AbstractEventFuture#touch}.
|
549
|
+
|
550
|
+
# @!macro promises.method.wait
|
551
|
+
# Wait (block the Thread) until receiver is {#resolved?}.
|
552
|
+
# @!macro promises.touches
|
553
|
+
#
|
554
|
+
# @!macro promises.warn.blocks
|
555
|
+
# @!macro promises.param.timeout
|
556
|
+
# @return [Future, true, false] self implies timeout was not used, true implies timeout was used
|
557
|
+
# and it was resolved, false implies it was not resolved within timeout.
|
558
|
+
def wait(timeout = nil)
|
559
|
+
result = wait_until_resolved(timeout)
|
560
|
+
timeout ? result : self
|
561
|
+
end
|
562
|
+
|
563
|
+
# Returns default executor.
|
564
|
+
# @return [Executor] default executor
|
565
|
+
# @see #with_default_executor
|
566
|
+
# @see FactoryMethods#future_on
|
567
|
+
# @see FactoryMethods#resolvable_future
|
568
|
+
# @see FactoryMethods#any_fulfilled_future_on
|
569
|
+
# @see similar
|
570
|
+
def default_executor
|
571
|
+
@DefaultExecutor
|
572
|
+
end
|
573
|
+
|
574
|
+
# @!macro promises.shortcut.on
|
575
|
+
# @return [Future]
|
576
|
+
def chain(*args, &task)
|
577
|
+
chain_on @DefaultExecutor, *args, &task
|
578
|
+
end
|
579
|
+
|
580
|
+
# Chains the task to be executed asynchronously on executor after it is resolved.
|
581
|
+
#
|
582
|
+
# @!macro promises.param.executor
|
583
|
+
# @!macro promises.param.args
|
584
|
+
# @return [Future]
|
585
|
+
# @!macro promise.param.task-future
|
586
|
+
#
|
587
|
+
# @overload an_event.chain_on(executor, *args, &task)
|
588
|
+
# @yield [*args] to the task.
|
589
|
+
# @overload a_future.chain_on(executor, *args, &task)
|
590
|
+
# @yield [fulfilled, value, reason, *args] to the task.
|
591
|
+
# @yieldparam [true, false] fulfilled
|
592
|
+
# @yieldparam [Object] value
|
593
|
+
# @yieldparam [Exception] reason
|
594
|
+
def chain_on(executor, *args, &task)
|
595
|
+
ChainPromise.new_blocked_by1(self, @DefaultExecutor, executor, args, &task).future
|
596
|
+
end
|
597
|
+
|
598
|
+
# @return [String] Short string representation.
|
599
|
+
def to_s
|
600
|
+
format '%s %s>', super[0..-2], state
|
601
|
+
end
|
602
|
+
|
603
|
+
alias_method :inspect, :to_s
|
604
|
+
|
605
|
+
# Resolves the resolvable when receiver is resolved.
|
606
|
+
#
|
607
|
+
# @param [Resolvable] resolvable
|
608
|
+
# @return [self]
|
609
|
+
def chain_resolvable(resolvable)
|
610
|
+
on_resolution! { resolvable.resolve_with internal_state }
|
611
|
+
end
|
612
|
+
|
613
|
+
alias_method :tangle, :chain_resolvable
|
614
|
+
|
615
|
+
# @!macro promises.shortcut.using
|
616
|
+
# @return [self]
|
617
|
+
def on_resolution(*args, &callback)
|
618
|
+
on_resolution_using @DefaultExecutor, *args, &callback
|
619
|
+
end
|
620
|
+
|
621
|
+
# Stores the callback to be executed synchronously on resolving thread after it is
|
622
|
+
# resolved.
|
623
|
+
#
|
624
|
+
# @!macro promises.param.args
|
625
|
+
# @!macro promise.param.callback
|
626
|
+
# @return [self]
|
627
|
+
#
|
628
|
+
# @overload an_event.on_resolution!(*args, &callback)
|
629
|
+
# @yield [*args] to the callback.
|
630
|
+
# @overload a_future.on_resolution!(*args, &callback)
|
631
|
+
# @yield [fulfilled, value, reason, *args] to the callback.
|
632
|
+
# @yieldparam [true, false] fulfilled
|
633
|
+
# @yieldparam [Object] value
|
634
|
+
# @yieldparam [Exception] reason
|
635
|
+
def on_resolution!(*args, &callback)
|
636
|
+
add_callback :callback_on_resolution, args, callback
|
637
|
+
end
|
638
|
+
|
639
|
+
# Stores the callback to be executed asynchronously on executor after it is resolved.
|
640
|
+
#
|
641
|
+
# @!macro promises.param.executor
|
642
|
+
# @!macro promises.param.args
|
643
|
+
# @!macro promise.param.callback
|
644
|
+
# @return [self]
|
645
|
+
#
|
646
|
+
# @overload an_event.on_resolution_using(executor, *args, &callback)
|
647
|
+
# @yield [*args] to the callback.
|
648
|
+
# @overload a_future.on_resolution_using(executor, *args, &callback)
|
649
|
+
# @yield [fulfilled, value, reason, *args] to the callback.
|
650
|
+
# @yieldparam [true, false] fulfilled
|
651
|
+
# @yieldparam [Object] value
|
652
|
+
# @yieldparam [Exception] reason
|
653
|
+
def on_resolution_using(executor, *args, &callback)
|
654
|
+
add_callback :async_callback_on_resolution, executor, args, callback
|
655
|
+
end
|
656
|
+
|
657
|
+
# @!macro promises.method.with_default_executor
|
658
|
+
# Crates new object with same class with the executor set as its new default executor.
|
659
|
+
# Any futures depending on it will use the new default executor.
|
660
|
+
# @!macro promises.shortcut.event-future
|
661
|
+
# @abstract
|
662
|
+
# @return [AbstractEventFuture]
|
663
|
+
def with_default_executor(executor)
|
664
|
+
raise NotImplementedError
|
665
|
+
end
|
666
|
+
|
667
|
+
# @!visibility private
|
668
|
+
def resolve_with(state, raise_on_reassign = true)
|
669
|
+
if compare_and_set_internal_state(PENDING, state)
|
670
|
+
# go to synchronized block only if there were waiting threads
|
671
|
+
@Lock.synchronize { @Condition.broadcast } unless @Waiters.value == 0
|
672
|
+
call_callbacks state
|
673
|
+
else
|
674
|
+
return rejected_resolution(raise_on_reassign, state)
|
675
|
+
end
|
676
|
+
self
|
677
|
+
end
|
678
|
+
|
679
|
+
# For inspection.
|
680
|
+
# @!visibility private
|
681
|
+
# @return [Array<AbstractPromise>]
|
682
|
+
def blocks
|
683
|
+
@Callbacks.each_with_object([]) do |(method, args), promises|
|
684
|
+
promises.push(args[0]) if method == :callback_notify_blocked
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
# For inspection.
|
689
|
+
# @!visibility private
|
690
|
+
def callbacks
|
691
|
+
@Callbacks.each.to_a
|
692
|
+
end
|
693
|
+
|
694
|
+
# For inspection.
|
695
|
+
# @!visibility private
|
696
|
+
def promise
|
697
|
+
@Promise
|
698
|
+
end
|
699
|
+
|
700
|
+
# For inspection.
|
701
|
+
# @!visibility private
|
702
|
+
def touched?
|
703
|
+
promise.touched?
|
704
|
+
end
|
705
|
+
|
706
|
+
# For inspection.
|
707
|
+
# @!visibility private
|
708
|
+
def waiting_threads
|
709
|
+
@Waiters.each.to_a
|
710
|
+
end
|
711
|
+
|
712
|
+
# @!visibility private
|
713
|
+
def add_callback_notify_blocked(promise, index)
|
714
|
+
add_callback :callback_notify_blocked, promise, index
|
715
|
+
end
|
716
|
+
|
717
|
+
# @!visibility private
|
718
|
+
def add_callback_clear_delayed_node(node)
|
719
|
+
add_callback(:callback_clear_delayed_node, node)
|
720
|
+
end
|
721
|
+
|
722
|
+
private
|
723
|
+
|
724
|
+
def add_callback(method, *args)
|
725
|
+
state = internal_state
|
726
|
+
if state.resolved?
|
727
|
+
call_callback method, state, args
|
728
|
+
else
|
729
|
+
@Callbacks.push [method, args]
|
730
|
+
state = internal_state
|
731
|
+
# take back if it was resolved in the meanwhile
|
732
|
+
call_callbacks state if state.resolved?
|
733
|
+
end
|
734
|
+
self
|
735
|
+
end
|
736
|
+
|
737
|
+
def callback_clear_delayed_node(state, node)
|
738
|
+
node.value = nil
|
739
|
+
end
|
740
|
+
|
741
|
+
# @return [Boolean]
|
742
|
+
def wait_until_resolved(timeout)
|
743
|
+
return true if resolved?
|
744
|
+
|
745
|
+
touch
|
746
|
+
|
747
|
+
@Lock.synchronize do
|
748
|
+
@Waiters.increment
|
749
|
+
begin
|
750
|
+
unless resolved?
|
751
|
+
@Condition.wait @Lock, timeout
|
752
|
+
end
|
753
|
+
ensure
|
754
|
+
# JRuby may raise ConcurrencyError
|
755
|
+
@Waiters.decrement
|
756
|
+
end
|
757
|
+
end
|
758
|
+
resolved?
|
759
|
+
end
|
760
|
+
|
761
|
+
def call_callback(method, state, args)
|
762
|
+
self.send method, state, *args
|
763
|
+
end
|
764
|
+
|
765
|
+
def call_callbacks(state)
|
766
|
+
method, args = @Callbacks.pop
|
767
|
+
while method
|
768
|
+
call_callback method, state, args
|
769
|
+
method, args = @Callbacks.pop
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
def with_async(executor, *args, &block)
|
774
|
+
Concurrent.executor(executor).post(*args, &block)
|
775
|
+
end
|
776
|
+
|
777
|
+
def async_callback_on_resolution(state, executor, args, callback)
|
778
|
+
with_async(executor, state, args, callback) do |st, ar, cb|
|
779
|
+
callback_on_resolution st, ar, cb
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
def callback_notify_blocked(state, promise, index)
|
784
|
+
promise.on_blocker_resolution self, index
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
# Represents an event which will happen in future (will be resolved). The event is either
|
789
|
+
# pending or resolved. It should be always resolved. Use {Future} to communicate rejections and
|
790
|
+
# cancellation.
|
791
|
+
class Event < AbstractEventFuture
|
792
|
+
|
793
|
+
alias_method :then, :chain
|
794
|
+
|
795
|
+
|
796
|
+
# @!macro promises.method.zip
|
797
|
+
# Creates a new event or a future which will be resolved when receiver and other are.
|
798
|
+
# Returns an event if receiver and other are events, otherwise returns a future.
|
799
|
+
# If just one of the parties is Future then the result
|
800
|
+
# of the returned future is equal to the result of the supplied future. If both are futures
|
801
|
+
# then the result is as described in {FactoryMethods#zip_futures_on}.
|
802
|
+
#
|
803
|
+
# @return [Future, Event]
|
804
|
+
def zip(other)
|
805
|
+
if other.is_a?(Future)
|
806
|
+
ZipFutureEventPromise.new_blocked_by2(other, self, @DefaultExecutor).future
|
807
|
+
else
|
808
|
+
ZipEventEventPromise.new_blocked_by2(self, other, @DefaultExecutor).event
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
alias_method :&, :zip
|
813
|
+
|
814
|
+
# Creates a new event which will be resolved when the first of receiver, `event_or_future`
|
815
|
+
# resolves.
|
816
|
+
#
|
817
|
+
# @return [Event]
|
818
|
+
def any(event_or_future)
|
819
|
+
AnyResolvedEventPromise.new_blocked_by2(self, event_or_future, @DefaultExecutor).event
|
820
|
+
end
|
821
|
+
|
822
|
+
alias_method :|, :any
|
823
|
+
|
824
|
+
# Creates new event dependent on receiver which will not evaluate until touched, see {#touch}.
|
825
|
+
# In other words, it inserts delay into the chain of Futures making rest of it lazy evaluated.
|
826
|
+
#
|
827
|
+
# @return [Event]
|
828
|
+
def delay
|
829
|
+
event = DelayPromise.new(@DefaultExecutor).event
|
830
|
+
ZipEventEventPromise.new_blocked_by2(self, event, @DefaultExecutor).event
|
831
|
+
end
|
832
|
+
|
833
|
+
# @!macro promise.method.schedule
|
834
|
+
# Creates new event dependent on receiver scheduled to execute on/in intended_time.
|
835
|
+
# In time is interpreted from the moment the receiver is resolved, therefore it inserts
|
836
|
+
# delay into the chain.
|
837
|
+
#
|
838
|
+
# @!macro promises.param.intended_time
|
839
|
+
# @return [Event]
|
840
|
+
def schedule(intended_time)
|
841
|
+
chain do
|
842
|
+
event = ScheduledPromise.new(@DefaultExecutor, intended_time).event
|
843
|
+
ZipEventEventPromise.new_blocked_by2(self, event, @DefaultExecutor).event
|
844
|
+
end.flat_event
|
845
|
+
end
|
846
|
+
|
847
|
+
# Converts event to a future. The future is fulfilled when the event is resolved, the future may never fail.
|
848
|
+
#
|
849
|
+
# @return [Future]
|
850
|
+
def to_future
|
851
|
+
future = Promises.resolvable_future
|
852
|
+
ensure
|
853
|
+
chain_resolvable(future)
|
854
|
+
end
|
855
|
+
|
856
|
+
# Returns self, since this is event
|
857
|
+
# @return [Event]
|
858
|
+
def to_event
|
859
|
+
self
|
860
|
+
end
|
861
|
+
|
862
|
+
# @!macro promises.method.with_default_executor
|
863
|
+
# @return [Event]
|
864
|
+
def with_default_executor(executor)
|
865
|
+
EventWrapperPromise.new_blocked_by1(self, executor).event
|
866
|
+
end
|
867
|
+
|
868
|
+
private
|
869
|
+
|
870
|
+
def rejected_resolution(raise_on_reassign, state)
|
871
|
+
Concurrent::MultipleAssignmentError.new('Event can be resolved only once') if raise_on_reassign
|
872
|
+
return false
|
873
|
+
end
|
874
|
+
|
875
|
+
def callback_on_resolution(state, args, callback)
|
876
|
+
callback.call(*args)
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
# Represents a value which will become available in future. May reject with a reason instead,
|
881
|
+
# e.g. when the tasks raises an exception.
|
882
|
+
class Future < AbstractEventFuture
|
883
|
+
|
884
|
+
# Is it in fulfilled state?
|
885
|
+
# @return [Boolean]
|
886
|
+
def fulfilled?
|
887
|
+
state = internal_state
|
888
|
+
state.resolved? && state.fulfilled?
|
889
|
+
end
|
890
|
+
|
891
|
+
# Is it in rejected state?
|
892
|
+
# @return [Boolean]
|
893
|
+
def rejected?
|
894
|
+
state = internal_state
|
895
|
+
state.resolved? && !state.fulfilled?
|
896
|
+
end
|
897
|
+
|
898
|
+
# @!macro promises.warn.nil
|
899
|
+
# @note Make sure returned `nil` is not confused with timeout, no value when rejected,
|
900
|
+
# no reason when fulfilled, etc.
|
901
|
+
# Use more exact methods if needed, like {#wait}, {#value!}, {#result}, etc.
|
902
|
+
|
903
|
+
# @!macro promises.method.value
|
904
|
+
# Return value of the future.
|
905
|
+
# @!macro promises.touches
|
906
|
+
#
|
907
|
+
# @!macro promises.warn.blocks
|
908
|
+
# @!macro promises.warn.nil
|
909
|
+
# @!macro promises.param.timeout
|
910
|
+
# @return [Object, nil] the value of the Future when fulfilled, nil on timeout or rejection.
|
911
|
+
def value(timeout = nil)
|
912
|
+
internal_state.value if wait_until_resolved timeout
|
913
|
+
end
|
914
|
+
|
915
|
+
# Returns reason of future's rejection.
|
916
|
+
# @!macro promises.touches
|
917
|
+
#
|
918
|
+
# @!macro promises.warn.blocks
|
919
|
+
# @!macro promises.warn.nil
|
920
|
+
# @!macro promises.param.timeout
|
921
|
+
# @return [Exception, nil] nil on timeout or fulfillment.
|
922
|
+
def reason(timeout = nil)
|
923
|
+
internal_state.reason if wait_until_resolved timeout
|
924
|
+
end
|
925
|
+
|
926
|
+
# Returns triplet fulfilled?, value, reason.
|
927
|
+
# @!macro promises.touches
|
928
|
+
#
|
929
|
+
# @!macro promises.warn.blocks
|
930
|
+
# @!macro promises.param.timeout
|
931
|
+
# @return [Array(Boolean, Object, Exception), nil] triplet of fulfilled?, value, reason, or nil
|
932
|
+
# on timeout.
|
933
|
+
def result(timeout = nil)
|
934
|
+
internal_state.result if wait_until_resolved timeout
|
935
|
+
end
|
936
|
+
|
937
|
+
# @!macro promises.method.wait
|
938
|
+
# @raise [Exception] {#reason} on rejection
|
939
|
+
def wait!(timeout = nil)
|
940
|
+
result = wait_until_resolved!(timeout)
|
941
|
+
timeout ? result : self
|
942
|
+
end
|
943
|
+
|
944
|
+
# @!macro promises.method.value
|
945
|
+
# @return [Object, nil] the value of the Future when fulfilled, nil on timeout.
|
946
|
+
# @raise [Exception] {#reason} on rejection
|
947
|
+
def value!(timeout = nil)
|
948
|
+
internal_state.value if wait_until_resolved! timeout
|
949
|
+
end
|
950
|
+
|
951
|
+
# Allows rejected Future to be risen with `raise` method.
|
952
|
+
# @example
|
953
|
+
# raise Promises.rejected_future(StandardError.new("boom"))
|
954
|
+
# @raise [StandardError] when raising not rejected future
|
955
|
+
# @return [Exception]
|
956
|
+
def exception(*args)
|
957
|
+
raise Concurrent::Error, 'it is not rejected' unless rejected?
|
958
|
+
reason = Array(internal_state.reason).flatten.compact
|
959
|
+
if reason.size > 1
|
960
|
+
ex = Concurrent::MultipleErrors.new reason
|
961
|
+
ex.set_backtrace(caller)
|
962
|
+
ex
|
963
|
+
else
|
964
|
+
ex = reason[0].clone.exception(*args)
|
965
|
+
ex.set_backtrace Array(ex.backtrace) + caller
|
966
|
+
ex
|
967
|
+
end
|
968
|
+
end
|
969
|
+
|
970
|
+
# @!macro promises.shortcut.on
|
971
|
+
# @return [Future]
|
972
|
+
def then(*args, &task)
|
973
|
+
then_on @DefaultExecutor, *args, &task
|
974
|
+
end
|
975
|
+
|
976
|
+
# Chains the task to be executed asynchronously on executor after it fulfills. Does not run
|
977
|
+
# the task if it rejects. It will resolve though, triggering any dependent futures.
|
978
|
+
#
|
979
|
+
# @!macro promises.param.executor
|
980
|
+
# @!macro promises.param.args
|
981
|
+
# @!macro promise.param.task-future
|
982
|
+
# @return [Future]
|
983
|
+
# @yield [value, *args] to the task.
|
984
|
+
def then_on(executor, *args, &task)
|
985
|
+
ThenPromise.new_blocked_by1(self, @DefaultExecutor, executor, args, &task).future
|
986
|
+
end
|
987
|
+
|
988
|
+
# @!macro promises.shortcut.on
|
989
|
+
# @return [Future]
|
990
|
+
def rescue(*args, &task)
|
991
|
+
rescue_on @DefaultExecutor, *args, &task
|
992
|
+
end
|
993
|
+
|
994
|
+
# Chains the task to be executed asynchronously on executor after it rejects. Does not run
|
995
|
+
# the task if it fulfills. It will resolve though, triggering any dependent futures.
|
996
|
+
#
|
997
|
+
# @!macro promises.param.executor
|
998
|
+
# @!macro promises.param.args
|
999
|
+
# @!macro promise.param.task-future
|
1000
|
+
# @return [Future]
|
1001
|
+
# @yield [reason, *args] to the task.
|
1002
|
+
def rescue_on(executor, *args, &task)
|
1003
|
+
RescuePromise.new_blocked_by1(self, @DefaultExecutor, executor, args, &task).future
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
# @!macro promises.method.zip
|
1007
|
+
# @return [Future]
|
1008
|
+
def zip(other)
|
1009
|
+
if other.is_a?(Future)
|
1010
|
+
ZipFuturesPromise.new_blocked_by2(self, other, @DefaultExecutor).future
|
1011
|
+
else
|
1012
|
+
ZipFutureEventPromise.new_blocked_by2(self, other, @DefaultExecutor).future
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
alias_method :&, :zip
|
1017
|
+
|
1018
|
+
# Creates a new event which will be resolved when the first of receiver, `event_or_future`
|
1019
|
+
# resolves. Returning future will have value nil if event_or_future is event and resolves
|
1020
|
+
# first.
|
1021
|
+
#
|
1022
|
+
# @return [Future]
|
1023
|
+
def any(event_or_future)
|
1024
|
+
AnyResolvedFuturePromise.new_blocked_by2(self, event_or_future, @DefaultExecutor).future
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
alias_method :|, :any
|
1028
|
+
|
1029
|
+
# Creates new future dependent on receiver which will not evaluate until touched, see {#touch}.
|
1030
|
+
# In other words, it inserts delay into the chain of Futures making rest of it lazy evaluated.
|
1031
|
+
#
|
1032
|
+
# @return [Future]
|
1033
|
+
def delay
|
1034
|
+
event = DelayPromise.new(@DefaultExecutor).event
|
1035
|
+
ZipFutureEventPromise.new_blocked_by2(self, event, @DefaultExecutor).future
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
# @!macro promise.method.schedule
|
1039
|
+
# @return [Future]
|
1040
|
+
def schedule(intended_time)
|
1041
|
+
chain do
|
1042
|
+
event = ScheduledPromise.new(@DefaultExecutor, intended_time).event
|
1043
|
+
ZipFutureEventPromise.new_blocked_by2(self, event, @DefaultExecutor).future
|
1044
|
+
end.flat
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# @!macro promises.method.with_default_executor
|
1048
|
+
# @return [Future]
|
1049
|
+
def with_default_executor(executor)
|
1050
|
+
FutureWrapperPromise.new_blocked_by1(self, executor).future
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
# Creates new future which will have result of the future returned by receiver. If receiver
|
1054
|
+
# rejects it will have its rejection.
|
1055
|
+
#
|
1056
|
+
# @param [Integer] level how many levels of futures should flatten
|
1057
|
+
# @return [Future]
|
1058
|
+
def flat_future(level = 1)
|
1059
|
+
FlatFuturePromise.new_blocked_by1(self, level, @DefaultExecutor).future
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
alias_method :flat, :flat_future
|
1063
|
+
|
1064
|
+
# Creates new event which will be resolved when the returned event by receiver is.
|
1065
|
+
# Be careful if the receiver rejects it will just resolve since Event does not hold reason.
|
1066
|
+
#
|
1067
|
+
# @return [Event]
|
1068
|
+
def flat_event
|
1069
|
+
FlatEventPromise.new_blocked_by1(self, @DefaultExecutor).event
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
# @!macro promises.shortcut.using
|
1073
|
+
# @return [self]
|
1074
|
+
def on_fulfillment(*args, &callback)
|
1075
|
+
on_fulfillment_using @DefaultExecutor, *args, &callback
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
# Stores the callback to be executed synchronously on resolving thread after it is
|
1079
|
+
# fulfilled. Does nothing on rejection.
|
1080
|
+
#
|
1081
|
+
# @!macro promises.param.args
|
1082
|
+
# @!macro promise.param.callback
|
1083
|
+
# @return [self]
|
1084
|
+
# @yield [value, *args] to the callback.
|
1085
|
+
def on_fulfillment!(*args, &callback)
|
1086
|
+
add_callback :callback_on_fulfillment, args, callback
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
# Stores the callback to be executed asynchronously on executor after it is
|
1090
|
+
# fulfilled. Does nothing on rejection.
|
1091
|
+
#
|
1092
|
+
# @!macro promises.param.executor
|
1093
|
+
# @!macro promises.param.args
|
1094
|
+
# @!macro promise.param.callback
|
1095
|
+
# @return [self]
|
1096
|
+
# @yield [value, *args] to the callback.
|
1097
|
+
def on_fulfillment_using(executor, *args, &callback)
|
1098
|
+
add_callback :async_callback_on_fulfillment, executor, args, callback
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
# @!macro promises.shortcut.using
|
1102
|
+
# @return [self]
|
1103
|
+
def on_rejection(*args, &callback)
|
1104
|
+
on_rejection_using @DefaultExecutor, *args, &callback
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
# Stores the callback to be executed synchronously on resolving thread after it is
|
1108
|
+
# rejected. Does nothing on fulfillment.
|
1109
|
+
#
|
1110
|
+
# @!macro promises.param.args
|
1111
|
+
# @!macro promise.param.callback
|
1112
|
+
# @return [self]
|
1113
|
+
# @yield [reason, *args] to the callback.
|
1114
|
+
def on_rejection!(*args, &callback)
|
1115
|
+
add_callback :callback_on_rejection, args, callback
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
# Stores the callback to be executed asynchronously on executor after it is
|
1119
|
+
# rejected. Does nothing on fulfillment.
|
1120
|
+
#
|
1121
|
+
# @!macro promises.param.executor
|
1122
|
+
# @!macro promises.param.args
|
1123
|
+
# @!macro promise.param.callback
|
1124
|
+
# @return [self]
|
1125
|
+
# @yield [reason, *args] to the callback.
|
1126
|
+
def on_rejection_using(executor, *args, &callback)
|
1127
|
+
add_callback :async_callback_on_rejection, executor, args, callback
|
1128
|
+
end
|
1129
|
+
|
1130
|
+
# Allows to use futures as green threads. The receiver has to evaluate to a future which
|
1131
|
+
# represents what should be done next. It basically flattens indefinitely until non Future
|
1132
|
+
# values is returned which becomes result of the returned future. Any encountered exception
|
1133
|
+
# will become reason of the returned future.
|
1134
|
+
#
|
1135
|
+
# @return [Future]
|
1136
|
+
# @example
|
1137
|
+
# body = lambda do |v|
|
1138
|
+
# v += 1
|
1139
|
+
# v < 5 ? Promises.future(v, &body) : v
|
1140
|
+
# end
|
1141
|
+
# Promises.future(0, &body).run.value! # => 5
|
1142
|
+
def run
|
1143
|
+
RunFuturePromise.new_blocked_by1(self, @DefaultExecutor).future
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
# @!visibility private
|
1147
|
+
def apply(args, block)
|
1148
|
+
internal_state.apply args, block
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
# Converts future to event which is resolved when future is resolved by fulfillment or rejection.
|
1152
|
+
#
|
1153
|
+
# @return [Event]
|
1154
|
+
def to_event
|
1155
|
+
event = Promises.resolvable_event
|
1156
|
+
ensure
|
1157
|
+
chain_resolvable(event)
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
# Returns self, since this is a future
|
1161
|
+
# @return [Future]
|
1162
|
+
def to_future
|
1163
|
+
self
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
private
|
1167
|
+
|
1168
|
+
def rejected_resolution(raise_on_reassign, state)
|
1169
|
+
if raise_on_reassign
|
1170
|
+
raise Concurrent::MultipleAssignmentError.new(
|
1171
|
+
"Future can be resolved only once. It's #{result}, trying to set #{state.result}.",
|
1172
|
+
current_result: result, new_result: state.result)
|
1173
|
+
end
|
1174
|
+
return false
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
def wait_until_resolved!(timeout = nil)
|
1178
|
+
result = wait_until_resolved(timeout)
|
1179
|
+
raise self if rejected?
|
1180
|
+
result
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
def async_callback_on_fulfillment(state, executor, args, callback)
|
1184
|
+
with_async(executor, state, args, callback) do |st, ar, cb|
|
1185
|
+
callback_on_fulfillment st, ar, cb
|
1186
|
+
end
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
def async_callback_on_rejection(state, executor, args, callback)
|
1190
|
+
with_async(executor, state, args, callback) do |st, ar, cb|
|
1191
|
+
callback_on_rejection st, ar, cb
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
def callback_on_fulfillment(state, args, callback)
|
1196
|
+
state.apply args, callback if state.fulfilled?
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
def callback_on_rejection(state, args, callback)
|
1200
|
+
state.apply args, callback unless state.fulfilled?
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
def callback_on_resolution(state, args, callback)
|
1204
|
+
callback.call(*state.result, *args)
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
# Marker module of Future, Event resolved manually by user.
|
1210
|
+
module Resolvable
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
# A Event which can be resolved by user.
|
1214
|
+
class ResolvableEvent < Event
|
1215
|
+
include Resolvable
|
1216
|
+
|
1217
|
+
|
1218
|
+
# @!macro raise_on_reassign
|
1219
|
+
# @raise [MultipleAssignmentError] when already resolved and raise_on_reassign is true.
|
1220
|
+
|
1221
|
+
# @!macro promise.param.raise_on_reassign
|
1222
|
+
# @param [Boolean] raise_on_reassign should method raise exception if already resolved
|
1223
|
+
# @return [self, false] false is returner when raise_on_reassign is false and the receiver
|
1224
|
+
# is already resolved.
|
1225
|
+
#
|
1226
|
+
|
1227
|
+
# Makes the event resolved, which triggers all dependent futures.
|
1228
|
+
#
|
1229
|
+
# @!macro promise.param.raise_on_reassign
|
1230
|
+
def resolve(raise_on_reassign = true)
|
1231
|
+
resolve_with RESOLVED, raise_on_reassign
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
# Creates new event wrapping receiver, effectively hiding the resolve method.
|
1235
|
+
#
|
1236
|
+
# @return [Event]
|
1237
|
+
def with_hidden_resolvable
|
1238
|
+
@with_hidden_resolvable ||= EventWrapperPromise.new_blocked_by1(self, @DefaultExecutor).event
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
# A Future which can be resolved by user.
|
1243
|
+
class ResolvableFuture < Future
|
1244
|
+
include Resolvable
|
1245
|
+
|
1246
|
+
# Makes the future resolved with result of triplet `fulfilled?`, `value`, `reason`,
|
1247
|
+
# which triggers all dependent futures.
|
1248
|
+
#
|
1249
|
+
# @!macro promise.param.raise_on_reassign
|
1250
|
+
def resolve(fulfilled = true, value = nil, reason = nil, raise_on_reassign = true)
|
1251
|
+
resolve_with(fulfilled ? Fulfilled.new(value) : Rejected.new(reason), raise_on_reassign)
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
# Makes the future fulfilled with `value`,
|
1255
|
+
# which triggers all dependent futures.
|
1256
|
+
#
|
1257
|
+
# @!macro promise.param.raise_on_reassign
|
1258
|
+
def fulfill(value, raise_on_reassign = true)
|
1259
|
+
promise.fulfill(value, raise_on_reassign)
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
# Makes the future rejected with `reason`,
|
1263
|
+
# which triggers all dependent futures.
|
1264
|
+
#
|
1265
|
+
# @!macro promise.param.raise_on_reassign
|
1266
|
+
def reject(reason, raise_on_reassign = true)
|
1267
|
+
promise.reject(reason, raise_on_reassign)
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
# Evaluates the block and sets its result as future's value fulfilling, if the block raises
|
1271
|
+
# an exception the future rejects with it.
|
1272
|
+
# @yield [*args] to the block.
|
1273
|
+
# @yieldreturn [Object] value
|
1274
|
+
# @return [self]
|
1275
|
+
def evaluate_to(*args, &block)
|
1276
|
+
promise.evaluate_to(*args, block)
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
# Evaluates the block and sets its result as future's value fulfilling, if the block raises
|
1280
|
+
# an exception the future rejects with it.
|
1281
|
+
# @yield [*args] to the block.
|
1282
|
+
# @yieldreturn [Object] value
|
1283
|
+
# @return [self]
|
1284
|
+
# @raise [Exception] also raise reason on rejection.
|
1285
|
+
def evaluate_to!(*args, &block)
|
1286
|
+
promise.evaluate_to(*args, block).wait!
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
# Creates new future wrapping receiver, effectively hiding the resolve method and similar.
|
1290
|
+
#
|
1291
|
+
# @return [Future]
|
1292
|
+
def with_hidden_resolvable
|
1293
|
+
@with_hidden_resolvable ||= FutureWrapperPromise.new_blocked_by1(self, @DefaultExecutor).future
|
1294
|
+
end
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
# @abstract
|
1298
|
+
# @private
|
1299
|
+
class AbstractPromise < Synchronization::Object
|
1300
|
+
safe_initialization!
|
1301
|
+
include InternalStates
|
1302
|
+
|
1303
|
+
def initialize(future)
|
1304
|
+
super()
|
1305
|
+
@Future = future
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
def future
|
1309
|
+
@Future
|
1310
|
+
end
|
1311
|
+
|
1312
|
+
alias_method :event, :future
|
1313
|
+
|
1314
|
+
def default_executor
|
1315
|
+
future.default_executor
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
def state
|
1319
|
+
future.state
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
def touch
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
def to_s
|
1326
|
+
format '%s %s>', super[0..-2], @Future
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
alias_method :inspect, :to_s
|
1330
|
+
|
1331
|
+
def delayed_because
|
1332
|
+
nil
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
private
|
1336
|
+
|
1337
|
+
def resolve_with(new_state, raise_on_reassign = true)
|
1338
|
+
@Future.resolve_with(new_state, raise_on_reassign)
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
# @return [Future]
|
1342
|
+
def evaluate_to(*args, block)
|
1343
|
+
resolve_with Fulfilled.new(block.call(*args))
|
1344
|
+
rescue Exception => error
|
1345
|
+
resolve_with Rejected.new(error)
|
1346
|
+
raise error unless error.is_a?(StandardError)
|
1347
|
+
end
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
class ResolvableEventPromise < AbstractPromise
|
1351
|
+
def initialize(default_executor)
|
1352
|
+
super ResolvableEvent.new(self, default_executor)
|
1353
|
+
end
|
1354
|
+
end
|
1355
|
+
|
1356
|
+
class ResolvableFuturePromise < AbstractPromise
|
1357
|
+
def initialize(default_executor)
|
1358
|
+
super ResolvableFuture.new(self, default_executor)
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
def fulfill(value, raise_on_reassign)
|
1362
|
+
resolve_with Fulfilled.new(value), raise_on_reassign
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
def reject(reason, raise_on_reassign)
|
1366
|
+
resolve_with Rejected.new(reason), raise_on_reassign
|
1367
|
+
end
|
1368
|
+
|
1369
|
+
public :evaluate_to
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
# @abstract
|
1373
|
+
class InnerPromise < AbstractPromise
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
# @abstract
|
1377
|
+
class BlockedPromise < InnerPromise
|
1378
|
+
|
1379
|
+
private_class_method :new
|
1380
|
+
|
1381
|
+
def self.new_blocked_by1(blocker, *args, &block)
|
1382
|
+
blocker_delayed = blocker.promise.delayed_because
|
1383
|
+
promise = new(blocker_delayed, 1, *args, &block)
|
1384
|
+
blocker.add_callback_notify_blocked promise, 0
|
1385
|
+
promise
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
def self.new_blocked_by2(blocker1, blocker2, *args, &block)
|
1389
|
+
blocker_delayed1 = blocker1.promise.delayed_because
|
1390
|
+
blocker_delayed2 = blocker2.promise.delayed_because
|
1391
|
+
delayed = if blocker_delayed1 && blocker_delayed2
|
1392
|
+
# TODO (pitr-ch 23-Dec-2016): use arrays when we know it will not grow (only flat adds delay)
|
1393
|
+
LockFreeStack.of2(blocker_delayed1, blocker_delayed2)
|
1394
|
+
else
|
1395
|
+
blocker_delayed1 || blocker_delayed2
|
1396
|
+
end
|
1397
|
+
promise = new(delayed, 2, *args, &block)
|
1398
|
+
blocker1.add_callback_notify_blocked promise, 0
|
1399
|
+
blocker2.add_callback_notify_blocked promise, 1
|
1400
|
+
promise
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
def self.new_blocked_by(blockers, *args, &block)
|
1404
|
+
delayed = blockers.reduce(nil) { |d, f| add_delayed d, f.promise.delayed_because }
|
1405
|
+
promise = new(delayed, blockers.size, *args, &block)
|
1406
|
+
blockers.each_with_index { |f, i| f.add_callback_notify_blocked promise, i }
|
1407
|
+
promise
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
def self.add_delayed(delayed1, delayed2)
|
1411
|
+
if delayed1 && delayed2
|
1412
|
+
delayed1.push delayed2
|
1413
|
+
delayed1
|
1414
|
+
else
|
1415
|
+
delayed1 || delayed2
|
1416
|
+
end
|
1417
|
+
end
|
1418
|
+
|
1419
|
+
def initialize(delayed, blockers_count, future)
|
1420
|
+
super(future)
|
1421
|
+
@Delayed = delayed
|
1422
|
+
@Countdown = AtomicFixnum.new blockers_count
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
def on_blocker_resolution(future, index)
|
1426
|
+
countdown = process_on_blocker_resolution(future, index)
|
1427
|
+
resolvable = resolvable?(countdown, future, index)
|
1428
|
+
|
1429
|
+
on_resolvable(future, index) if resolvable
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
def delayed_because
|
1433
|
+
@Delayed
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
def touch
|
1437
|
+
clear_and_propagate_touch
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
# for inspection only
|
1441
|
+
def blocked_by
|
1442
|
+
blocked_by = []
|
1443
|
+
ObjectSpace.each_object(AbstractEventFuture) { |o| blocked_by.push o if o.blocks.include? self }
|
1444
|
+
blocked_by
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
private
|
1448
|
+
|
1449
|
+
def clear_and_propagate_touch(stack_or_element = @Delayed)
|
1450
|
+
return if stack_or_element.nil?
|
1451
|
+
|
1452
|
+
if stack_or_element.is_a? LockFreeStack
|
1453
|
+
stack_or_element.clear_each { |element| clear_and_propagate_touch element }
|
1454
|
+
else
|
1455
|
+
stack_or_element.touch unless stack_or_element.nil? # if still present
|
1456
|
+
end
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
# @return [true,false] if resolvable
|
1460
|
+
def resolvable?(countdown, future, index)
|
1461
|
+
countdown.zero?
|
1462
|
+
end
|
1463
|
+
|
1464
|
+
def process_on_blocker_resolution(future, index)
|
1465
|
+
@Countdown.decrement
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
def on_resolvable(resolved_future, index)
|
1469
|
+
raise NotImplementedError
|
1470
|
+
end
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
# @abstract
|
1474
|
+
class BlockedTaskPromise < BlockedPromise
|
1475
|
+
def initialize(delayed, blockers_count, default_executor, executor, args, &task)
|
1476
|
+
raise ArgumentError, 'no block given' unless block_given?
|
1477
|
+
super delayed, 1, Future.new(self, default_executor)
|
1478
|
+
@Executor = executor
|
1479
|
+
@Task = task
|
1480
|
+
@Args = args
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
def executor
|
1484
|
+
@Executor
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
class ThenPromise < BlockedTaskPromise
|
1489
|
+
private
|
1490
|
+
|
1491
|
+
def initialize(delayed, blockers_count, default_executor, executor, args, &task)
|
1492
|
+
super delayed, blockers_count, default_executor, executor, args, &task
|
1493
|
+
end
|
1494
|
+
|
1495
|
+
def on_resolvable(resolved_future, index)
|
1496
|
+
if resolved_future.fulfilled?
|
1497
|
+
Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task|
|
1498
|
+
evaluate_to lambda { future.apply args, task }
|
1499
|
+
end
|
1500
|
+
else
|
1501
|
+
resolve_with resolved_future.internal_state
|
1502
|
+
end
|
1503
|
+
end
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
class RescuePromise < BlockedTaskPromise
|
1507
|
+
private
|
1508
|
+
|
1509
|
+
def initialize(delayed, blockers_count, default_executor, executor, args, &task)
|
1510
|
+
super delayed, blockers_count, default_executor, executor, args, &task
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
def on_resolvable(resolved_future, index)
|
1514
|
+
if resolved_future.rejected?
|
1515
|
+
Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task|
|
1516
|
+
evaluate_to lambda { future.apply args, task }
|
1517
|
+
end
|
1518
|
+
else
|
1519
|
+
resolve_with resolved_future.internal_state
|
1520
|
+
end
|
1521
|
+
end
|
1522
|
+
end
|
1523
|
+
|
1524
|
+
class ChainPromise < BlockedTaskPromise
|
1525
|
+
private
|
1526
|
+
|
1527
|
+
def on_resolvable(resolved_future, index)
|
1528
|
+
if Future === resolved_future
|
1529
|
+
Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task|
|
1530
|
+
evaluate_to(*future.result, *args, task)
|
1531
|
+
end
|
1532
|
+
else
|
1533
|
+
Concurrent.executor(@Executor).post(@Args, @Task) do |args, task|
|
1534
|
+
evaluate_to(*args, task)
|
1535
|
+
end
|
1536
|
+
end
|
1537
|
+
end
|
1538
|
+
end
|
1539
|
+
|
1540
|
+
# will be immediately resolved
|
1541
|
+
class ImmediateEventPromise < InnerPromise
|
1542
|
+
def initialize(default_executor)
|
1543
|
+
super Event.new(self, default_executor).resolve_with(RESOLVED)
|
1544
|
+
end
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
class ImmediateFuturePromise < InnerPromise
|
1548
|
+
def initialize(default_executor, fulfilled, value, reason)
|
1549
|
+
super Future.new(self, default_executor).
|
1550
|
+
resolve_with(fulfilled ? Fulfilled.new(value) : Rejected.new(reason))
|
1551
|
+
end
|
1552
|
+
end
|
1553
|
+
|
1554
|
+
class AbstractFlatPromise < BlockedPromise
|
1555
|
+
|
1556
|
+
def initialize(delayed_because, blockers_count, event_or_future)
|
1557
|
+
delayed = LockFreeStack.of1(self)
|
1558
|
+
super(delayed, blockers_count, event_or_future)
|
1559
|
+
# noinspection RubyArgCount
|
1560
|
+
@Touched = AtomicBoolean.new false
|
1561
|
+
@DelayedBecause = delayed_because || LockFreeStack.new
|
1562
|
+
|
1563
|
+
event_or_future.add_callback_clear_delayed_node delayed.peek
|
1564
|
+
end
|
1565
|
+
|
1566
|
+
def touch
|
1567
|
+
if @Touched.make_true
|
1568
|
+
clear_and_propagate_touch @DelayedBecause
|
1569
|
+
end
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
private
|
1573
|
+
|
1574
|
+
def touched?
|
1575
|
+
@Touched.value
|
1576
|
+
end
|
1577
|
+
|
1578
|
+
def on_resolvable(resolved_future, index)
|
1579
|
+
resolve_with resolved_future.internal_state
|
1580
|
+
end
|
1581
|
+
|
1582
|
+
def resolvable?(countdown, future, index)
|
1583
|
+
!@Future.internal_state.resolved? && super(countdown, future, index)
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
def add_delayed_of(future)
|
1587
|
+
delayed = future.promise.delayed_because
|
1588
|
+
if touched?
|
1589
|
+
clear_and_propagate_touch delayed
|
1590
|
+
else
|
1591
|
+
BlockedPromise.add_delayed @DelayedBecause, delayed
|
1592
|
+
clear_and_propagate_touch @DelayedBecause if touched?
|
1593
|
+
end
|
1594
|
+
end
|
1595
|
+
|
1596
|
+
end
|
1597
|
+
|
1598
|
+
class FlatEventPromise < AbstractFlatPromise
|
1599
|
+
|
1600
|
+
private
|
1601
|
+
|
1602
|
+
def initialize(delayed, blockers_count, default_executor)
|
1603
|
+
super delayed, 2, Event.new(self, default_executor)
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
def process_on_blocker_resolution(future, index)
|
1607
|
+
countdown = super(future, index)
|
1608
|
+
if countdown.nonzero?
|
1609
|
+
internal_state = future.internal_state
|
1610
|
+
|
1611
|
+
unless internal_state.fulfilled?
|
1612
|
+
resolve_with RESOLVED
|
1613
|
+
return countdown
|
1614
|
+
end
|
1615
|
+
|
1616
|
+
value = internal_state.value
|
1617
|
+
case value
|
1618
|
+
when Future, Event
|
1619
|
+
add_delayed_of value
|
1620
|
+
value.add_callback_notify_blocked self, nil
|
1621
|
+
countdown
|
1622
|
+
else
|
1623
|
+
resolve_with RESOLVED
|
1624
|
+
end
|
1625
|
+
end
|
1626
|
+
countdown
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
end
|
1630
|
+
|
1631
|
+
class FlatFuturePromise < AbstractFlatPromise
|
1632
|
+
|
1633
|
+
private
|
1634
|
+
|
1635
|
+
def initialize(delayed, blockers_count, levels, default_executor)
|
1636
|
+
raise ArgumentError, 'levels has to be higher than 0' if levels < 1
|
1637
|
+
# flat promise may result to a future having delayed futures, therefore we have to have empty stack
|
1638
|
+
# to be able to add new delayed futures
|
1639
|
+
super delayed || LockFreeStack.new, 1 + levels, Future.new(self, default_executor)
|
1640
|
+
end
|
1641
|
+
|
1642
|
+
def process_on_blocker_resolution(future, index)
|
1643
|
+
countdown = super(future, index)
|
1644
|
+
if countdown.nonzero?
|
1645
|
+
internal_state = future.internal_state
|
1646
|
+
|
1647
|
+
unless internal_state.fulfilled?
|
1648
|
+
resolve_with internal_state
|
1649
|
+
return countdown
|
1650
|
+
end
|
1651
|
+
|
1652
|
+
value = internal_state.value
|
1653
|
+
case value
|
1654
|
+
when Future
|
1655
|
+
add_delayed_of value
|
1656
|
+
value.add_callback_notify_blocked self, nil
|
1657
|
+
countdown
|
1658
|
+
when Event
|
1659
|
+
evaluate_to(lambda { raise TypeError, 'cannot flatten to Event' })
|
1660
|
+
else
|
1661
|
+
evaluate_to(lambda { raise TypeError, "returned value #{value.inspect} is not a Future" })
|
1662
|
+
end
|
1663
|
+
end
|
1664
|
+
countdown
|
1665
|
+
end
|
1666
|
+
|
1667
|
+
end
|
1668
|
+
|
1669
|
+
class RunFuturePromise < AbstractFlatPromise
|
1670
|
+
|
1671
|
+
private
|
1672
|
+
|
1673
|
+
def initialize(delayed, blockers_count, default_executor)
|
1674
|
+
super delayed, 1, Future.new(self, default_executor)
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
def process_on_blocker_resolution(future, index)
|
1678
|
+
internal_state = future.internal_state
|
1679
|
+
|
1680
|
+
unless internal_state.fulfilled?
|
1681
|
+
resolve_with internal_state
|
1682
|
+
return 0
|
1683
|
+
end
|
1684
|
+
|
1685
|
+
value = internal_state.value
|
1686
|
+
case value
|
1687
|
+
when Future
|
1688
|
+
add_delayed_of value
|
1689
|
+
value.add_callback_notify_blocked self, nil
|
1690
|
+
else
|
1691
|
+
resolve_with internal_state
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
1
|
1695
|
+
end
|
1696
|
+
end
|
1697
|
+
|
1698
|
+
class ZipEventEventPromise < BlockedPromise
|
1699
|
+
def initialize(delayed, blockers_count, default_executor)
|
1700
|
+
super delayed, 2, Event.new(self, default_executor)
|
1701
|
+
end
|
1702
|
+
|
1703
|
+
private
|
1704
|
+
|
1705
|
+
def on_resolvable(resolved_future, index)
|
1706
|
+
resolve_with RESOLVED
|
1707
|
+
end
|
1708
|
+
end
|
1709
|
+
|
1710
|
+
class ZipFutureEventPromise < BlockedPromise
|
1711
|
+
def initialize(delayed, blockers_count, default_executor)
|
1712
|
+
super delayed, 2, Future.new(self, default_executor)
|
1713
|
+
@result = nil
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
private
|
1717
|
+
|
1718
|
+
def process_on_blocker_resolution(future, index)
|
1719
|
+
# first blocking is future, take its result
|
1720
|
+
@result = future.internal_state if index == 0
|
1721
|
+
# super has to be called after above to piggyback on volatile @Countdown
|
1722
|
+
super future, index
|
1723
|
+
end
|
1724
|
+
|
1725
|
+
def on_resolvable(resolved_future, index)
|
1726
|
+
resolve_with @result
|
1727
|
+
end
|
1728
|
+
end
|
1729
|
+
|
1730
|
+
class EventWrapperPromise < BlockedPromise
|
1731
|
+
def initialize(delayed, blockers_count, default_executor)
|
1732
|
+
super delayed, 1, Event.new(self, default_executor)
|
1733
|
+
end
|
1734
|
+
|
1735
|
+
private
|
1736
|
+
|
1737
|
+
def on_resolvable(resolved_future, index)
|
1738
|
+
resolve_with RESOLVED
|
1739
|
+
end
|
1740
|
+
end
|
1741
|
+
|
1742
|
+
class FutureWrapperPromise < BlockedPromise
|
1743
|
+
def initialize(delayed, blockers_count, default_executor)
|
1744
|
+
super delayed, 1, Future.new(self, default_executor)
|
1745
|
+
end
|
1746
|
+
|
1747
|
+
private
|
1748
|
+
|
1749
|
+
def on_resolvable(resolved_future, index)
|
1750
|
+
resolve_with resolved_future.internal_state
|
1751
|
+
end
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
class ZipFuturesPromise < BlockedPromise
|
1755
|
+
|
1756
|
+
private
|
1757
|
+
|
1758
|
+
def initialize(delayed, blockers_count, default_executor)
|
1759
|
+
super(delayed, blockers_count, Future.new(self, default_executor))
|
1760
|
+
@Resolutions = ::Array.new(blockers_count, nil)
|
1761
|
+
|
1762
|
+
on_resolvable nil, nil if blockers_count == 0
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
def process_on_blocker_resolution(future, index)
|
1766
|
+
# TODO (pitr-ch 18-Dec-2016): Can we assume that array will never break under parallel access when never re-sized?
|
1767
|
+
@Resolutions[index] = future.internal_state # has to be set before countdown in super
|
1768
|
+
super future, index
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
def on_resolvable(resolved_future, index)
|
1772
|
+
all_fulfilled = true
|
1773
|
+
values = ::Array.new(@Resolutions.size)
|
1774
|
+
reasons = ::Array.new(@Resolutions.size)
|
1775
|
+
|
1776
|
+
@Resolutions.each_with_index do |internal_state, i|
|
1777
|
+
fulfilled, values[i], reasons[i] = internal_state.result
|
1778
|
+
all_fulfilled &&= fulfilled
|
1779
|
+
end
|
1780
|
+
|
1781
|
+
if all_fulfilled
|
1782
|
+
resolve_with FulfilledArray.new(values)
|
1783
|
+
else
|
1784
|
+
resolve_with PartiallyRejected.new(values, reasons)
|
1785
|
+
end
|
1786
|
+
end
|
1787
|
+
end
|
1788
|
+
|
1789
|
+
class ZipEventsPromise < BlockedPromise
|
1790
|
+
|
1791
|
+
private
|
1792
|
+
|
1793
|
+
def initialize(delayed, blockers_count, default_executor)
|
1794
|
+
super delayed, blockers_count, Event.new(self, default_executor)
|
1795
|
+
|
1796
|
+
on_resolvable nil, nil if blockers_count == 0
|
1797
|
+
end
|
1798
|
+
|
1799
|
+
def on_resolvable(resolved_future, index)
|
1800
|
+
resolve_with RESOLVED
|
1801
|
+
end
|
1802
|
+
end
|
1803
|
+
|
1804
|
+
# @abstract
|
1805
|
+
class AbstractAnyPromise < BlockedPromise
|
1806
|
+
end
|
1807
|
+
|
1808
|
+
class AnyResolvedFuturePromise < AbstractAnyPromise
|
1809
|
+
|
1810
|
+
private
|
1811
|
+
|
1812
|
+
def initialize(delayed, blockers_count, default_executor)
|
1813
|
+
super delayed, blockers_count, Future.new(self, default_executor)
|
1814
|
+
end
|
1815
|
+
|
1816
|
+
def resolvable?(countdown, future, index)
|
1817
|
+
true
|
1818
|
+
end
|
1819
|
+
|
1820
|
+
def on_resolvable(resolved_future, index)
|
1821
|
+
resolve_with resolved_future.internal_state, false
|
1822
|
+
end
|
1823
|
+
end
|
1824
|
+
|
1825
|
+
class AnyResolvedEventPromise < AbstractAnyPromise
|
1826
|
+
|
1827
|
+
private
|
1828
|
+
|
1829
|
+
def initialize(delayed, blockers_count, default_executor)
|
1830
|
+
super delayed, blockers_count, Event.new(self, default_executor)
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
def resolvable?(countdown, future, index)
|
1834
|
+
true
|
1835
|
+
end
|
1836
|
+
|
1837
|
+
def on_resolvable(resolved_future, index)
|
1838
|
+
resolve_with RESOLVED, false
|
1839
|
+
end
|
1840
|
+
end
|
1841
|
+
|
1842
|
+
class AnyFulfilledFuturePromise < AnyResolvedFuturePromise
|
1843
|
+
|
1844
|
+
private
|
1845
|
+
|
1846
|
+
def resolvable?(countdown, future, index)
|
1847
|
+
future.fulfilled? ||
|
1848
|
+
# inlined super from BlockedPromise
|
1849
|
+
countdown.zero?
|
1850
|
+
end
|
1851
|
+
end
|
1852
|
+
|
1853
|
+
class DelayPromise < InnerPromise
|
1854
|
+
|
1855
|
+
def initialize(default_executor)
|
1856
|
+
event = Event.new(self, default_executor)
|
1857
|
+
@Delayed = LockFreeStack.of1(self)
|
1858
|
+
super event
|
1859
|
+
event.add_callback_clear_delayed_node @Delayed.peek
|
1860
|
+
end
|
1861
|
+
|
1862
|
+
def touch
|
1863
|
+
@Future.resolve_with RESOLVED
|
1864
|
+
end
|
1865
|
+
|
1866
|
+
def delayed_because
|
1867
|
+
@Delayed
|
1868
|
+
end
|
1869
|
+
|
1870
|
+
end
|
1871
|
+
|
1872
|
+
class ScheduledPromise < InnerPromise
|
1873
|
+
def intended_time
|
1874
|
+
@IntendedTime
|
1875
|
+
end
|
1876
|
+
|
1877
|
+
def inspect
|
1878
|
+
"#{to_s[0..-2]} intended_time: #{@IntendedTime}>"
|
1879
|
+
end
|
1880
|
+
|
1881
|
+
private
|
1882
|
+
|
1883
|
+
def initialize(default_executor, intended_time)
|
1884
|
+
super Event.new(self, default_executor)
|
1885
|
+
|
1886
|
+
@IntendedTime = intended_time
|
1887
|
+
|
1888
|
+
in_seconds = begin
|
1889
|
+
now = Time.now
|
1890
|
+
schedule_time = if @IntendedTime.is_a? Time
|
1891
|
+
@IntendedTime
|
1892
|
+
else
|
1893
|
+
now + @IntendedTime
|
1894
|
+
end
|
1895
|
+
[0, schedule_time.to_f - now.to_f].max
|
1896
|
+
end
|
1897
|
+
|
1898
|
+
Concurrent.global_timer_set.post(in_seconds) do
|
1899
|
+
@Future.resolve_with RESOLVED
|
1900
|
+
end
|
1901
|
+
end
|
1902
|
+
end
|
1903
|
+
|
1904
|
+
extend FactoryMethods
|
1905
|
+
|
1906
|
+
private_constant :AbstractPromise,
|
1907
|
+
:ResolvableEventPromise,
|
1908
|
+
:ResolvableFuturePromise,
|
1909
|
+
:InnerPromise,
|
1910
|
+
:BlockedPromise,
|
1911
|
+
:BlockedTaskPromise,
|
1912
|
+
:ThenPromise,
|
1913
|
+
:RescuePromise,
|
1914
|
+
:ChainPromise,
|
1915
|
+
:ImmediateEventPromise,
|
1916
|
+
:ImmediateFuturePromise,
|
1917
|
+
:AbstractFlatPromise,
|
1918
|
+
:FlatFuturePromise,
|
1919
|
+
:FlatEventPromise,
|
1920
|
+
:RunFuturePromise,
|
1921
|
+
:ZipEventEventPromise,
|
1922
|
+
:ZipFutureEventPromise,
|
1923
|
+
:EventWrapperPromise,
|
1924
|
+
:FutureWrapperPromise,
|
1925
|
+
:ZipFuturesPromise,
|
1926
|
+
:ZipEventsPromise,
|
1927
|
+
:AbstractAnyPromise,
|
1928
|
+
:AnyResolvedFuturePromise,
|
1929
|
+
:AnyFulfilledFuturePromise,
|
1930
|
+
:AnyResolvedEventPromise,
|
1931
|
+
:DelayPromise,
|
1932
|
+
:ScheduledPromise
|
1933
|
+
|
1934
|
+
|
1935
|
+
end
|
1936
|
+
end
|