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