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,14 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Behaviour
4
+ # Terminates all children when the actor terminates.
5
+ class TerminatesChildren < Abstract
6
+ def on_event(public, event)
7
+ event_name, _ = event
8
+ children.map { |ch| ch << :terminate! } if event_name == :terminated
9
+ super public, event
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,74 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Behaviour
4
+
5
+ # Handles actor termination.
6
+ # @note Actor rejects envelopes when terminated.
7
+ # @note TODO missing example
8
+ class Termination < Abstract
9
+
10
+ # @!attribute [r] terminated
11
+ # @return [Edge::Event] event which will become set when actor is terminated.
12
+ # @!attribute [r] reason
13
+ attr_reader :terminated, :reason
14
+
15
+ def initialize(core, subsequent, core_options, trapping = false)
16
+ super core, subsequent, core_options
17
+ @terminated = Concurrent.event
18
+ @public_terminated = @terminated.hide_completable
19
+ @reason = nil
20
+ @trapping = trapping
21
+ end
22
+
23
+ # @note Actor rejects envelopes when terminated.
24
+ # @return [true, false] if actor is terminated
25
+ def terminated?
26
+ @terminated.completed?
27
+ end
28
+
29
+ def trapping?
30
+ @trapping
31
+ end
32
+
33
+ def trapping=(val)
34
+ @trapping = !!val
35
+ end
36
+
37
+ def on_envelope(envelope)
38
+ command, reason = envelope.message
39
+ case command
40
+ when :terminated?
41
+ terminated?
42
+ when :terminate!
43
+ if trapping? && reason != :kill
44
+ pass envelope
45
+ else
46
+ terminate! reason
47
+ end
48
+ when :termination_event
49
+ @public_terminated
50
+ else
51
+ if terminated?
52
+ reject_envelope envelope
53
+ MESSAGE_PROCESSED
54
+ else
55
+ pass envelope
56
+ end
57
+ end
58
+ end
59
+
60
+ # Terminates the actor. Any Envelope received after termination is rejected.
61
+ # Terminates all its children, does not wait until they are terminated.
62
+ def terminate!(reason = :normal)
63
+ # TODO return after all children are terminated
64
+ return true if terminated?
65
+ @reason = reason
66
+ terminated.complete
67
+ broadcast(true, [:terminated, reason]) # TODO do not end up in Dead Letter Router
68
+ parent << :remove_child if parent
69
+ true
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,167 @@
1
+ require 'concurrent/concern/logging'
2
+
3
+ module Concurrent
4
+ module Actor
5
+
6
+ # New actor is defined by subclassing {RestartingContext}, {Context} and defining its abstract methods.
7
+ # {AbstractContext} can be subclassed directly to implement more specific behaviour see {Root} implementation.
8
+ #
9
+ # - {Context}
10
+ #
11
+ # > {include:Actor::Context}
12
+ #
13
+ # - {RestartingContext}.
14
+ #
15
+ # > {include:Actor::RestartingContext}
16
+ #
17
+ # Example of ac actor definition:
18
+ #
19
+ # {include:file:doc/actor/define.out.rb}
20
+ #
21
+ # See methods of {AbstractContext} what else can be tweaked, e.g {AbstractContext#default_reference_class}
22
+ #
23
+ # @abstract implement {AbstractContext#on_message} and {AbstractContext#behaviour_definition}
24
+ class AbstractContext
25
+ include TypeCheck
26
+ include InternalDelegations
27
+ include Concern::Logging
28
+
29
+ attr_reader :core
30
+
31
+ # @abstract override to define Actor's behaviour
32
+ # @param [Object] message
33
+ # @return [Object] a result which will be used to set the Future supplied to Reference#ask
34
+ # @note self should not be returned (or sent to other actors), {#reference} should be used
35
+ # instead
36
+ def on_message(message)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # override to add custom code invocation on internal events like `:terminated`, `:resumed`, `anError`.
41
+ def on_event(event)
42
+ end
43
+
44
+ # @api private
45
+ def on_envelope(envelope)
46
+ @envelope = envelope
47
+ on_message envelope.message
48
+ ensure
49
+ @envelope = nil
50
+ end
51
+
52
+ # if you want to pass the message to next behaviour, usually
53
+ # {Behaviour::ErrorsOnUnknownMessage}
54
+ def pass
55
+ core.behaviour!(Behaviour::ExecutesContext).pass envelope
56
+ end
57
+
58
+ # Defines an actor responsible for dead letters. Any rejected message send
59
+ # with {Reference#tell} is sent there, a message with future is considered
60
+ # already monitored for failures. Default behaviour is to use
61
+ # {AbstractContext#dead_letter_routing} of the parent, so if no
62
+ # {AbstractContext#dead_letter_routing} method is overridden in
63
+ # parent-chain the message ends up in `Actor.root.dead_letter_routing`
64
+ # agent which will log warning.
65
+ # @return [Reference]
66
+ def dead_letter_routing
67
+ parent.dead_letter_routing
68
+ end
69
+
70
+ # @return [Array<Array(Behavior::Abstract, Array<Object>)>]
71
+ def behaviour_definition
72
+ raise NotImplementedError
73
+ end
74
+
75
+ # @return [Envelope] current envelope, accessible inside #on_message processing
76
+ def envelope
77
+ @envelope or raise 'envelope not set'
78
+ end
79
+
80
+ # override if different class for reference is needed
81
+ # @return [CLass] descendant of {Reference}
82
+ def default_reference_class
83
+ Reference
84
+ end
85
+
86
+ # override to se different default executor, e.g. to change it to global_operation_pool
87
+ # @return [Executor]
88
+ def default_executor
89
+ Concurrent.global_io_executor
90
+ end
91
+
92
+ # tell self a message
93
+ def tell(message)
94
+ reference.tell message
95
+ end
96
+
97
+ def ask(message)
98
+ raise 'actor cannot ask itself'
99
+ end
100
+
101
+ alias_method :<<, :tell
102
+ alias_method :ask!, :ask
103
+
104
+ # Behaves as {Concurrent::Actor.spawn} but :class is auto-inserted based on receiver so it can be omitted.
105
+ # @example by class and name
106
+ # AdHoc.spawn(:ping1) { -> message { message } }
107
+ #
108
+ # @example by option hash
109
+ # inc2 = AdHoc.spawn(name: 'increment by 2',
110
+ # args: [2],
111
+ # executor: Concurrent.configuration.global_task_pool) do |increment_by|
112
+ # lambda { |number| number + increment_by }
113
+ # end
114
+ # inc2.ask!(2) # => 4
115
+ # @see Concurrent::Actor.spawn
116
+ def self.spawn(name_or_opts, *args, &block)
117
+ Actor.spawn to_spawn_options(name_or_opts, *args), &block
118
+ end
119
+
120
+ # behaves as {Concurrent::Actor.spawn!} but :class is auto-inserted based on receiver so it can be omitted.
121
+ def self.spawn!(name_or_opts, *args, &block)
122
+ Actor.spawn! to_spawn_options(name_or_opts, *args), &block
123
+ end
124
+
125
+ private
126
+
127
+ def initialize_core(core)
128
+ @core = Type! core, Core
129
+ end
130
+
131
+ def self.to_spawn_options(name_or_opts, *args)
132
+ if name_or_opts.is_a? Hash
133
+ if name_or_opts.key?(:class) && name_or_opts[:class] != self
134
+ raise ArgumentError,
135
+ ':class option is ignored when calling on context class, use Actor.spawn instead'
136
+ end
137
+ name_or_opts.merge class: self
138
+ else
139
+ { class: self, name: name_or_opts, args: args }
140
+ end
141
+ end
142
+
143
+ # to avoid confusion with Kernel.spawn
144
+ undef_method :spawn
145
+ end
146
+
147
+ # Basic Context of an Actor. It supports only linking and it simply terminates on error.
148
+ # Uses {Behaviour.basic_behaviour_definition}:
149
+ #
150
+ # @abstract implement {AbstractContext#on_message}
151
+ class Context < AbstractContext
152
+ def behaviour_definition
153
+ Behaviour.basic_behaviour_definition
154
+ end
155
+ end
156
+
157
+ # Context of an Actor for robust systems. It supports supervision, linking, pauses on error.
158
+ # Uses {Behaviour.restarting_behaviour_definition}
159
+ #
160
+ # @abstract implement {AbstractContext#on_message}
161
+ class RestartingContext < AbstractContext
162
+ def behaviour_definition
163
+ Behaviour.restarting_behaviour_definition
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,220 @@
1
+ require 'concurrent/concern/logging'
2
+ require 'concurrent/executors'
3
+
4
+ module Concurrent
5
+ module Actor
6
+
7
+ require 'set'
8
+
9
+ # Core of the actor.
10
+ # @note Whole class should be considered private. An user should use {Context}s and {Reference}s only.
11
+ # @note devel: core should not block on anything, e.g. it cannot wait on children to terminate
12
+ # that would eat up all threads in task pool and deadlock
13
+ class Core < Synchronization::Object
14
+ include TypeCheck
15
+ include Concern::Logging
16
+
17
+ # @!attribute [r] reference
18
+ # Reference to this actor which can be safely passed around.
19
+ # @return [Reference]
20
+ # @!attribute [r] name
21
+ # The name of actor instance, it should be uniq (not enforced). Allows easier orientation
22
+ # between actor instances.
23
+ # @return [String]
24
+ # @!attribute [r] path
25
+ # Path of this actor. It is used for easier orientation and logging.
26
+ # Path is constructed recursively with: `parent.path + self.name` up to a {Actor.root},
27
+ # e.g. `/an_actor/its_child`.
28
+ # @return [String]
29
+ # @!attribute [r] executor
30
+ # Executor which is used to process messages.
31
+ # @return [Executor]
32
+ # @!attribute [r] actor_class
33
+ # A subclass of {AbstractContext} representing Actor's behaviour.
34
+ # @return [Context]
35
+ attr_reader :reference, :name, :path, :executor, :context_class, :context, :behaviour_definition
36
+
37
+ # @option opts [String] name
38
+ # @option opts [Context] actor_class a class to be instantiated defining Actor's behaviour
39
+ # @option opts [Array<Object>] args arguments for actor_class instantiation
40
+ # @option opts [Executor] executor, default is `global_io_executor`
41
+ # @option opts [true, false] link, atomically link the actor to its parent (default: true)
42
+ # @option opts [Class] reference a custom descendant of {Reference} to use
43
+ # @option opts [Array<Array(Behavior::Abstract, Array<Object>)>] behaviour_definition, array of pairs
44
+ # where each pair is behaviour class and its args, see {Behaviour.basic_behaviour_definition}
45
+ # @option opts [CompletableFuture, nil] initialized, if present it'll be set or failed after {Context} initialization
46
+ # @option opts [Reference, nil] parent **private api** parent of the actor (the one spawning )
47
+ # @option opts [Proc, nil] logger a proc accepting (level, progname, message = nil, &block) params,
48
+ # can be used to hook actor instance to any logging system, see {Concurrent::Concern::Logging}
49
+ # @param [Proc] block for class instantiation
50
+ def initialize(opts = {}, &block)
51
+ super(&nil)
52
+ synchronize { ns_initialize(opts, &block) }
53
+ end
54
+
55
+ # A parent Actor. When actor is spawned the {Actor.current} becomes its parent.
56
+ # When actor is spawned from a thread outside of an actor ({Actor.current} is nil) {Actor.root} is assigned.
57
+ # @return [Reference, nil]
58
+ def parent
59
+ @parent_core && @parent_core.reference
60
+ end
61
+
62
+ # @see AbstractContext#dead_letter_routing
63
+ def dead_letter_routing
64
+ @context.dead_letter_routing
65
+ end
66
+
67
+ # @return [Array<Reference>] of children actors
68
+ def children
69
+ guard!
70
+ @children.to_a
71
+ end
72
+
73
+ # @api private
74
+ def add_child(child)
75
+ guard!
76
+ Type! child, Reference
77
+ @children.add child
78
+ nil
79
+ end
80
+
81
+ # @api private
82
+ def remove_child(child)
83
+ guard!
84
+ Type! child, Reference
85
+ @children.delete child
86
+ nil
87
+ end
88
+
89
+ # is executed by Reference scheduling processing of new messages
90
+ # can be called from other alternative Reference implementations
91
+ # @param [Envelope] envelope
92
+ def on_envelope(envelope)
93
+ schedule_execution do
94
+ log DEBUG, "was #{envelope.future ? 'asked' : 'told'} #{envelope.message.inspect} by #{envelope.sender}"
95
+ process_envelope envelope
96
+ end
97
+ nil
98
+ end
99
+
100
+ # ensures that we are inside of the executor
101
+ def guard!
102
+ unless Actor.current == reference
103
+ raise "can be called only inside actor #{reference} but was #{Actor.current}"
104
+ end
105
+ end
106
+
107
+ def log(level, message = nil, &block)
108
+ super level, @path, message, &block
109
+ end
110
+
111
+ # Schedules blocks to be executed on executor sequentially,
112
+ # sets Actress.current
113
+ def schedule_execution
114
+ @serialized_execution.post(@executor) do
115
+ synchronize do
116
+ begin
117
+ Thread.current[:__current_actor__] = reference
118
+ yield
119
+ rescue => e
120
+ log FATAL, e
121
+ ensure
122
+ Thread.current[:__current_actor__] = nil
123
+ end
124
+ end
125
+ end
126
+
127
+ nil
128
+ end
129
+
130
+ def broadcast(public, event)
131
+ log DEBUG, "event: #{event.inspect} (#{public ? 'public' : 'private'})"
132
+ @first_behaviour.on_event(public, event)
133
+ end
134
+
135
+ # @param [Class] behaviour_class
136
+ # @return [Behaviour::Abstract, nil] based on behaviour_class
137
+ def behaviour(behaviour_class)
138
+ @behaviours[behaviour_class]
139
+ end
140
+
141
+ # @param [Class] behaviour_class
142
+ # @return [Behaviour::Abstract] based on behaviour_class
143
+ # @raise [KeyError] when no behaviour
144
+ def behaviour!(behaviour_class)
145
+ @behaviours.fetch behaviour_class
146
+ end
147
+
148
+ # @api private
149
+ def allocate_context
150
+ @context = @context_class.allocate
151
+ end
152
+
153
+ # @api private
154
+ def build_context
155
+ @context.send :initialize_core, self
156
+ @context.send :initialize, *@args, &@block
157
+ end
158
+
159
+ # @api private
160
+ def process_envelope(envelope)
161
+ @first_behaviour.on_envelope envelope
162
+ end
163
+
164
+ private
165
+
166
+ def ns_initialize(opts, &block)
167
+ @mailbox = Array.new
168
+ @serialized_execution = SerializedExecution.new
169
+ @children = Set.new
170
+
171
+ @context_class = Child! opts.fetch(:class), AbstractContext
172
+ allocate_context
173
+
174
+ @executor = Type! opts.fetch(:executor, @context.default_executor), Concurrent::AbstractExecutorService
175
+ raise ArgumentError, 'ImmediateExecutor is not supported' if @executor.is_a? ImmediateExecutor
176
+
177
+ @reference = (Child! opts[:reference_class] || @context.default_reference_class, Reference).new self
178
+ @name = (Type! opts.fetch(:name), String, Symbol).to_s
179
+
180
+ parent = opts[:parent]
181
+ @parent_core = (Type! parent, Reference, NilClass) && parent.send(:core)
182
+ if @parent_core.nil? && @name != '/'
183
+ raise 'only root has no parent'
184
+ end
185
+
186
+ @path = @parent_core ? File.join(@parent_core.path, @name) : @name
187
+ @logger = opts[:logger]
188
+
189
+ @parent_core.add_child reference if @parent_core
190
+
191
+ initialize_behaviours opts
192
+
193
+ @args = opts.fetch(:args, [])
194
+ @block = block
195
+ initialized = Type! opts[:initialized], Edge::CompletableFuture, NilClass
196
+
197
+ schedule_execution do
198
+ begin
199
+ build_context
200
+ initialized.success reference if initialized
201
+ log DEBUG, 'spawned'
202
+ rescue => ex
203
+ log ERROR, ex
204
+ @first_behaviour.terminate!
205
+ initialized.fail ex if initialized
206
+ end
207
+ end
208
+ end
209
+
210
+ def initialize_behaviours(opts)
211
+ @behaviour_definition = (Type! opts[:behaviour_definition] || @context.behaviour_definition, Array).each do |(behaviour, *args)|
212
+ Child! behaviour, Behaviour::Abstract
213
+ end
214
+ @behaviours = {}
215
+ @first_behaviour = @behaviour_definition.reverse.
216
+ reduce(nil) { |last, (behaviour, *args)| @behaviours[behaviour] = behaviour.new(self, last, opts, *args) }
217
+ end
218
+ end
219
+ end
220
+ end