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,51 @@
|
|
1
|
+
require 'concurrent/concern/logging'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Actor
|
5
|
+
module Behaviour
|
6
|
+
class Abstract
|
7
|
+
include TypeCheck
|
8
|
+
include InternalDelegations
|
9
|
+
include Concern::Logging
|
10
|
+
|
11
|
+
attr_reader :core, :subsequent
|
12
|
+
|
13
|
+
def initialize(core, subsequent, core_options)
|
14
|
+
@core = Type! core, Core
|
15
|
+
@subsequent = Type! subsequent, Abstract, NilClass
|
16
|
+
end
|
17
|
+
|
18
|
+
# override to add extra behaviour
|
19
|
+
# @note super needs to be called not to break the chain
|
20
|
+
def on_envelope(envelope)
|
21
|
+
pass envelope
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [Envelope] envelope to pass to {#subsequent} behaviour
|
25
|
+
def pass(envelope)
|
26
|
+
subsequent.on_envelope envelope
|
27
|
+
end
|
28
|
+
|
29
|
+
# override to add extra behaviour
|
30
|
+
# @note super needs to be called not to break the chain
|
31
|
+
def on_event(public, event)
|
32
|
+
subsequent.on_event public, event if subsequent
|
33
|
+
end
|
34
|
+
|
35
|
+
# broadcasts event to all behaviours and context
|
36
|
+
# @see #on_event
|
37
|
+
# @see AbstractContext#on_event
|
38
|
+
def broadcast(public, event)
|
39
|
+
core.broadcast(public, event)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reject_envelope(envelope)
|
43
|
+
envelope.reject! ActorTerminated.new(reference)
|
44
|
+
dead_letter_routing << envelope unless envelope.future
|
45
|
+
log DEBUG, "rejected #{envelope.message} from #{envelope.sender_path}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
|
5
|
+
# Accepts `:await` messages. Which allows to wait on Actor to process all previously send
|
6
|
+
# messages.
|
7
|
+
#
|
8
|
+
# actor << :a << :b
|
9
|
+
# actor.ask(:await).wait # blocks until :a and :b are processed
|
10
|
+
class Awaits < Abstract
|
11
|
+
def on_envelope(envelope)
|
12
|
+
if envelope.message == :await
|
13
|
+
true
|
14
|
+
else
|
15
|
+
pass envelope
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
|
5
|
+
# Any message reaching this behaviour is buffered. Only one message is is
|
6
|
+
# scheduled at any given time. Others are kept in buffer until another one
|
7
|
+
# can be scheduled. This effectively means that messages handled by
|
8
|
+
# behaviours before buffer have higher priority and they can be processed
|
9
|
+
# before messages arriving into buffer. This allows for the processing of
|
10
|
+
# internal actor messages like (`:link`, `:supervise`) first.
|
11
|
+
class Buffer < Abstract
|
12
|
+
def initialize(core, subsequent, core_options)
|
13
|
+
super core, subsequent, core_options
|
14
|
+
@buffer = []
|
15
|
+
@receive_envelope_scheduled = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_envelope(envelope)
|
19
|
+
@buffer.push envelope
|
20
|
+
process_envelopes?
|
21
|
+
MESSAGE_PROCESSED
|
22
|
+
end
|
23
|
+
|
24
|
+
# Ensures that only one envelope processing is scheduled with #schedule_execution,
|
25
|
+
# this allows other scheduled blocks to be executed before next envelope processing.
|
26
|
+
# Simply put this ensures that Core is still responsive to internal calls (like add_child)
|
27
|
+
# even though the Actor is flooded with messages.
|
28
|
+
def process_envelopes?
|
29
|
+
unless @buffer.empty? || @receive_envelope_scheduled
|
30
|
+
@receive_envelope_scheduled = true
|
31
|
+
process_envelope
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_envelope
|
36
|
+
envelope = @buffer.shift
|
37
|
+
return nil unless envelope
|
38
|
+
pass envelope
|
39
|
+
ensure
|
40
|
+
@receive_envelope_scheduled = false
|
41
|
+
core.schedule_execution { process_envelopes? }
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_event(public, event)
|
45
|
+
event_name, _ = event
|
46
|
+
case event_name
|
47
|
+
when :terminated, :restarted
|
48
|
+
@buffer.each { |envelope| reject_envelope envelope }
|
49
|
+
@buffer.clear
|
50
|
+
end
|
51
|
+
super public, event_name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
# Simply fails when message arrives here. It's usually the last behaviour.
|
5
|
+
class ErrorsOnUnknownMessage < Abstract
|
6
|
+
def on_envelope(envelope)
|
7
|
+
raise UnknownMessage, envelope
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
# Delegates messages and events to {AbstractContext} instance.
|
5
|
+
class ExecutesContext < Abstract
|
6
|
+
def on_envelope(envelope)
|
7
|
+
context.on_envelope envelope
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_event(public, event)
|
11
|
+
context.on_event(event)
|
12
|
+
super public, event
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
# TODO track what is linked, clean when :terminated
|
5
|
+
# send :linked/:unlinked messages back to build the array of linked actors
|
6
|
+
|
7
|
+
# Links the actor to other actors and sends actor's events to them,
|
8
|
+
# like: `:terminated`, `:paused`, `:resumed`, errors, etc.
|
9
|
+
# Linked actor needs to handle those messages.
|
10
|
+
#
|
11
|
+
# listener = AdHoc.spawn name: :listener do
|
12
|
+
# lambda do |message|
|
13
|
+
# case message
|
14
|
+
# when Reference
|
15
|
+
# if message.ask!(:linked?)
|
16
|
+
# message << :unlink
|
17
|
+
# else
|
18
|
+
# message << :link
|
19
|
+
# end
|
20
|
+
# else
|
21
|
+
# puts "got event #{message.inspect} from #{envelope.sender}"
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# an_actor = AdHoc.spawn name: :an_actor, supervise: true, behaviour_definition: Behaviour.restarting_behaviour_definition do
|
27
|
+
# lambda { |message| raise 'failed'}
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # link the actor
|
31
|
+
# listener.ask(an_actor).wait
|
32
|
+
# an_actor.ask(:fail).wait
|
33
|
+
# # unlink the actor
|
34
|
+
# listener.ask(an_actor).wait
|
35
|
+
# an_actor.ask(:fail).wait
|
36
|
+
# an_actor << :terminate!
|
37
|
+
#
|
38
|
+
# produces only two events, other events happened after unlinking
|
39
|
+
#
|
40
|
+
# got event #<RuntimeError: failed> from #<Concurrent::Actor::Reference /an_actor (Concurrent::Actor::Utils::AdHoc)>
|
41
|
+
# got event :reset from #<Concurrent::Actor::Reference /an_actor (Concurrent::Actor::Utils::AdHoc)>
|
42
|
+
class Linking < Abstract
|
43
|
+
def initialize(core, subsequent, core_options)
|
44
|
+
super core, subsequent, core_options
|
45
|
+
@linked = Set.new
|
46
|
+
@linked.add Actor.current if core_options[:link] != false
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_envelope(envelope)
|
50
|
+
case envelope.message
|
51
|
+
when :link
|
52
|
+
link envelope.sender
|
53
|
+
when :unlink
|
54
|
+
unlink envelope.sender
|
55
|
+
when :linked?
|
56
|
+
@linked.include? envelope.sender
|
57
|
+
when :linked
|
58
|
+
@linked.to_a
|
59
|
+
else
|
60
|
+
pass envelope
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def link(ref)
|
65
|
+
@linked.add(ref)
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def unlink(ref)
|
70
|
+
@linked.delete(ref)
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_event(public, event)
|
75
|
+
event_name, _ = event
|
76
|
+
@linked.each { |a| a << event } if public
|
77
|
+
@linked.clear if event_name == :terminated
|
78
|
+
super public, event
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
|
5
|
+
# Allows to pause actors on errors.
|
6
|
+
# When paused all arriving messages are collected and processed after the actor
|
7
|
+
# is resumed or reset. Resume will simply continue with next message.
|
8
|
+
# Reset also reinitialized context.
|
9
|
+
# @note TODO missing example
|
10
|
+
class Pausing < Abstract
|
11
|
+
def initialize(core, subsequent, core_options)
|
12
|
+
super core, subsequent, core_options
|
13
|
+
@paused = false
|
14
|
+
@deferred = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def paused?
|
18
|
+
@paused
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_envelope(envelope)
|
22
|
+
case envelope.message
|
23
|
+
when :pause!
|
24
|
+
pause!
|
25
|
+
when :paused?
|
26
|
+
paused?
|
27
|
+
when :resume!
|
28
|
+
resume!
|
29
|
+
when :reset!
|
30
|
+
reset!
|
31
|
+
when :restart!
|
32
|
+
restart!
|
33
|
+
else
|
34
|
+
if paused?
|
35
|
+
@deferred << envelope
|
36
|
+
MESSAGE_PROCESSED
|
37
|
+
else
|
38
|
+
pass envelope
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def pause!(error = nil)
|
44
|
+
do_pause
|
45
|
+
broadcast true, error || :paused
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def resume!
|
50
|
+
return false unless paused?
|
51
|
+
do_resume
|
52
|
+
broadcast(true, :resumed)
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def reset!
|
57
|
+
return false unless paused?
|
58
|
+
broadcast(false, :resetting)
|
59
|
+
do_reset
|
60
|
+
broadcast(true, :reset)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def restart!
|
65
|
+
return false unless paused?
|
66
|
+
broadcast(false, :restarting)
|
67
|
+
do_restart
|
68
|
+
broadcast(true, :restarted)
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def on_event(public, event)
|
73
|
+
event_name, _ = event
|
74
|
+
reject_deferred if event_name == :terminated
|
75
|
+
super public, event
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def do_pause
|
81
|
+
@paused = true
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def do_resume
|
86
|
+
@paused = false
|
87
|
+
reschedule_deferred
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def do_reset
|
92
|
+
rebuild_context
|
93
|
+
do_resume
|
94
|
+
reschedule_deferred
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def do_restart
|
99
|
+
rebuild_context
|
100
|
+
reject_deferred
|
101
|
+
do_resume
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def rebuild_context
|
106
|
+
core.allocate_context
|
107
|
+
core.build_context
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def reschedule_deferred
|
112
|
+
@deferred.each { |envelope| core.schedule_execution { core.process_envelope envelope } }
|
113
|
+
@deferred.clear
|
114
|
+
end
|
115
|
+
|
116
|
+
def reject_deferred
|
117
|
+
@deferred.each { |envelope| reject_envelope envelope }
|
118
|
+
@deferred.clear
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
# Removes terminated children.
|
5
|
+
class RemovesChild < Abstract
|
6
|
+
def on_envelope(envelope)
|
7
|
+
if envelope.message == :remove_child
|
8
|
+
core.remove_child envelope.sender
|
9
|
+
else
|
10
|
+
pass envelope
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
# Collects returning value and sets the CompletableFuture in the {Envelope} or error on failure.
|
5
|
+
class SetResults < Abstract
|
6
|
+
attr_reader :error_strategy
|
7
|
+
|
8
|
+
def initialize(core, subsequent, core_options, error_strategy)
|
9
|
+
super core, subsequent, core_options
|
10
|
+
@error_strategy = Match! error_strategy, :just_log, :terminate!, :pause!
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_envelope(envelope)
|
14
|
+
result = pass envelope
|
15
|
+
if result != MESSAGE_PROCESSED && !envelope.future.nil?
|
16
|
+
envelope.future.success result
|
17
|
+
log DEBUG, "finished processing of #{envelope.message.inspect}"
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
rescue => error
|
21
|
+
log ERROR, error
|
22
|
+
case error_strategy
|
23
|
+
when :terminate!
|
24
|
+
terminate!
|
25
|
+
when :pause!
|
26
|
+
behaviour!(Pausing).pause!(error)
|
27
|
+
when :just_log
|
28
|
+
# nothing
|
29
|
+
else
|
30
|
+
raise
|
31
|
+
end
|
32
|
+
envelope.future.fail error unless envelope.future.nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Actor
|
3
|
+
module Behaviour
|
4
|
+
|
5
|
+
# Handles supervised actors. Handle configures what to do with failed child: :terminate!, :resume!, :reset!,
|
6
|
+
# or :restart!. Strategy sets :one_for_one (restarts just failed actor) or :one_for_all (restarts all child actors).
|
7
|
+
# @note TODO missing example
|
8
|
+
# @note this will change in next version to support supervision trees better
|
9
|
+
class Supervising < Abstract
|
10
|
+
def initialize(core, subsequent, core_options, handle, strategy)
|
11
|
+
super core, subsequent, core_options
|
12
|
+
@handle = Match! handle, :terminate!, :resume!, :reset!, :restart!
|
13
|
+
@strategy = case @handle
|
14
|
+
when :terminate!
|
15
|
+
Match! strategy, nil
|
16
|
+
when :resume!
|
17
|
+
Match! strategy, :one_for_one
|
18
|
+
when :reset!, :restart!
|
19
|
+
Match! strategy, :one_for_one, :one_for_all
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_envelope(envelope)
|
24
|
+
case envelope.message
|
25
|
+
when Exception, :paused
|
26
|
+
receivers = if @strategy == :one_for_all
|
27
|
+
children
|
28
|
+
else
|
29
|
+
[envelope.sender]
|
30
|
+
end
|
31
|
+
receivers.each { |ch| ch << @handle }
|
32
|
+
else
|
33
|
+
pass envelope
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|