concurrent-ruby-edge 0.1.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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