concurrent-ruby-edge 0.3.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +552 -0
- data/LICENSE.txt +18 -18
- data/README.md +261 -103
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/abstract.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/awaits.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/buffer.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/errors_on_unknown_message.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/executes_context.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/linking.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/pausing.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/removes_child.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/sets_results.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/supervising.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/termination.rb +3 -1
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour.rb +1 -1
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/context.rb +3 -1
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/core.rb +5 -4
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/default_dead_letter_handler.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/envelope.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/errors.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/internal_delegations.rb +3 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/reference.rb +9 -8
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/root.rb +3 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/ad_hoc.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/balancer.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/broadcast.rb +1 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/pool.rb +1 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor.rb +11 -6
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/base.rb +14 -14
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/dropping.rb +1 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/sliding.rb +1 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/unbuffered.rb +1 -1
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/tick.rb +1 -1
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel.rb +3 -2
- data/lib/concurrent-ruby-edge/concurrent/edge/cancellation.rb +107 -0
- data/lib/concurrent-ruby-edge/concurrent/edge/channel.rb +453 -0
- data/lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb +1549 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set/node.rb +2 -2
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set.rb +8 -7
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_queue.rb +2 -0
- data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/old_channel_integration.rb +2 -0
- data/lib/concurrent-ruby-edge/concurrent/edge/processing_actor.rb +184 -0
- data/lib/concurrent-ruby-edge/concurrent/edge/promises.rb +174 -0
- data/lib/concurrent-ruby-edge/concurrent/edge/throttle.rb +229 -0
- data/lib/concurrent-ruby-edge/concurrent/edge/version.rb +3 -0
- data/lib/concurrent-ruby-edge/concurrent/edge.rb +21 -0
- data/lib/concurrent-ruby-edge/concurrent/executor/wrapping_executor.rb +50 -0
- data/lib/concurrent-ruby-edge/concurrent/lazy_register.rb +83 -0
- data/lib/{concurrent-edge.rb → concurrent-ruby-edge/concurrent-edge.rb} +5 -4
- metadata +71 -67
- data/lib/concurrent/edge/atomic_markable_reference.rb +0 -184
- data/lib/concurrent/edge/cancellation.rb +0 -138
- data/lib/concurrent/edge/lock_free_stack.rb +0 -126
- data/lib/concurrent/edge/processing_actor.rb +0 -161
- data/lib/concurrent/edge/promises.rb +0 -2111
- data/lib/concurrent/edge/throttle.rb +0 -192
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/public_delegations.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/type_check.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/buffered.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/ticker.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/timer.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/after_clause.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/default_clause.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/error_clause.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/put_clause.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/take_clause.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector.rb +0 -0
- /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set/window.rb +0 -0
@@ -1,138 +0,0 @@
|
|
1
|
-
module Concurrent
|
2
|
-
|
3
|
-
# Provides tools for cooperative cancellation.
|
4
|
-
# Inspired by <https://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx>
|
5
|
-
#
|
6
|
-
# @example
|
7
|
-
# # Create new cancellation. `cancellation` is used for cancelling, `token` is passed down to
|
8
|
-
# # tasks for cooperative cancellation
|
9
|
-
# cancellation, token = Concurrent::Cancellation.create
|
10
|
-
# Thread.new(token) do |token|
|
11
|
-
# # Count 1+1 (simulating some other meaningful work) repeatedly
|
12
|
-
# # until the token is cancelled through cancellation.
|
13
|
-
# token.loop_until_canceled { 1+1 }
|
14
|
-
# end
|
15
|
-
# sleep 0.1
|
16
|
-
# cancellation.cancel # Stop the thread by cancelling
|
17
|
-
class Cancellation < Synchronization::Object
|
18
|
-
safe_initialization!
|
19
|
-
|
20
|
-
# Creates the cancellation object. Returns both the cancellation and the token for convenience.
|
21
|
-
# @param [Object] resolve_args resolve_args Arguments which are used when resolve method is called on
|
22
|
-
# resolvable_future_or_event
|
23
|
-
# @param [Promises::Resolvable] resolvable_future_or_event resolvable used to track cancellation.
|
24
|
-
# Can be retrieved by `token.to_future` ot `token.to_event`.
|
25
|
-
# @example
|
26
|
-
# cancellation, token = Concurrent::Cancellation.create
|
27
|
-
# @return [Array(Cancellation, Cancellation::Token)]
|
28
|
-
def self.create(resolvable_future_or_event = Promises.resolvable_event, *resolve_args)
|
29
|
-
cancellation = new(resolvable_future_or_event, *resolve_args)
|
30
|
-
[cancellation, cancellation.token]
|
31
|
-
end
|
32
|
-
|
33
|
-
private_class_method :new
|
34
|
-
|
35
|
-
# Returns the token associated with the cancellation.
|
36
|
-
# @return [Token]
|
37
|
-
def token
|
38
|
-
@Token
|
39
|
-
end
|
40
|
-
|
41
|
-
# Cancel this cancellation. All executions depending on the token will cooperatively stop.
|
42
|
-
# @return [true, false]
|
43
|
-
# @raise when cancelling for the second tim
|
44
|
-
def cancel(raise_on_repeated_call = true)
|
45
|
-
!!@Cancel.resolve(*@ResolveArgs, raise_on_repeated_call)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Is the cancellation cancelled?
|
49
|
-
# @return [true, false]
|
50
|
-
def canceled?
|
51
|
-
@Cancel.resolved?
|
52
|
-
end
|
53
|
-
|
54
|
-
# Short string representation.
|
55
|
-
# @return [String]
|
56
|
-
def to_s
|
57
|
-
format '<#%s:0x%x canceled:%s>', self.class, object_id << 1, canceled?
|
58
|
-
end
|
59
|
-
|
60
|
-
alias_method :inspect, :to_s
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def initialize(future, *resolve_args)
|
65
|
-
raise ArgumentError, 'future is not Resolvable' unless future.is_a?(Promises::Resolvable)
|
66
|
-
@Cancel = future
|
67
|
-
@Token = Token.new @Cancel.with_hidden_resolvable
|
68
|
-
@ResolveArgs = resolve_args
|
69
|
-
end
|
70
|
-
|
71
|
-
# Created through {Cancellation.create}, passed down to tasks to be able to check if canceled.
|
72
|
-
class Token < Synchronization::Object
|
73
|
-
safe_initialization!
|
74
|
-
|
75
|
-
# @return [Event] Event which will be resolved when the token is cancelled.
|
76
|
-
def to_event
|
77
|
-
@Cancel.to_event
|
78
|
-
end
|
79
|
-
|
80
|
-
# @return [Future] Future which will be resolved when the token is cancelled with arguments passed in
|
81
|
-
# {Cancellation.create} .
|
82
|
-
def to_future
|
83
|
-
@Cancel.to_future
|
84
|
-
end
|
85
|
-
|
86
|
-
# Is the token cancelled?
|
87
|
-
# @return [true, false]
|
88
|
-
def canceled?
|
89
|
-
@Cancel.resolved?
|
90
|
-
end
|
91
|
-
|
92
|
-
# Repeatedly evaluates block until the token is {#canceled?}.
|
93
|
-
# @yield to the block repeatedly.
|
94
|
-
# @yieldreturn [Object]
|
95
|
-
# @return [Object] last result of the block
|
96
|
-
def loop_until_canceled(&block)
|
97
|
-
until canceled?
|
98
|
-
result = block.call
|
99
|
-
end
|
100
|
-
result
|
101
|
-
end
|
102
|
-
|
103
|
-
# Raise error when cancelled
|
104
|
-
# @param [#exception] error to be risen
|
105
|
-
# @raise the error
|
106
|
-
# @return [self]
|
107
|
-
def raise_if_canceled(error = CancelledOperationError)
|
108
|
-
raise error if canceled?
|
109
|
-
self
|
110
|
-
end
|
111
|
-
|
112
|
-
# Creates a new token which is cancelled when any of the tokens is.
|
113
|
-
# @param [Token] tokens to combine
|
114
|
-
# @return [Token] new token
|
115
|
-
def join(*tokens, &block)
|
116
|
-
block ||= -> tokens { Promises.any_event(*tokens.map(&:to_event)) }
|
117
|
-
self.class.new block.call([@Cancel, *tokens])
|
118
|
-
end
|
119
|
-
|
120
|
-
# Short string representation.
|
121
|
-
# @return [String]
|
122
|
-
def to_s
|
123
|
-
format '<#%s:0x%x canceled:%s>', self.class, object_id << 1, canceled?
|
124
|
-
end
|
125
|
-
|
126
|
-
alias_method :inspect, :to_s
|
127
|
-
|
128
|
-
private
|
129
|
-
|
130
|
-
def initialize(cancel)
|
131
|
-
@Cancel = cancel
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# FIXME (pitr-ch 27-Mar-2016): cooperation with mutex, condition, select etc?
|
136
|
-
# TODO (pitr-ch 27-Mar-2016): examples (scheduled to be cancelled in 10 sec)
|
137
|
-
end
|
138
|
-
end
|
@@ -1,126 +0,0 @@
|
|
1
|
-
module Concurrent
|
2
|
-
|
3
|
-
# @!visibility private
|
4
|
-
class LockFreeStack < Synchronization::Object
|
5
|
-
|
6
|
-
safe_initialization!
|
7
|
-
|
8
|
-
class Node
|
9
|
-
# TODO (pitr-ch 20-Dec-2016): Could be unified with Stack class?
|
10
|
-
|
11
|
-
attr_reader :value, :next_node
|
12
|
-
# allow to nil-ify to free GC when the entry is no longer relevant, not synchronised
|
13
|
-
attr_writer :value
|
14
|
-
|
15
|
-
def initialize(value, next_node)
|
16
|
-
@value = value
|
17
|
-
@next_node = next_node
|
18
|
-
end
|
19
|
-
|
20
|
-
singleton_class.send :alias_method, :[], :new
|
21
|
-
end
|
22
|
-
|
23
|
-
class Empty < Node
|
24
|
-
def next_node
|
25
|
-
self
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
EMPTY = Empty[nil, nil]
|
30
|
-
|
31
|
-
private(*attr_atomic(:head))
|
32
|
-
|
33
|
-
def self.of1(value)
|
34
|
-
new Node[value, EMPTY]
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.of2(value1, value2)
|
38
|
-
new Node[value1, Node[value2, EMPTY]]
|
39
|
-
end
|
40
|
-
|
41
|
-
def initialize(head = EMPTY)
|
42
|
-
super()
|
43
|
-
self.head = head
|
44
|
-
end
|
45
|
-
|
46
|
-
def empty?(head = self.head)
|
47
|
-
head.equal? EMPTY
|
48
|
-
end
|
49
|
-
|
50
|
-
def compare_and_push(head, value)
|
51
|
-
compare_and_set_head head, Node[value, head]
|
52
|
-
end
|
53
|
-
|
54
|
-
def push(value)
|
55
|
-
while true
|
56
|
-
current_head = head
|
57
|
-
return self if compare_and_set_head current_head, Node[value, current_head]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def peek
|
62
|
-
head
|
63
|
-
end
|
64
|
-
|
65
|
-
def compare_and_pop(head)
|
66
|
-
compare_and_set_head head, head.next_node
|
67
|
-
end
|
68
|
-
|
69
|
-
def pop
|
70
|
-
while true
|
71
|
-
current_head = head
|
72
|
-
return current_head.value if compare_and_set_head current_head, current_head.next_node
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def compare_and_clear(head)
|
77
|
-
compare_and_set_head head, EMPTY
|
78
|
-
end
|
79
|
-
|
80
|
-
include Enumerable
|
81
|
-
|
82
|
-
def each(head = nil)
|
83
|
-
return to_enum(:each, head) unless block_given?
|
84
|
-
it = head || peek
|
85
|
-
until it.equal?(EMPTY)
|
86
|
-
yield it.value
|
87
|
-
it = it.next_node
|
88
|
-
end
|
89
|
-
self
|
90
|
-
end
|
91
|
-
|
92
|
-
def clear
|
93
|
-
while true
|
94
|
-
current_head = head
|
95
|
-
return false if current_head == EMPTY
|
96
|
-
return true if compare_and_set_head current_head, EMPTY
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def clear_if(head)
|
101
|
-
compare_and_set_head head, EMPTY
|
102
|
-
end
|
103
|
-
|
104
|
-
def replace_if(head, new_head)
|
105
|
-
compare_and_set_head head, new_head
|
106
|
-
end
|
107
|
-
|
108
|
-
def clear_each(&block)
|
109
|
-
while true
|
110
|
-
current_head = head
|
111
|
-
return self if current_head == EMPTY
|
112
|
-
if compare_and_set_head current_head, EMPTY
|
113
|
-
each current_head, &block
|
114
|
-
return self
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# @return [String] Short string representation.
|
120
|
-
def to_s
|
121
|
-
format '<#%s:0x%x %s>', self.class, object_id << 1, to_a.to_s
|
122
|
-
end
|
123
|
-
|
124
|
-
alias_method :inspect, :to_s
|
125
|
-
end
|
126
|
-
end
|
@@ -1,161 +0,0 @@
|
|
1
|
-
module Concurrent
|
2
|
-
|
3
|
-
# A new implementation of actor which also simulates the process, therefore it can be used
|
4
|
-
# in the same way as Erlang's actors but **without** occupying thread. A tens of thousands
|
5
|
-
# ProcessingActors can run at the same time sharing a thread pool.
|
6
|
-
# @example
|
7
|
-
# # Runs on a pool, does not consume 50_000 threads
|
8
|
-
# actors = 50_000.times.map do |i|
|
9
|
-
# Concurrent::ProcessingActor.act(i) { |a, i| a.receive.then_on(:fast, i) { |m, i| m + i } }
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# actors.each { |a| a.tell 1 }
|
13
|
-
# values = actors.map(&:termination).map(&:value)
|
14
|
-
# values[0,5] # => [1, 2, 3, 4, 5]
|
15
|
-
# values[-5, 5] # => [49996, 49997, 49998, 49999, 50000]
|
16
|
-
# @!macro edge_warning
|
17
|
-
class ProcessingActor < Synchronization::Object
|
18
|
-
# TODO (pitr-ch 18-Dec-2016): (un)linking, bidirectional, sends special message, multiple link calls has no effect,
|
19
|
-
# TODO (pitr-ch 21-Dec-2016): Make terminated a cancellation token?
|
20
|
-
# link_spawn atomic, Can it be fixed by sending exit when linked dead actor?
|
21
|
-
|
22
|
-
safe_initialization!
|
23
|
-
|
24
|
-
# @return [Promises::Channel] actor's mailbox.
|
25
|
-
def mailbox
|
26
|
-
@Mailbox
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [Promises::Future(Object)] a future which is resolved when the actor ends its processing.
|
30
|
-
# It can either be fulfilled with a value when actor ends normally or rejected with
|
31
|
-
# a reason (exception) when actor fails.
|
32
|
-
def termination
|
33
|
-
@Terminated.with_hidden_resolvable
|
34
|
-
end
|
35
|
-
|
36
|
-
# Creates an actor.
|
37
|
-
# @see #act_listening Behaves the same way, but does not take mailbox as a first argument.
|
38
|
-
# @return [ProcessingActor]
|
39
|
-
# @example
|
40
|
-
# actor = Concurrent::ProcessingActor.act do |actor|
|
41
|
-
# actor.receive.then do |message|
|
42
|
-
# # the actor ends normally with message
|
43
|
-
# message
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# actor.tell :a_message
|
48
|
-
# # => <#Concurrent::ProcessingActor:0x7fff11280560 termination:pending>
|
49
|
-
# actor.termination.value! # => :a_message
|
50
|
-
def self.act(*args, &process)
|
51
|
-
act_listening Promises::Channel.new, *args, &process
|
52
|
-
end
|
53
|
-
|
54
|
-
# Creates an actor listening to a specified channel (mailbox).
|
55
|
-
# @param [Object] args Arguments passed to the process.
|
56
|
-
# @param [Promises::Channel] channel which serves as mailing box. The channel can have limited
|
57
|
-
# size to achieve backpressure.
|
58
|
-
# @yield args to the process to get back a future which represents the actors execution.
|
59
|
-
# @yieldparam [Object] *args
|
60
|
-
# @yieldreturn [Promises::Future(Object)] a future representing next step of execution
|
61
|
-
# @return [ProcessingActor]
|
62
|
-
# @example
|
63
|
-
# # TODO (pitr-ch 19-Jan-2017): actor with limited mailbox
|
64
|
-
def self.act_listening(channel, *args, &process)
|
65
|
-
actor = ProcessingActor.new channel
|
66
|
-
Promises.
|
67
|
-
future(actor, *args, &process).
|
68
|
-
run.
|
69
|
-
chain_resolvable(actor.instance_variable_get(:@Terminated))
|
70
|
-
actor
|
71
|
-
end
|
72
|
-
|
73
|
-
# Receives a message when available, used in the actor's process.
|
74
|
-
# @return [Promises::Future(Object)] a future which will be fulfilled with a message from
|
75
|
-
# mailbox when it is available.
|
76
|
-
def receive(probe = Promises.resolvable_future)
|
77
|
-
# TODO (pitr-ch 27-Dec-2016): patterns
|
78
|
-
@Mailbox.pop probe
|
79
|
-
end
|
80
|
-
|
81
|
-
# Tells a message to the actor. May block current thread if the mailbox is full.
|
82
|
-
# {#tell} is a better option since it does not block. It's usually used to integrate with
|
83
|
-
# threading code.
|
84
|
-
# @example
|
85
|
-
# Thread.new(actor) do |actor|
|
86
|
-
# # ...
|
87
|
-
# actor.tell! :a_message # blocks until the message is told
|
88
|
-
# # (there is a space for it in the channel)
|
89
|
-
# # ...
|
90
|
-
# end
|
91
|
-
# @param [Object] message
|
92
|
-
# @return [self]
|
93
|
-
def tell!(message)
|
94
|
-
@Mailbox.push(message).wait!
|
95
|
-
self
|
96
|
-
end
|
97
|
-
|
98
|
-
# Tells a message to the actor.
|
99
|
-
# @param [Object] message
|
100
|
-
# @return [Promises::Future(ProcessingActor)] a future which will be fulfilled with the actor
|
101
|
-
# when the message is pushed to mailbox.
|
102
|
-
def tell(message)
|
103
|
-
@Mailbox.push(message).then(self) { |_, actor| actor }
|
104
|
-
end
|
105
|
-
|
106
|
-
# Simplifies common pattern when a message sender also requires an answer to the message
|
107
|
-
# from the actor. It appends a resolvable_future for the answer after the message.
|
108
|
-
# @todo has to be nice also on the receive side, cannot make structure like this [message = [...], answer]
|
109
|
-
# all receives should receive something friendly
|
110
|
-
# @param [Object] message
|
111
|
-
# @param [Promises::ResolvableFuture] answer
|
112
|
-
# @return [Promises::Future] a future which will be fulfilled with the answer to the message
|
113
|
-
# @example
|
114
|
-
# add_once_actor = Concurrent::ProcessingActor.act do |actor|
|
115
|
-
# actor.receive.then do |(a, b), answer|
|
116
|
-
# result = a + b
|
117
|
-
# answer.fulfill result
|
118
|
-
# # terminate with result value
|
119
|
-
# result
|
120
|
-
# end
|
121
|
-
# end
|
122
|
-
# # => <#Concurrent::ProcessingActor:0x7fcd1315f6e8 termination:pending>
|
123
|
-
#
|
124
|
-
# add_once_actor.ask([1, 2]).value! # => 3
|
125
|
-
# # fails the actor already added once
|
126
|
-
# add_once_actor.ask(%w(ab cd)).reason
|
127
|
-
# # => #<RuntimeError: actor terminated normally before answering with a value: 3>
|
128
|
-
# add_once_actor.termination.value! # => 3
|
129
|
-
def ask(message, answer = Promises.resolvable_future)
|
130
|
-
tell [message, answer]
|
131
|
-
# do not leave answers unanswered when actor terminates.
|
132
|
-
Promises.any(
|
133
|
-
Promises.fulfilled_future(:answer).zip(answer),
|
134
|
-
Promises.fulfilled_future(:termination).zip(@Terminated)
|
135
|
-
).chain do |fulfilled, (which, value), (_, reason)|
|
136
|
-
# TODO (pitr-ch 20-Jan-2017): we have to know which future was resolved
|
137
|
-
# TODO (pitr-ch 20-Jan-2017): make the combinator programmable, so anyone can create what is needed
|
138
|
-
# TODO (pitr-ch 19-Jan-2017): ensure no callbacks are accumulated on @Terminated
|
139
|
-
if which == :termination
|
140
|
-
raise reason.nil? ? format('actor terminated normally before answering with a value: %s', value) : reason
|
141
|
-
else
|
142
|
-
fulfilled ? value : raise(reason)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# @return [String] string representation.
|
148
|
-
def inspect
|
149
|
-
format '<#%s:0x%x termination:%s>', self.class, object_id << 1, termination.state
|
150
|
-
end
|
151
|
-
|
152
|
-
private
|
153
|
-
|
154
|
-
def initialize(channel = Promises::Channel.new)
|
155
|
-
@Mailbox = channel
|
156
|
-
@Terminated = Promises.resolvable_future
|
157
|
-
super()
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
end
|