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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +284 -0
  4. data/lib/concurrent-edge.rb +11 -0
  5. data/lib/concurrent/actor.rb +98 -0
  6. data/lib/concurrent/actor/behaviour.rb +143 -0
  7. data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
  8. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  9. data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
  10. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  11. data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
  12. data/lib/concurrent/actor/behaviour/linking.rb +83 -0
  13. data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
  14. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  15. data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
  16. data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
  17. data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
  18. data/lib/concurrent/actor/behaviour/termination.rb +74 -0
  19. data/lib/concurrent/actor/context.rb +167 -0
  20. data/lib/concurrent/actor/core.rb +220 -0
  21. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  22. data/lib/concurrent/actor/envelope.rb +41 -0
  23. data/lib/concurrent/actor/errors.rb +27 -0
  24. data/lib/concurrent/actor/internal_delegations.rb +59 -0
  25. data/lib/concurrent/actor/public_delegations.rb +40 -0
  26. data/lib/concurrent/actor/reference.rb +106 -0
  27. data/lib/concurrent/actor/root.rb +37 -0
  28. data/lib/concurrent/actor/type_check.rb +48 -0
  29. data/lib/concurrent/actor/utils.rb +10 -0
  30. data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
  31. data/lib/concurrent/actor/utils/balancer.rb +43 -0
  32. data/lib/concurrent/actor/utils/broadcast.rb +52 -0
  33. data/lib/concurrent/actor/utils/pool.rb +54 -0
  34. data/lib/concurrent/agent.rb +289 -0
  35. data/lib/concurrent/channel.rb +6 -0
  36. data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
  37. data/lib/concurrent/channel/buffered_channel.rb +87 -0
  38. data/lib/concurrent/channel/channel.rb +19 -0
  39. data/lib/concurrent/channel/ring_buffer.rb +65 -0
  40. data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
  41. data/lib/concurrent/channel/waitable_list.rb +48 -0
  42. data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
  43. data/lib/concurrent/edge/future.rb +1226 -0
  44. data/lib/concurrent/edge/lock_free_stack.rb +85 -0
  45. 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