concurrent-ruby-edge 0.1.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|