concurrent-ruby-edge 0.1.0.pre2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/lib/concurrent-edge.rb +11 -0
- data/lib/concurrent/actor.rb +98 -0
- data/lib/concurrent/actor/behaviour.rb +143 -0
- data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
- data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
- data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
- data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
- data/lib/concurrent/actor/behaviour/linking.rb +83 -0
- data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
- data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
- data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
- data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
- data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
- data/lib/concurrent/actor/behaviour/termination.rb +74 -0
- data/lib/concurrent/actor/context.rb +167 -0
- data/lib/concurrent/actor/core.rb +220 -0
- data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
- data/lib/concurrent/actor/envelope.rb +41 -0
- data/lib/concurrent/actor/errors.rb +27 -0
- data/lib/concurrent/actor/internal_delegations.rb +59 -0
- data/lib/concurrent/actor/public_delegations.rb +40 -0
- data/lib/concurrent/actor/reference.rb +106 -0
- data/lib/concurrent/actor/root.rb +37 -0
- data/lib/concurrent/actor/type_check.rb +48 -0
- data/lib/concurrent/actor/utils.rb +10 -0
- data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
- data/lib/concurrent/actor/utils/balancer.rb +43 -0
- data/lib/concurrent/actor/utils/broadcast.rb +52 -0
- data/lib/concurrent/actor/utils/pool.rb +54 -0
- data/lib/concurrent/agent.rb +289 -0
- data/lib/concurrent/channel.rb +6 -0
- data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
- data/lib/concurrent/channel/buffered_channel.rb +87 -0
- data/lib/concurrent/channel/channel.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +65 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
- data/lib/concurrent/channel/waitable_list.rb +48 -0
- data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
- data/lib/concurrent/edge/future.rb +1226 -0
- data/lib/concurrent/edge/lock_free_stack.rb +85 -0
- metadata +110 -0
@@ -0,0 +1,1226 @@
|
|
1
|
+
require 'concurrent' # TODO do not require whole concurrent gem
|
2
|
+
require 'concurrent/edge/lock_free_stack'
|
3
|
+
|
4
|
+
|
5
|
+
# @note different name just not to collide for now
|
6
|
+
module Concurrent
|
7
|
+
module Edge
|
8
|
+
|
9
|
+
# Provides edge features, which will be added to or replace features in main gem.
|
10
|
+
#
|
11
|
+
# Contains new unified implementation of Futures and Promises which combines Features of previous `Future`,
|
12
|
+
# `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
|
13
|
+
# new synchronization layer to make all the paths lock-free with exception of blocking threads on `#wait`.
|
14
|
+
# It offers better performance and does not block threads (exception being #wait and similar methods where it's
|
15
|
+
# intended).
|
16
|
+
#
|
17
|
+
# ## Examples
|
18
|
+
# {include:file:examples/edge_futures.out.rb}
|
19
|
+
#
|
20
|
+
# @!macro edge_warning
|
21
|
+
module FutureShortcuts
|
22
|
+
# User is responsible for completing the event once by {Edge::CompletableEvent#complete}
|
23
|
+
# @return [CompletableEvent]
|
24
|
+
def event(default_executor = :io)
|
25
|
+
CompletableEventPromise.new(default_executor).future
|
26
|
+
end
|
27
|
+
|
28
|
+
# @overload future(default_executor = :io, &task)
|
29
|
+
# Constructs new Future which will be completed after block is evaluated on executor. Evaluation begins immediately.
|
30
|
+
# @return [Future]
|
31
|
+
# @overload future(default_executor = :io)
|
32
|
+
# User is responsible for completing the future once by {Edge::CompletableFuture#success} or {Edge::CompletableFuture#fail}
|
33
|
+
# @return [CompletableFuture]
|
34
|
+
def future(*args, &task)
|
35
|
+
future_on :io, *args, &task
|
36
|
+
end
|
37
|
+
|
38
|
+
def future_on(default_executor, *args, &task)
|
39
|
+
if task
|
40
|
+
ImmediatePromise.new(default_executor, *args).future.then(&task)
|
41
|
+
else
|
42
|
+
CompletableFuturePromise.new(default_executor).future
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :async, :future
|
47
|
+
|
48
|
+
# Constructs new Future which will evaluate to the block after
|
49
|
+
# requested by calling `#wait`, `#value`, `#value!`, etc. on it or on any of the chained futures.
|
50
|
+
# @return [Future]
|
51
|
+
def delay(*args, &task)
|
52
|
+
delay_on :io, *args, &task
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Future]
|
56
|
+
def delay_on(default_executor, *args, &task)
|
57
|
+
Delay.new(default_executor, *args).future.then(&task)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Schedules the block to be executed on executor in given intended_time.
|
61
|
+
# @param [Numeric, Time] intended_time Numeric => run in `intended_time` seconds. Time => eun on time.
|
62
|
+
# @return [Future]
|
63
|
+
def schedule(intended_time, *args, &task)
|
64
|
+
schedule_on :io, intended_time, *args, &task
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Future]
|
68
|
+
def schedule_on(default_executor, intended_time, *args, &task)
|
69
|
+
ScheduledPromise.new(default_executor, intended_time, *args).future.then(&task)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Constructs new {Future} which is completed after all futures are complete. Its value is array
|
73
|
+
# of dependent future values. If there is an error it fails with the first one.
|
74
|
+
# @param [Event] futures
|
75
|
+
# @return [Future]
|
76
|
+
def zip(*futures)
|
77
|
+
AllPromise.new(futures, :io).future
|
78
|
+
end
|
79
|
+
|
80
|
+
# Constructs new {Future} which is completed after first of the futures is complete.
|
81
|
+
# @param [Event] futures
|
82
|
+
# @return [Future]
|
83
|
+
def any(*futures)
|
84
|
+
AnyPromise.new(futures, :io).future
|
85
|
+
end
|
86
|
+
|
87
|
+
# only proof of concept
|
88
|
+
# @return [Future]
|
89
|
+
def select(*channels)
|
90
|
+
probe = future
|
91
|
+
channels.each { |ch| ch.select probe }
|
92
|
+
probe
|
93
|
+
end
|
94
|
+
|
95
|
+
# post job on :fast executor
|
96
|
+
# @return [true, false]
|
97
|
+
def post!(*args, &job)
|
98
|
+
post_on(:fast, *args, &job)
|
99
|
+
end
|
100
|
+
|
101
|
+
# post job on :io executor
|
102
|
+
# @return [true, false]
|
103
|
+
def post(*args, &job)
|
104
|
+
post_on(:io, *args, &job)
|
105
|
+
end
|
106
|
+
|
107
|
+
# post job on executor
|
108
|
+
# @return [true, false]
|
109
|
+
def post_on(executor, *args, &job)
|
110
|
+
Concurrent.executor(executor).post *args, &job
|
111
|
+
end
|
112
|
+
|
113
|
+
# TODO add first(futures, count=count)
|
114
|
+
# TODO allow to to have a zip point for many futures and process them in batches by 10
|
115
|
+
end
|
116
|
+
|
117
|
+
extend FutureShortcuts
|
118
|
+
include FutureShortcuts
|
119
|
+
|
120
|
+
# Represents an event which will happen in future (will be completed). It has to always happen.
|
121
|
+
class Event < Synchronization::Object
|
122
|
+
include Concern::Deprecation
|
123
|
+
|
124
|
+
class State
|
125
|
+
def completed?
|
126
|
+
raise NotImplementedError
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_sym
|
130
|
+
raise NotImplementedError
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class Pending < State
|
135
|
+
def completed?
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_sym
|
140
|
+
:pending
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class Completed < State
|
145
|
+
def completed?
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_sym
|
150
|
+
:completed
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
PENDING = Pending.new
|
155
|
+
COMPLETED = Completed.new
|
156
|
+
|
157
|
+
def initialize(promise, default_executor)
|
158
|
+
@Promise = promise
|
159
|
+
@DefaultExecutor = default_executor
|
160
|
+
@Touched = AtomicBoolean.new(false)
|
161
|
+
@Callbacks = LockFreeStack.new
|
162
|
+
@Waiters = LockFreeStack.new
|
163
|
+
@State = AtomicReference.new PENDING
|
164
|
+
super()
|
165
|
+
ensure_ivar_visibility!
|
166
|
+
end
|
167
|
+
|
168
|
+
# @return [:pending, :completed]
|
169
|
+
def state
|
170
|
+
@State.get.to_sym
|
171
|
+
end
|
172
|
+
|
173
|
+
# Is Event/Future pending?
|
174
|
+
# @return [Boolean]
|
175
|
+
def pending?(state = @State.get)
|
176
|
+
!state.completed?
|
177
|
+
end
|
178
|
+
|
179
|
+
def unscheduled?
|
180
|
+
raise 'unsupported'
|
181
|
+
end
|
182
|
+
|
183
|
+
alias_method :incomplete?, :pending?
|
184
|
+
|
185
|
+
# Has the Event been completed?
|
186
|
+
# @return [Boolean]
|
187
|
+
def completed?(state = @State.get)
|
188
|
+
state.completed?
|
189
|
+
end
|
190
|
+
|
191
|
+
alias_method :complete?, :completed?
|
192
|
+
|
193
|
+
# Wait until Event is #complete?
|
194
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
195
|
+
# @return [Event] self
|
196
|
+
def wait(timeout = nil)
|
197
|
+
touch
|
198
|
+
wait_until_complete timeout
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
# @!visibility private
|
203
|
+
def touch
|
204
|
+
# distribute touch to promise only once
|
205
|
+
@Promise.touch if @Touched.make_true
|
206
|
+
self
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return [Executor] current default executor
|
210
|
+
# @see #with_default_executor
|
211
|
+
def default_executor
|
212
|
+
@DefaultExecutor
|
213
|
+
end
|
214
|
+
|
215
|
+
# @yield [success, value, reason] of the parent
|
216
|
+
def chain(executor = nil, &callback)
|
217
|
+
ChainPromise.new(self, @DefaultExecutor, executor || @DefaultExecutor, &callback).future
|
218
|
+
end
|
219
|
+
|
220
|
+
alias_method :then, :chain
|
221
|
+
|
222
|
+
# TODO take block optionally
|
223
|
+
# Zip with futures producing new Future
|
224
|
+
# @return [Future]
|
225
|
+
def zip(*futures)
|
226
|
+
AllPromise.new([self, *futures], @DefaultExecutor).future
|
227
|
+
end
|
228
|
+
|
229
|
+
alias_method :&, :zip
|
230
|
+
|
231
|
+
# Inserts delay into the chain of Futures making rest of it lazy evaluated.
|
232
|
+
# @return [Future]
|
233
|
+
def delay
|
234
|
+
zip(Delay.new(@DefaultExecutor).future)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Schedules rest of the chain for execution with specified time or on specified time
|
238
|
+
# @return [Future]
|
239
|
+
def schedule(intended_time)
|
240
|
+
chain { ScheduledPromise.new(@DefaultExecutor, intended_time).event.zip(self) }.flat
|
241
|
+
end
|
242
|
+
|
243
|
+
# Zips with selected value form the suplied channels
|
244
|
+
# @return [Future]
|
245
|
+
def then_select(*channels)
|
246
|
+
self.zip(Concurrent.select(*channels))
|
247
|
+
end
|
248
|
+
|
249
|
+
# @yield [success, value, reason] executed async on `executor` when completed
|
250
|
+
# @return self
|
251
|
+
def on_completion(executor = nil, &callback)
|
252
|
+
add_callback :pr_async_callback_on_completion, executor || @DefaultExecutor, callback
|
253
|
+
end
|
254
|
+
|
255
|
+
# @yield [success, value, reason] executed sync when completed
|
256
|
+
# @return self
|
257
|
+
def on_completion!(&callback)
|
258
|
+
add_callback :pr_callback_on_completion, callback
|
259
|
+
end
|
260
|
+
|
261
|
+
# Changes default executor for rest of the chain
|
262
|
+
# @return [Future]
|
263
|
+
def with_default_executor(executor)
|
264
|
+
AllPromise.new([self], executor).future
|
265
|
+
end
|
266
|
+
|
267
|
+
def to_s
|
268
|
+
"<##{self.class}:0x#{'%x' % (object_id << 1)} #{state.to_sym}>"
|
269
|
+
end
|
270
|
+
|
271
|
+
def inspect
|
272
|
+
"#{to_s[0..-2]} blocks:[#{blocks.map(&:to_s).join(', ')}]>"
|
273
|
+
end
|
274
|
+
|
275
|
+
def set(*args, &block)
|
276
|
+
raise 'Use CompletableEvent#complete or CompletableFuture#complete instead, ' +
|
277
|
+
'constructed by Concurrent.event or Concurrent.future respectively.'
|
278
|
+
end
|
279
|
+
|
280
|
+
# @!visibility private
|
281
|
+
def complete(raise_on_reassign = true)
|
282
|
+
if complete_state
|
283
|
+
# go to synchronized block only if there were waiting threads
|
284
|
+
synchronize { ns_broadcast } if @Waiters.clear
|
285
|
+
call_callbacks
|
286
|
+
else
|
287
|
+
Concurrent::MultipleAssignmentError.new('multiple assignment') if raise_on_reassign
|
288
|
+
return false
|
289
|
+
end
|
290
|
+
self
|
291
|
+
end
|
292
|
+
|
293
|
+
# @!visibility private
|
294
|
+
# just for inspection
|
295
|
+
# @return [Array<AbstractPromise>]
|
296
|
+
def blocks
|
297
|
+
@Callbacks.each_with_object([]) do |callback, promises|
|
298
|
+
promises.push *callback.select { |v| v.is_a? AbstractPromise }
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# @!visibility private
|
303
|
+
# just for inspection
|
304
|
+
def callbacks
|
305
|
+
@Callbacks.each.to_a
|
306
|
+
end
|
307
|
+
|
308
|
+
# @!visibility private
|
309
|
+
def add_callback(method, *args)
|
310
|
+
if completed?
|
311
|
+
call_callback method, *args
|
312
|
+
else
|
313
|
+
@Callbacks.push [method, *args]
|
314
|
+
call_callbacks if completed?
|
315
|
+
end
|
316
|
+
self
|
317
|
+
end
|
318
|
+
|
319
|
+
# @!visibility private
|
320
|
+
# only for inspection
|
321
|
+
def promise
|
322
|
+
@Promise
|
323
|
+
end
|
324
|
+
|
325
|
+
# @!visibility private
|
326
|
+
# only for inspection
|
327
|
+
def touched
|
328
|
+
@Touched.value
|
329
|
+
end
|
330
|
+
|
331
|
+
# only for debugging inspection
|
332
|
+
def waiting_threads
|
333
|
+
@Waiters.each.to_a
|
334
|
+
end
|
335
|
+
|
336
|
+
private
|
337
|
+
|
338
|
+
def wait_until_complete(timeout)
|
339
|
+
while true
|
340
|
+
last_waiter = @Waiters.peek # waiters' state before completion
|
341
|
+
break if completed?
|
342
|
+
|
343
|
+
# synchronize so it cannot be signaled before it waits
|
344
|
+
synchronize do
|
345
|
+
# ok only if completing thread did not start signaling
|
346
|
+
next unless @Waiters.compare_and_push last_waiter, Thread.current
|
347
|
+
ns_wait_until(timeout) { completed? }
|
348
|
+
break
|
349
|
+
end
|
350
|
+
end
|
351
|
+
self
|
352
|
+
end
|
353
|
+
|
354
|
+
def complete_state
|
355
|
+
COMPLETED if @State.compare_and_set(PENDING, COMPLETED)
|
356
|
+
end
|
357
|
+
|
358
|
+
def pr_with_async(executor, *args, &block)
|
359
|
+
Concurrent.post_on(executor, *args, &block)
|
360
|
+
end
|
361
|
+
|
362
|
+
def pr_async_callback_on_completion(executor, callback)
|
363
|
+
pr_with_async(executor) { pr_callback_on_completion callback }
|
364
|
+
end
|
365
|
+
|
366
|
+
def pr_callback_on_completion(callback)
|
367
|
+
callback.call
|
368
|
+
end
|
369
|
+
|
370
|
+
def pr_callback_notify_blocked(promise)
|
371
|
+
promise.on_done self
|
372
|
+
end
|
373
|
+
|
374
|
+
def call_callback(method, *args)
|
375
|
+
self.send method, *args
|
376
|
+
end
|
377
|
+
|
378
|
+
def call_callbacks
|
379
|
+
method, *args = @Callbacks.pop
|
380
|
+
while method
|
381
|
+
call_callback method, *args
|
382
|
+
method, *args = @Callbacks.pop
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Represents a value which will become available in future. May fail with a reason instead.
|
388
|
+
class Future < Event
|
389
|
+
class CompletedWithResult < Completed
|
390
|
+
def result
|
391
|
+
[success?, value, reason]
|
392
|
+
end
|
393
|
+
|
394
|
+
def success?
|
395
|
+
raise NotImplementedError
|
396
|
+
end
|
397
|
+
|
398
|
+
def value
|
399
|
+
raise NotImplementedError
|
400
|
+
end
|
401
|
+
|
402
|
+
def reason
|
403
|
+
raise NotImplementedError
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class Success < CompletedWithResult
|
408
|
+
def initialize(value)
|
409
|
+
@Value = value
|
410
|
+
end
|
411
|
+
|
412
|
+
def success?
|
413
|
+
true
|
414
|
+
end
|
415
|
+
|
416
|
+
def apply(block)
|
417
|
+
block.call value
|
418
|
+
end
|
419
|
+
|
420
|
+
def value
|
421
|
+
@Value
|
422
|
+
end
|
423
|
+
|
424
|
+
def reason
|
425
|
+
nil
|
426
|
+
end
|
427
|
+
|
428
|
+
def to_sym
|
429
|
+
:success
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
class SuccessArray < Success
|
434
|
+
def apply(block)
|
435
|
+
block.call *value
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
class Failed < CompletedWithResult
|
440
|
+
def initialize(reason)
|
441
|
+
@Reason = reason
|
442
|
+
end
|
443
|
+
|
444
|
+
def success?
|
445
|
+
false
|
446
|
+
end
|
447
|
+
|
448
|
+
def value
|
449
|
+
nil
|
450
|
+
end
|
451
|
+
|
452
|
+
def reason
|
453
|
+
@Reason
|
454
|
+
end
|
455
|
+
|
456
|
+
def to_sym
|
457
|
+
:failed
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# @!method state
|
462
|
+
# @return [:pending, :success, :failed]
|
463
|
+
|
464
|
+
# Has Future been success?
|
465
|
+
# @return [Boolean]
|
466
|
+
def success?(state = @State.get)
|
467
|
+
state.success?
|
468
|
+
end
|
469
|
+
|
470
|
+
def fulfilled?
|
471
|
+
deprecated_method 'fulfilled?', 'success?'
|
472
|
+
success?
|
473
|
+
end
|
474
|
+
|
475
|
+
# Has Future been failed?
|
476
|
+
# @return [Boolean]
|
477
|
+
def failed?(state = @State.get)
|
478
|
+
!success?(state)
|
479
|
+
end
|
480
|
+
|
481
|
+
def rejected?
|
482
|
+
deprecated_method 'rejected?', 'failed?'
|
483
|
+
failed?
|
484
|
+
end
|
485
|
+
|
486
|
+
# @return [Object] the value of the Future when success
|
487
|
+
def value(timeout = nil)
|
488
|
+
touch
|
489
|
+
wait_until_complete timeout
|
490
|
+
@State.get.value
|
491
|
+
end
|
492
|
+
|
493
|
+
# @return [Exception] the reason of the Future's failure
|
494
|
+
def reason(timeout = nil)
|
495
|
+
touch
|
496
|
+
wait_until_complete timeout
|
497
|
+
@State.get.reason
|
498
|
+
end
|
499
|
+
|
500
|
+
# @return [Array(Boolean, Object, Exception)] triplet of success, value, reason
|
501
|
+
def result(timeout = nil)
|
502
|
+
touch
|
503
|
+
wait_until_complete timeout
|
504
|
+
@State.get.result
|
505
|
+
end
|
506
|
+
|
507
|
+
# Wait until Future is #complete?
|
508
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
509
|
+
# @raise reason on failure
|
510
|
+
# @return [Event] self
|
511
|
+
def wait!(timeout = nil)
|
512
|
+
touch
|
513
|
+
wait_until_complete! timeout
|
514
|
+
end
|
515
|
+
|
516
|
+
# Wait until Future is #complete?
|
517
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
518
|
+
# @raise reason on failure
|
519
|
+
# @return [Object]
|
520
|
+
def value!(timeout = nil)
|
521
|
+
touch
|
522
|
+
wait_until_complete!(timeout)
|
523
|
+
@State.get.value
|
524
|
+
end
|
525
|
+
|
526
|
+
# @example allows failed Future to be risen
|
527
|
+
# raise Concurrent.future.fail
|
528
|
+
def exception(*args)
|
529
|
+
touch
|
530
|
+
raise 'obligation is not failed' unless failed?
|
531
|
+
@State.get.reason.exception(*args)
|
532
|
+
end
|
533
|
+
|
534
|
+
# @yield [value] executed only on parent success
|
535
|
+
# @return [Future]
|
536
|
+
def then(executor = nil, &callback)
|
537
|
+
ThenPromise.new(self, @DefaultExecutor, executor || @DefaultExecutor, &callback).future
|
538
|
+
end
|
539
|
+
|
540
|
+
# Asks the actor with its value.
|
541
|
+
# @return [Future] new future with the response form the actor
|
542
|
+
def then_ask(actor)
|
543
|
+
self.then { |v| actor.ask(v) }.flat
|
544
|
+
end
|
545
|
+
|
546
|
+
# @yield [reason] executed only on parent failure
|
547
|
+
# @return [Future]
|
548
|
+
def rescue(executor = nil, &callback)
|
549
|
+
RescuePromise.new(self, @DefaultExecutor, executor || @DefaultExecutor, &callback).future
|
550
|
+
end
|
551
|
+
|
552
|
+
# zips with the Future in the value
|
553
|
+
# @example
|
554
|
+
# Concurrent.future { Concurrent.future { 1 } }.flat.vale # => 1
|
555
|
+
def flat(level = 1)
|
556
|
+
FlattingPromise.new(self, level, @DefaultExecutor).future
|
557
|
+
end
|
558
|
+
|
559
|
+
# @return [Future] which has first completed value from futures
|
560
|
+
def any(*futures)
|
561
|
+
AnyPromise.new([self, *futures], @DefaultExecutor).future
|
562
|
+
end
|
563
|
+
|
564
|
+
alias_method :|, :any
|
565
|
+
|
566
|
+
# only proof of concept
|
567
|
+
def then_push(channel)
|
568
|
+
on_success { |value| channel.push value } # FIXME it's blocking for now
|
569
|
+
end
|
570
|
+
|
571
|
+
# @yield [value] executed async on `executor` when success
|
572
|
+
# @return self
|
573
|
+
def on_success(executor = nil, &callback)
|
574
|
+
add_callback :pr_async_callback_on_success, executor || @DefaultExecutor, callback
|
575
|
+
end
|
576
|
+
|
577
|
+
# @yield [reason] executed async on `executor` when failed?
|
578
|
+
# @return self
|
579
|
+
def on_failure(executor = nil, &callback)
|
580
|
+
add_callback :pr_async_callback_on_failure, executor || @DefaultExecutor, callback
|
581
|
+
end
|
582
|
+
|
583
|
+
# @yield [value] executed sync when success
|
584
|
+
# @return self
|
585
|
+
def on_success!(&callback)
|
586
|
+
add_callback :pr_callback_on_success, callback
|
587
|
+
end
|
588
|
+
|
589
|
+
# @yield [reason] executed sync when failed?
|
590
|
+
# @return self
|
591
|
+
def on_failure!(&callback)
|
592
|
+
add_callback :pr_callback_on_failure, callback
|
593
|
+
end
|
594
|
+
|
595
|
+
# @!visibility private
|
596
|
+
def complete(success, value, reason, raise_on_reassign = true)
|
597
|
+
if (new_state = complete_state success, value, reason)
|
598
|
+
@Waiters.clear
|
599
|
+
synchronize { ns_broadcast }
|
600
|
+
call_callbacks new_state
|
601
|
+
else
|
602
|
+
raise reason || Concurrent::MultipleAssignmentError.new('multiple assignment') if raise_on_reassign
|
603
|
+
return false
|
604
|
+
end
|
605
|
+
self
|
606
|
+
end
|
607
|
+
|
608
|
+
# @!visibility private
|
609
|
+
def add_callback(method, *args)
|
610
|
+
state = @State.get
|
611
|
+
if completed?(state)
|
612
|
+
call_callback method, state, *args
|
613
|
+
else
|
614
|
+
@Callbacks.push [method, *args]
|
615
|
+
state = @State.get
|
616
|
+
# take back if it was completed in the meanwhile
|
617
|
+
call_callbacks state if completed?(state)
|
618
|
+
end
|
619
|
+
self
|
620
|
+
end
|
621
|
+
|
622
|
+
# @!visibility private
|
623
|
+
def apply(block)
|
624
|
+
@State.get.apply block
|
625
|
+
end
|
626
|
+
|
627
|
+
private
|
628
|
+
|
629
|
+
def wait_until_complete!(timeout = nil)
|
630
|
+
wait_until_complete(timeout)
|
631
|
+
raise self if failed?
|
632
|
+
self
|
633
|
+
end
|
634
|
+
|
635
|
+
def complete_state(success, value, reason)
|
636
|
+
new_state = if success
|
637
|
+
if value.is_a?(Array)
|
638
|
+
SuccessArray.new(value)
|
639
|
+
else
|
640
|
+
Success.new(value)
|
641
|
+
end
|
642
|
+
else
|
643
|
+
Failed.new(reason)
|
644
|
+
end
|
645
|
+
new_state if @State.compare_and_set(PENDING, new_state)
|
646
|
+
end
|
647
|
+
|
648
|
+
def call_callbacks(state)
|
649
|
+
method, *args = @Callbacks.pop
|
650
|
+
while method
|
651
|
+
call_callback method, state, *args
|
652
|
+
method, *args = @Callbacks.pop
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
def call_callback(method, state, *args)
|
657
|
+
self.send method, state, *args
|
658
|
+
end
|
659
|
+
|
660
|
+
def pr_async_callback_on_success(state, executor, callback)
|
661
|
+
pr_with_async(executor, state, callback) do |state, callback|
|
662
|
+
pr_callback_on_success state, callback
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
def pr_async_callback_on_failure(state, executor, callback)
|
667
|
+
pr_with_async(executor, state, callback) do |state, callback|
|
668
|
+
pr_callback_on_failure state, callback
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
def pr_callback_on_success(state, callback)
|
673
|
+
state.apply callback if state.success?
|
674
|
+
end
|
675
|
+
|
676
|
+
def pr_callback_on_failure(state, callback)
|
677
|
+
state.apply callback unless state.success?
|
678
|
+
end
|
679
|
+
|
680
|
+
def pr_callback_on_completion(state, callback)
|
681
|
+
callback.call state.result
|
682
|
+
end
|
683
|
+
|
684
|
+
def pr_callback_notify_blocked(state, promise)
|
685
|
+
super(promise)
|
686
|
+
end
|
687
|
+
|
688
|
+
def pr_async_callback_on_completion(state, executor, callback)
|
689
|
+
pr_with_async(executor, state, callback) do |state, callback|
|
690
|
+
pr_callback_on_completion state, callback
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
end
|
695
|
+
|
696
|
+
# A Event which can be completed by user.
|
697
|
+
class CompletableEvent < Event
|
698
|
+
# Complete the Event, `raise` if already completed
|
699
|
+
def complete(raise_on_reassign = true)
|
700
|
+
super raise_on_reassign
|
701
|
+
end
|
702
|
+
|
703
|
+
def hide_completable
|
704
|
+
Concurrent.zip(self)
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
# A Future which can be completed by user.
|
709
|
+
class CompletableFuture < Future
|
710
|
+
# Complete the future with triplet od `success`, `value`, `reason`
|
711
|
+
# `raise` if already completed
|
712
|
+
# return [self]
|
713
|
+
def complete(success, value, reason, raise_on_reassign = true)
|
714
|
+
super success, value, reason, raise_on_reassign
|
715
|
+
end
|
716
|
+
|
717
|
+
# Complete the future with value
|
718
|
+
# return [self]
|
719
|
+
def success(value)
|
720
|
+
promise.success(value)
|
721
|
+
end
|
722
|
+
|
723
|
+
# Try to complete the future with value
|
724
|
+
# return [self]
|
725
|
+
def try_success(value)
|
726
|
+
promise.try_success(value)
|
727
|
+
end
|
728
|
+
|
729
|
+
# Fail the future with reason
|
730
|
+
# return [self]
|
731
|
+
def fail(reason = StandardError.new)
|
732
|
+
promise.fail(reason)
|
733
|
+
end
|
734
|
+
|
735
|
+
# Try to fail the future with reason
|
736
|
+
# return [self]
|
737
|
+
def try_fail(reason = StandardError.new)
|
738
|
+
promise.try_fail(reason)
|
739
|
+
end
|
740
|
+
|
741
|
+
# Evaluate the future to value if there is an exception the future fails with it
|
742
|
+
# return [self]
|
743
|
+
def evaluate_to(*args, &block)
|
744
|
+
promise.evaluate_to(*args, block)
|
745
|
+
end
|
746
|
+
|
747
|
+
# Evaluate the future to value if there is an exception the future fails with it
|
748
|
+
# @raise the exception
|
749
|
+
# return [self]
|
750
|
+
def evaluate_to!(*args, &block)
|
751
|
+
promise.evaluate_to!(*args, block)
|
752
|
+
end
|
753
|
+
|
754
|
+
def hide_completable
|
755
|
+
Concurrent.zip(self)
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
# TODO modularize blocked_by and notify blocked
|
760
|
+
|
761
|
+
# @abstract
|
762
|
+
# @!visibility private
|
763
|
+
class AbstractPromise < Synchronization::Object
|
764
|
+
def initialize(future)
|
765
|
+
@Future = future
|
766
|
+
ensure_ivar_visibility!
|
767
|
+
end
|
768
|
+
|
769
|
+
def future
|
770
|
+
@Future
|
771
|
+
end
|
772
|
+
|
773
|
+
alias_method :event, :future
|
774
|
+
|
775
|
+
def default_executor
|
776
|
+
future.default_executor
|
777
|
+
end
|
778
|
+
|
779
|
+
def state
|
780
|
+
future.state
|
781
|
+
end
|
782
|
+
|
783
|
+
def touch
|
784
|
+
end
|
785
|
+
|
786
|
+
def to_s
|
787
|
+
"<##{self.class}:0x#{'%x' % (object_id << 1)} #{state}>"
|
788
|
+
end
|
789
|
+
|
790
|
+
def inspect
|
791
|
+
to_s
|
792
|
+
end
|
793
|
+
|
794
|
+
private
|
795
|
+
|
796
|
+
def complete(*args)
|
797
|
+
@Future.complete(*args)
|
798
|
+
end
|
799
|
+
|
800
|
+
# @return [Future]
|
801
|
+
def evaluate_to(*args, block)
|
802
|
+
complete true, block.call(*args), nil
|
803
|
+
rescue => error
|
804
|
+
complete false, nil, error
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
# @!visibility private
|
809
|
+
class CompletableEventPromise < AbstractPromise
|
810
|
+
public :complete
|
811
|
+
|
812
|
+
def initialize(default_executor)
|
813
|
+
super CompletableEvent.new(self, default_executor)
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
# @!visibility private
|
818
|
+
class CompletableFuturePromise < AbstractPromise
|
819
|
+
# TODO consider to allow being blocked_by
|
820
|
+
|
821
|
+
def initialize(default_executor)
|
822
|
+
super CompletableFuture.new(self, default_executor)
|
823
|
+
end
|
824
|
+
|
825
|
+
# Set the `Future` to a value and wake or notify all threads waiting on it.
|
826
|
+
#
|
827
|
+
# @param [Object] value the value to store in the `Future`
|
828
|
+
# @raise [Concurrent::MultipleAssignmentError] if the `Future` has already been set or otherwise completed
|
829
|
+
# @return [Future]
|
830
|
+
def success(value)
|
831
|
+
complete(true, value, nil)
|
832
|
+
end
|
833
|
+
|
834
|
+
def try_success(value)
|
835
|
+
complete(true, value, nil, false)
|
836
|
+
end
|
837
|
+
|
838
|
+
# Set the `Future` to failed due to some error and wake or notify all threads waiting on it.
|
839
|
+
#
|
840
|
+
# @param [Object] reason for the failure
|
841
|
+
# @raise [Concurrent::MultipleAssignmentError] if the `Future` has already been set or otherwise completed
|
842
|
+
# @return [Future]
|
843
|
+
def fail(reason = StandardError.new)
|
844
|
+
complete(false, nil, reason)
|
845
|
+
end
|
846
|
+
|
847
|
+
def try_fail(reason = StandardError.new)
|
848
|
+
!!complete(false, nil, reason, false)
|
849
|
+
end
|
850
|
+
|
851
|
+
public :complete
|
852
|
+
public :evaluate_to
|
853
|
+
|
854
|
+
# @return [Future]
|
855
|
+
def evaluate_to!(*args, block)
|
856
|
+
evaluate_to(*args, block).wait!
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
# @abstract
|
861
|
+
# @!visibility private
|
862
|
+
class InnerPromise < AbstractPromise
|
863
|
+
end
|
864
|
+
|
865
|
+
# @abstract
|
866
|
+
# @!visibility private
|
867
|
+
class BlockedPromise < InnerPromise
|
868
|
+
def initialize(future, blocked_by_futures, countdown, &block)
|
869
|
+
initialize_blocked_by(blocked_by_futures)
|
870
|
+
@Countdown = AtomicFixnum.new countdown
|
871
|
+
|
872
|
+
super(future)
|
873
|
+
@BlockedBy.each { |future| future.add_callback :pr_callback_notify_blocked, self }
|
874
|
+
end
|
875
|
+
|
876
|
+
# @api private
|
877
|
+
def on_done(future)
|
878
|
+
countdown = process_on_done(future)
|
879
|
+
completable = completable?(countdown)
|
880
|
+
|
881
|
+
if completable
|
882
|
+
on_completable(future)
|
883
|
+
# futures could be deleted from blocked_by one by one here, but that would be too expensive,
|
884
|
+
# it's done once when all are done to free the reference
|
885
|
+
clear_blocked_by!
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
def touch
|
890
|
+
blocked_by.each(&:touch)
|
891
|
+
end
|
892
|
+
|
893
|
+
# !visibility private
|
894
|
+
# for inspection only
|
895
|
+
def blocked_by
|
896
|
+
@BlockedBy
|
897
|
+
end
|
898
|
+
|
899
|
+
def inspect
|
900
|
+
"#{to_s[0..-2]} blocked_by:[#{ blocked_by.map(&:to_s).join(', ')}]>"
|
901
|
+
end
|
902
|
+
|
903
|
+
private
|
904
|
+
|
905
|
+
def initialize_blocked_by(blocked_by_futures)
|
906
|
+
@BlockedBy = Array(blocked_by_futures)
|
907
|
+
end
|
908
|
+
|
909
|
+
def clear_blocked_by!
|
910
|
+
# not synchronized because we do not care when this change propagates
|
911
|
+
@BlockedBy = []
|
912
|
+
nil
|
913
|
+
end
|
914
|
+
|
915
|
+
# @return [true,false] if completable
|
916
|
+
def completable?(countdown)
|
917
|
+
countdown.zero?
|
918
|
+
end
|
919
|
+
|
920
|
+
def process_on_done(future)
|
921
|
+
@Countdown.decrement
|
922
|
+
end
|
923
|
+
|
924
|
+
def on_completable(done_future)
|
925
|
+
raise NotImplementedError
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
# @abstract
|
930
|
+
# @!visibility private
|
931
|
+
class BlockedTaskPromise < BlockedPromise
|
932
|
+
def initialize(blocked_by_future, default_executor, executor, &task)
|
933
|
+
raise ArgumentError, 'no block given' unless block_given?
|
934
|
+
@Executor = executor
|
935
|
+
@Task = task
|
936
|
+
super Future.new(self, default_executor), blocked_by_future, 1
|
937
|
+
end
|
938
|
+
|
939
|
+
def executor
|
940
|
+
@Executor
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
# @!visibility private
|
945
|
+
class ThenPromise < BlockedTaskPromise
|
946
|
+
private
|
947
|
+
|
948
|
+
def initialize(blocked_by_future, default_executor, executor, &task)
|
949
|
+
raise ArgumentError, 'only Future can be appended with then' unless blocked_by_future.is_a? Future
|
950
|
+
super blocked_by_future, default_executor, executor, &task
|
951
|
+
end
|
952
|
+
|
953
|
+
def on_completable(done_future)
|
954
|
+
if done_future.success?
|
955
|
+
Concurrent.post_on(@Executor, done_future, @Task) do |done_future, task|
|
956
|
+
evaluate_to lambda { done_future.apply task }
|
957
|
+
end
|
958
|
+
else
|
959
|
+
complete false, nil, done_future.reason
|
960
|
+
end
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
# @!visibility private
|
965
|
+
class RescuePromise < BlockedTaskPromise
|
966
|
+
private
|
967
|
+
|
968
|
+
def initialize(blocked_by_future, default_executor, executor, &task)
|
969
|
+
raise ArgumentError, 'only Future can be rescued' unless blocked_by_future.is_a? Future
|
970
|
+
super blocked_by_future, default_executor, executor, &task
|
971
|
+
end
|
972
|
+
|
973
|
+
def on_completable(done_future)
|
974
|
+
if done_future.failed?
|
975
|
+
Concurrent.post_on(@Executor, done_future.reason, @Task) { |reason, task| evaluate_to reason, task }
|
976
|
+
else
|
977
|
+
complete true, done_future.value, nil
|
978
|
+
end
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
# @!visibility private
|
983
|
+
class ChainPromise < BlockedTaskPromise
|
984
|
+
private
|
985
|
+
|
986
|
+
def on_completable(done_future)
|
987
|
+
if Future === done_future
|
988
|
+
Concurrent.post_on(@Executor, done_future, @Task) { |future, task| evaluate_to *future.result, task }
|
989
|
+
else
|
990
|
+
Concurrent.post_on(@Executor, @Task) { |task| evaluate_to task }
|
991
|
+
end
|
992
|
+
end
|
993
|
+
end
|
994
|
+
|
995
|
+
# will be immediately completed
|
996
|
+
# @!visibility private
|
997
|
+
class ImmediatePromise < InnerPromise
|
998
|
+
def initialize(default_executor, *args)
|
999
|
+
super(if args.empty?
|
1000
|
+
Event.new(self, default_executor).complete
|
1001
|
+
else
|
1002
|
+
Future.new(self, default_executor).complete(true, args, nil)
|
1003
|
+
end)
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
# @!visibility private
|
1008
|
+
class FlattingPromise < BlockedPromise
|
1009
|
+
|
1010
|
+
# !visibility private
|
1011
|
+
def blocked_by
|
1012
|
+
@BlockedBy.each.to_a
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
private
|
1016
|
+
|
1017
|
+
def process_on_done(future)
|
1018
|
+
countdown = super(future)
|
1019
|
+
value = future.value!
|
1020
|
+
if countdown.nonzero?
|
1021
|
+
case value
|
1022
|
+
when Future
|
1023
|
+
@BlockedBy.push value
|
1024
|
+
value.add_callback :pr_callback_notify_blocked, self
|
1025
|
+
@Countdown.value
|
1026
|
+
when Event
|
1027
|
+
raise TypeError, 'cannot flatten to Event'
|
1028
|
+
else
|
1029
|
+
raise TypeError, "returned value #{value.inspect} is not a Future"
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
countdown
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def initialize(blocked_by_future, levels, default_executor)
|
1036
|
+
raise ArgumentError, 'levels has to be higher than 0' if levels < 1
|
1037
|
+
blocked_by_future.is_a? Future or
|
1038
|
+
raise ArgumentError, 'only Future can be flatten'
|
1039
|
+
super Future.new(self, default_executor), blocked_by_future, 1 + levels
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
def initialize_blocked_by(blocked_by_future)
|
1043
|
+
@BlockedBy = LockFreeStack.new.push(blocked_by_future)
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
def on_completable(done_future)
|
1047
|
+
complete *done_future.result
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
def clear_blocked_by!
|
1051
|
+
@BlockedBy.clear
|
1052
|
+
nil
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
# @!visibility private
|
1057
|
+
class AllPromise < BlockedPromise
|
1058
|
+
|
1059
|
+
private
|
1060
|
+
|
1061
|
+
def initialize(blocked_by_futures, default_executor)
|
1062
|
+
klass = Event
|
1063
|
+
blocked_by_futures.each do |f|
|
1064
|
+
if f.is_a?(Future)
|
1065
|
+
if klass == Event
|
1066
|
+
klass = Future
|
1067
|
+
break
|
1068
|
+
end
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
# noinspection RubyArgCount
|
1073
|
+
super(klass.new(self, default_executor), blocked_by_futures, blocked_by_futures.size)
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
def on_completable(done_future)
|
1077
|
+
all_success = true
|
1078
|
+
values = []
|
1079
|
+
reasons = []
|
1080
|
+
|
1081
|
+
blocked_by.each do |future|
|
1082
|
+
next unless future.is_a?(Future)
|
1083
|
+
success, value, reason = future.result
|
1084
|
+
|
1085
|
+
unless success
|
1086
|
+
all_success = false
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
values << value
|
1090
|
+
reasons << reason
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
if all_success
|
1094
|
+
if values.empty?
|
1095
|
+
complete
|
1096
|
+
else
|
1097
|
+
complete(true, values.size == 1 ? values.first : values, nil)
|
1098
|
+
end
|
1099
|
+
else
|
1100
|
+
# TODO what about other reasons?
|
1101
|
+
complete(false, nil, reasons.compact.first)
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
# @!visibility private
|
1107
|
+
class AnyPromise < BlockedPromise
|
1108
|
+
|
1109
|
+
private
|
1110
|
+
|
1111
|
+
def initialize(blocked_by_futures, default_executor)
|
1112
|
+
blocked_by_futures.all? { |f| f.is_a? Future } or
|
1113
|
+
raise ArgumentError, 'accepts only Futures not Events'
|
1114
|
+
super(Future.new(self, default_executor), blocked_by_futures, blocked_by_futures.size)
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
def completable?(countdown)
|
1118
|
+
true
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
def on_completable(done_future)
|
1122
|
+
complete *done_future.result, false
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
# @!visibility private
|
1127
|
+
class Delay < InnerPromise
|
1128
|
+
def touch
|
1129
|
+
if @Args.empty?
|
1130
|
+
@Future.complete
|
1131
|
+
else
|
1132
|
+
@Future.complete(true, @Args, nil)
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
private
|
1137
|
+
|
1138
|
+
def initialize(default_executor, *args)
|
1139
|
+
@Args = args
|
1140
|
+
super(if args.empty?
|
1141
|
+
Event.new(self, default_executor)
|
1142
|
+
else
|
1143
|
+
Future.new(self, default_executor)
|
1144
|
+
end)
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
# will be evaluated to task in intended_time
|
1149
|
+
# @!visibility private
|
1150
|
+
class ScheduledPromise < InnerPromise
|
1151
|
+
def intended_time
|
1152
|
+
@IntendedTime
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
def inspect
|
1156
|
+
"#{to_s[0..-2]} intended_time:[#{@IntendedTime}}>"
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
private
|
1160
|
+
|
1161
|
+
def initialize(default_executor, intended_time, *args)
|
1162
|
+
@IntendedTime = intended_time
|
1163
|
+
|
1164
|
+
in_seconds = begin
|
1165
|
+
now = Time.now
|
1166
|
+
schedule_time = if @IntendedTime.is_a? Time
|
1167
|
+
@IntendedTime
|
1168
|
+
else
|
1169
|
+
now + @IntendedTime
|
1170
|
+
end
|
1171
|
+
[0, schedule_time.to_f - now.to_f].max
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
use_event = args.empty?
|
1175
|
+
super(if use_event
|
1176
|
+
Event.new(self, default_executor)
|
1177
|
+
else
|
1178
|
+
Future.new(self, default_executor)
|
1179
|
+
end)
|
1180
|
+
|
1181
|
+
Concurrent.global_timer_set.post(in_seconds, *args) do |*args|
|
1182
|
+
if use_event
|
1183
|
+
@Future.complete
|
1184
|
+
else
|
1185
|
+
@Future.complete(true, args, nil)
|
1186
|
+
end
|
1187
|
+
end
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
# proof of concept
|
1192
|
+
class Channel < Synchronization::Object
|
1193
|
+
# TODO make lock free
|
1194
|
+
def initialize
|
1195
|
+
super
|
1196
|
+
@ProbeSet = Concurrent::Channel::WaitableList.new
|
1197
|
+
ensure_ivar_visibility!
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
def probe_set_size
|
1201
|
+
@ProbeSet.size
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
def push(value)
|
1205
|
+
until @ProbeSet.take.try_success([value, self])
|
1206
|
+
end
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
def pop
|
1210
|
+
select(Concurrent.future)
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
def select(probe)
|
1214
|
+
@ProbeSet.put(probe)
|
1215
|
+
probe
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
def inspect
|
1219
|
+
to_s
|
1220
|
+
end
|
1221
|
+
end
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
extend Edge::FutureShortcuts
|
1225
|
+
include Edge::FutureShortcuts
|
1226
|
+
end
|