concurrent-ruby-edge 0.3.1 → 0.7.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 +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
|