concurrent-ruby-edge 0.1.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/lib/concurrent-edge.rb +11 -0
- data/lib/concurrent/actor.rb +98 -0
- data/lib/concurrent/actor/behaviour.rb +143 -0
- data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
- data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
- data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
- data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
- data/lib/concurrent/actor/behaviour/linking.rb +83 -0
- data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
- data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
- data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
- data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
- data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
- data/lib/concurrent/actor/behaviour/termination.rb +74 -0
- data/lib/concurrent/actor/context.rb +167 -0
- data/lib/concurrent/actor/core.rb +220 -0
- data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
- data/lib/concurrent/actor/envelope.rb +41 -0
- data/lib/concurrent/actor/errors.rb +27 -0
- data/lib/concurrent/actor/internal_delegations.rb +59 -0
- data/lib/concurrent/actor/public_delegations.rb +40 -0
- data/lib/concurrent/actor/reference.rb +106 -0
- data/lib/concurrent/actor/root.rb +37 -0
- data/lib/concurrent/actor/type_check.rb +48 -0
- data/lib/concurrent/actor/utils.rb +10 -0
- data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
- data/lib/concurrent/actor/utils/balancer.rb +43 -0
- data/lib/concurrent/actor/utils/broadcast.rb +52 -0
- data/lib/concurrent/actor/utils/pool.rb +54 -0
- data/lib/concurrent/agent.rb +289 -0
- data/lib/concurrent/channel.rb +6 -0
- data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
- data/lib/concurrent/channel/buffered_channel.rb +87 -0
- data/lib/concurrent/channel/channel.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +65 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
- data/lib/concurrent/channel/waitable_list.rb +48 -0
- data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
- data/lib/concurrent/edge/future.rb +1226 -0
- data/lib/concurrent/edge/lock_free_stack.rb +85 -0
- metadata +110 -0
@@ -0,0 +1,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
|