concurrent-ruby-edge 0.1.0.pre2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/lib/concurrent-edge.rb +11 -0
- data/lib/concurrent/actor.rb +98 -0
- data/lib/concurrent/actor/behaviour.rb +143 -0
- data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
- data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
- data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
- data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
- data/lib/concurrent/actor/behaviour/linking.rb +83 -0
- data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
- data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
- data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
- data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
- data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
- data/lib/concurrent/actor/behaviour/termination.rb +74 -0
- data/lib/concurrent/actor/context.rb +167 -0
- data/lib/concurrent/actor/core.rb +220 -0
- data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
- data/lib/concurrent/actor/envelope.rb +41 -0
- data/lib/concurrent/actor/errors.rb +27 -0
- data/lib/concurrent/actor/internal_delegations.rb +59 -0
- data/lib/concurrent/actor/public_delegations.rb +40 -0
- data/lib/concurrent/actor/reference.rb +106 -0
- data/lib/concurrent/actor/root.rb +37 -0
- data/lib/concurrent/actor/type_check.rb +48 -0
- data/lib/concurrent/actor/utils.rb +10 -0
- data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
- data/lib/concurrent/actor/utils/balancer.rb +43 -0
- data/lib/concurrent/actor/utils/broadcast.rb +52 -0
- data/lib/concurrent/actor/utils/pool.rb +54 -0
- data/lib/concurrent/agent.rb +289 -0
- data/lib/concurrent/channel.rb +6 -0
- data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
- data/lib/concurrent/channel/buffered_channel.rb +87 -0
- data/lib/concurrent/channel/channel.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +65 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
- data/lib/concurrent/channel/waitable_list.rb +48 -0
- data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
- data/lib/concurrent/edge/future.rb +1226 -0
- data/lib/concurrent/edge/lock_free_stack.rb +85 -0
- metadata +110 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
class Envelope
|
4
|
+
include TypeCheck
|
5
|
+
|
6
|
+
# @!attribute [r] message
|
7
|
+
# @return [Object] a message
|
8
|
+
# @!attribute [r] future
|
9
|
+
# @return [Edge::Future] a future which becomes resolved after message is processed
|
10
|
+
# @!attribute [r] sender
|
11
|
+
# @return [Reference, Thread] an actor or thread sending the message
|
12
|
+
# @!attribute [r] address
|
13
|
+
# @return [Reference] where this message will be delivered
|
14
|
+
|
15
|
+
attr_reader :message, :future, :sender, :address
|
16
|
+
|
17
|
+
def initialize(message, future, sender, address)
|
18
|
+
@message = message
|
19
|
+
@future = Type! future, Edge::CompletableFuture, NilClass
|
20
|
+
@sender = Type! sender, Reference, Thread
|
21
|
+
@address = Type! address, Reference
|
22
|
+
end
|
23
|
+
|
24
|
+
def sender_path
|
25
|
+
if sender.is_a? Reference
|
26
|
+
sender.path
|
27
|
+
else
|
28
|
+
sender.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def address_path
|
33
|
+
address.path
|
34
|
+
end
|
35
|
+
|
36
|
+
def reject!(error)
|
37
|
+
future.fail error unless future.nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
Error = Class.new(StandardError)
|
4
|
+
|
5
|
+
class ActorTerminated < Error
|
6
|
+
include TypeCheck
|
7
|
+
|
8
|
+
attr_reader :reference
|
9
|
+
|
10
|
+
def initialize(reference)
|
11
|
+
@reference = Type! reference, Reference
|
12
|
+
super reference.path
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UnknownMessage < Error
|
17
|
+
include TypeCheck
|
18
|
+
|
19
|
+
attr_reader :envelope
|
20
|
+
|
21
|
+
def initialize(envelope)
|
22
|
+
@envelope = Type! envelope, Envelope
|
23
|
+
super envelope.message.inspect
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module InternalDelegations
|
4
|
+
include PublicDelegations
|
5
|
+
|
6
|
+
# @see Core#children
|
7
|
+
def children
|
8
|
+
core.children
|
9
|
+
end
|
10
|
+
|
11
|
+
# @see Termination#terminate!
|
12
|
+
def terminate!(reason = nil)
|
13
|
+
behaviour!(Behaviour::Termination).terminate!(reason)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see Termination#terminated?
|
17
|
+
def terminated?
|
18
|
+
behaviour!(Behaviour::Termination).terminated?
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see Termination#reason
|
22
|
+
def reason
|
23
|
+
behaviour!(Behaviour::Termination).reason
|
24
|
+
end
|
25
|
+
|
26
|
+
# delegates to core.log
|
27
|
+
# @see Logging#log
|
28
|
+
def log(level, message = nil, &block)
|
29
|
+
core.log(level, message, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see AbstractContext#dead_letter_routing
|
33
|
+
def dead_letter_routing
|
34
|
+
context.dead_letter_routing
|
35
|
+
end
|
36
|
+
|
37
|
+
def redirect(reference, envelope = self.envelope)
|
38
|
+
reference.message(envelope.message, envelope.future)
|
39
|
+
Behaviour::MESSAGE_PROCESSED
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [AbstractContext]
|
43
|
+
def context
|
44
|
+
core.context
|
45
|
+
end
|
46
|
+
|
47
|
+
# see Core#behaviour
|
48
|
+
def behaviour(behaviour_class)
|
49
|
+
core.behaviour(behaviour_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
# see Core#behaviour!
|
53
|
+
def behaviour!(behaviour_class)
|
54
|
+
core.behaviour!(behaviour_class)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
|
4
|
+
# Provides publicly expose-able methods from {Core}.
|
5
|
+
module PublicDelegations
|
6
|
+
# @see Core#name
|
7
|
+
def name
|
8
|
+
core.name
|
9
|
+
end
|
10
|
+
|
11
|
+
# @see Core#path
|
12
|
+
def path
|
13
|
+
core.path
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see Core#parent
|
17
|
+
def parent
|
18
|
+
core.parent
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see Core#reference
|
22
|
+
def reference
|
23
|
+
core.reference
|
24
|
+
end
|
25
|
+
|
26
|
+
# @see Core#executor
|
27
|
+
def executor
|
28
|
+
core.executor
|
29
|
+
end
|
30
|
+
|
31
|
+
# @see Core#context_class
|
32
|
+
def context_class
|
33
|
+
core.context_class
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :ref, :reference
|
37
|
+
alias_method :actor_class, :context_class
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
|
4
|
+
# {Reference} is public interface of Actor instances. It is used for sending messages and can
|
5
|
+
# be freely passed around the application. It also provides some basic information about the actor,
|
6
|
+
# see {PublicDelegations}.
|
7
|
+
#
|
8
|
+
# AdHoc.spawn('printer') { -> message { puts message } }
|
9
|
+
# # => #<Concurrent::Actor::Reference:0x7fd0d2883218 /printer (Concurrent::Actor::Utils::AdHoc)>
|
10
|
+
# # ^object_id ^path ^context class
|
11
|
+
class Reference
|
12
|
+
include TypeCheck
|
13
|
+
include PublicDelegations
|
14
|
+
|
15
|
+
attr_reader :core
|
16
|
+
private :core
|
17
|
+
|
18
|
+
# @!visibility private
|
19
|
+
def initialize(core)
|
20
|
+
@core = Type! core, Core
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sends the message asynchronously to the actor and immediately returns
|
24
|
+
# `self` (the reference) allowing to chain message telling.
|
25
|
+
# @param [Object] message
|
26
|
+
# @return [Reference] self
|
27
|
+
# @example
|
28
|
+
# printer = AdHoc.spawn('printer') { -> message { puts message } }
|
29
|
+
# printer.tell('ping').tell('pong')
|
30
|
+
# printer << 'ping' << 'pong'
|
31
|
+
# # => 'ping'\n'pong'\n'ping'\n'pong'\n
|
32
|
+
def tell(message)
|
33
|
+
message message, nil
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :<<, :tell
|
37
|
+
|
38
|
+
# @note it's a good practice to use tell whenever possible. Ask should be used only for
|
39
|
+
# testing and when it returns very shortly. It can lead to deadlock if all threads in
|
40
|
+
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
|
41
|
+
# global_io_executor.
|
42
|
+
#
|
43
|
+
# @note it's a good practice to use {#tell} whenever possible. Results can be send back with other messages.
|
44
|
+
# Ask should be used only for testing and when it returns very shortly. It can lead to deadlock if all threads in
|
45
|
+
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
|
46
|
+
# global_io_executor.
|
47
|
+
# @param [Object] message
|
48
|
+
# @param [Edge::Future] future to be fulfilled be message's processing result
|
49
|
+
# @return [Edge::Future] supplied future
|
50
|
+
# @example
|
51
|
+
# adder = AdHoc.spawn('adder') { -> message { message + 1 } }
|
52
|
+
# adder.ask(1).value # => 2
|
53
|
+
# adder.ask(nil).wait.reason # => #<NoMethodError: undefined method `+' for nil:NilClass>
|
54
|
+
def ask(message, future = Concurrent.future)
|
55
|
+
message message, future
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sends the message synchronously and blocks until the message
|
59
|
+
# is processed. Raises on error.
|
60
|
+
#
|
61
|
+
# @note it's a good practice to use {#tell} whenever possible. Results can be send back with other messages.
|
62
|
+
# Ask should be used only for testing and when it returns very shortly. It can lead to deadlock if all threads in
|
63
|
+
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
|
64
|
+
# global_io_executor.
|
65
|
+
# @param [Object] message
|
66
|
+
# @param [Edge::Future] future to be fulfilled be message's processing result
|
67
|
+
# @return [Object] message's processing result
|
68
|
+
# @raise [Exception] future.reason if future is #failed?
|
69
|
+
# @example
|
70
|
+
# adder = AdHoc.spawn('adder') { -> message { message + 1 } }
|
71
|
+
# adder.ask!(1) # => 2
|
72
|
+
def ask!(message, future = Concurrent.future)
|
73
|
+
ask(message, future).value!
|
74
|
+
end
|
75
|
+
|
76
|
+
def map(messages)
|
77
|
+
messages.map { |m| self.ask(m) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# behaves as {#tell} when no future and as {#ask} when future
|
81
|
+
def message(message, future = nil)
|
82
|
+
core.on_envelope Envelope.new(message, future, Actor.current || Thread.current, self)
|
83
|
+
return future ? future.hide_completable : self
|
84
|
+
end
|
85
|
+
|
86
|
+
# @see AbstractContext#dead_letter_routing
|
87
|
+
def dead_letter_routing
|
88
|
+
core.dead_letter_routing
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
"#<#{self.class}:0x#{'%x' % (object_id << 1)} #{path} (#{actor_class})>"
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :inspect, :to_s
|
96
|
+
|
97
|
+
def ==(other)
|
98
|
+
Type? other, self.class and other.send(:core) == core
|
99
|
+
end
|
100
|
+
|
101
|
+
# to avoid confusion with Kernel.spawn
|
102
|
+
undef_method :spawn
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
# implements the root actor
|
4
|
+
class Root < AbstractContext
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
# noinspection RubyArgCount
|
8
|
+
@dead_letter_router = Core.new(parent: reference,
|
9
|
+
class: DefaultDeadLetterHandler,
|
10
|
+
supervise: true,
|
11
|
+
name: :default_dead_letter_handler).reference
|
12
|
+
end
|
13
|
+
|
14
|
+
# to allow spawning of new actors, spawn needs to be called inside the parent Actor
|
15
|
+
def on_message(message)
|
16
|
+
case
|
17
|
+
when message.is_a?(Array) && message.first == :spawn
|
18
|
+
Actor.spawn message[1], &message[2]
|
19
|
+
when message == :dead_letter_routing
|
20
|
+
@dead_letter_router
|
21
|
+
else
|
22
|
+
# ignore
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def dead_letter_routing
|
27
|
+
@dead_letter_router
|
28
|
+
end
|
29
|
+
|
30
|
+
def behaviour_definition
|
31
|
+
[*Behaviour.base(:just_log),
|
32
|
+
*Behaviour.supervising,
|
33
|
+
*Behaviour.user_messages]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
|
4
|
+
# taken from Algebrick
|
5
|
+
# supplies type-checking helpers whenever included
|
6
|
+
module TypeCheck
|
7
|
+
|
8
|
+
def Type?(value, *types)
|
9
|
+
types.any? { |t| value.is_a? t }
|
10
|
+
end
|
11
|
+
|
12
|
+
def Type!(value, *types)
|
13
|
+
Type?(value, *types) or
|
14
|
+
TypeCheck.error(value, 'is not', types)
|
15
|
+
value
|
16
|
+
end
|
17
|
+
|
18
|
+
def Match?(value, *types)
|
19
|
+
types.any? { |t| t === value }
|
20
|
+
end
|
21
|
+
|
22
|
+
def Match!(value, *types)
|
23
|
+
Match?(value, *types) or
|
24
|
+
TypeCheck.error(value, 'is not matching', types)
|
25
|
+
value
|
26
|
+
end
|
27
|
+
|
28
|
+
def Child?(value, *types)
|
29
|
+
Type?(value, Class) &&
|
30
|
+
types.any? { |t| value <= t }
|
31
|
+
end
|
32
|
+
|
33
|
+
def Child!(value, *types)
|
34
|
+
Child?(value, *types) or
|
35
|
+
TypeCheck.error(value, 'is not child', types)
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.error(value, message, types)
|
42
|
+
raise TypeError,
|
43
|
+
"Value (#{value.class}) '#{value}' #{message} any of: #{types.join('; ')}."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
module AsAdHoc
|
6
|
+
def initialize(*args, &initializer)
|
7
|
+
@on_message = Type! initializer.call(*args), Proc
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_message(message)
|
11
|
+
instance_exec message, &@on_message
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Allows quick creation of actors with behaviour defined by blocks.
|
16
|
+
# @example ping
|
17
|
+
# AdHoc.spawn :forward, an_actor do |where|
|
18
|
+
# # this block has to return proc defining #on_message behaviour
|
19
|
+
# -> message { where.tell message }
|
20
|
+
# end
|
21
|
+
# @note TODO remove in favor of the module
|
22
|
+
class AdHoc < Context
|
23
|
+
include AsAdHoc
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Distributes messages between subscribed actors. Each actor'll get only one message then
|
6
|
+
# it's unsubscribed. The actor needs to resubscribe when it's ready to receive next message.
|
7
|
+
# It will buffer the messages if there is no worker registered.
|
8
|
+
# @see Pool
|
9
|
+
class Balancer < RestartingContext
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@receivers = []
|
13
|
+
@buffer = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_message(message)
|
17
|
+
command, who = message
|
18
|
+
case command
|
19
|
+
when :subscribe
|
20
|
+
@receivers << (who || envelope.sender)
|
21
|
+
distribute
|
22
|
+
true
|
23
|
+
when :unsubscribe
|
24
|
+
@receivers.delete(who || envelope.sender)
|
25
|
+
true
|
26
|
+
when :subscribed?
|
27
|
+
@receivers.include?(who || envelope.sender)
|
28
|
+
else
|
29
|
+
@buffer << envelope
|
30
|
+
distribute
|
31
|
+
Behaviour::MESSAGE_PROCESSED
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def distribute
|
36
|
+
while !@receivers.empty? && !@buffer.empty?
|
37
|
+
redirect @receivers.shift, @buffer.shift
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|