concurrent-ruby 0.7.0.rc1-java → 0.7.0.rc2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/README.md +3 -2
  2. data/lib/concurrent.rb +2 -1
  3. data/lib/concurrent/actor.rb +104 -0
  4. data/lib/concurrent/{actress → actor}/ad_hoc.rb +2 -3
  5. data/lib/concurrent/actor/behaviour.rb +70 -0
  6. data/lib/concurrent/actor/behaviour/abstract.rb +48 -0
  7. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  8. data/lib/concurrent/actor/behaviour/buffer.rb +54 -0
  9. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  10. data/lib/concurrent/actor/behaviour/executes_context.rb +18 -0
  11. data/lib/concurrent/actor/behaviour/linking.rb +42 -0
  12. data/lib/concurrent/actor/behaviour/pausing.rb +77 -0
  13. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  14. data/lib/concurrent/actor/behaviour/sets_results.rb +36 -0
  15. data/lib/concurrent/actor/behaviour/supervised.rb +58 -0
  16. data/lib/concurrent/actor/behaviour/supervising.rb +34 -0
  17. data/lib/concurrent/actor/behaviour/terminates_children.rb +13 -0
  18. data/lib/concurrent/actor/behaviour/termination.rb +54 -0
  19. data/lib/concurrent/actor/context.rb +153 -0
  20. data/lib/concurrent/actor/core.rb +213 -0
  21. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  22. data/lib/concurrent/{actress → actor}/envelope.rb +1 -1
  23. data/lib/concurrent/actor/errors.rb +27 -0
  24. data/lib/concurrent/actor/internal_delegations.rb +49 -0
  25. data/lib/concurrent/{actress/core_delegations.rb → actor/public_delegations.rb} +11 -13
  26. data/lib/concurrent/{actress → actor}/reference.rb +25 -8
  27. data/lib/concurrent/actor/root.rb +37 -0
  28. data/lib/concurrent/{actress → actor}/type_check.rb +1 -1
  29. data/lib/concurrent/actor/utills.rb +7 -0
  30. data/lib/concurrent/actor/utils/broadcast.rb +36 -0
  31. data/lib/concurrent/actress.rb +2 -224
  32. data/lib/concurrent/agent.rb +10 -12
  33. data/lib/concurrent/atomic.rb +32 -1
  34. data/lib/concurrent/atomic/atomic_boolean.rb +55 -13
  35. data/lib/concurrent/atomic/atomic_fixnum.rb +54 -16
  36. data/lib/concurrent/atomic/synchronization.rb +51 -0
  37. data/lib/concurrent/atomic/thread_local_var.rb +15 -50
  38. data/lib/concurrent/atomic_reference/mutex_atomic.rb +1 -1
  39. data/lib/concurrent/atomic_reference/ruby.rb +15 -0
  40. data/lib/concurrent/atomics.rb +1 -0
  41. data/lib/concurrent/channel/unbuffered_channel.rb +2 -1
  42. data/lib/concurrent/configuration.rb +6 -3
  43. data/lib/concurrent/dataflow.rb +20 -3
  44. data/lib/concurrent/delay.rb +23 -31
  45. data/lib/concurrent/executor/executor.rb +7 -2
  46. data/lib/concurrent/executor/timer_set.rb +1 -1
  47. data/lib/concurrent/future.rb +2 -1
  48. data/lib/concurrent/lazy_register.rb +58 -0
  49. data/lib/concurrent/options_parser.rb +4 -2
  50. data/lib/concurrent/promise.rb +2 -1
  51. data/lib/concurrent/scheduled_task.rb +6 -5
  52. data/lib/concurrent/tvar.rb +6 -10
  53. data/lib/concurrent/utility/processor_count.rb +4 -2
  54. data/lib/concurrent/version.rb +1 -1
  55. data/lib/concurrent_ruby_ext.jar +0 -0
  56. data/lib/concurrent_ruby_ext.so +0 -0
  57. metadata +32 -11
  58. data/lib/concurrent/actress/context.rb +0 -98
  59. data/lib/concurrent/actress/core.rb +0 -228
  60. data/lib/concurrent/actress/errors.rb +0 -14
  61. data/lib/concurrent_ruby_ext.bundle +0 -0
@@ -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,36 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Behaviour
4
+ # Collects returning value and sets the IVar in the {Envelope} or error on failure.
5
+ class SetResults < Abstract
6
+ attr_reader :error_strategy
7
+
8
+ def initialize(core, subsequent, error_strategy)
9
+ super core, subsequent
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.ivar.nil?
16
+ envelope.ivar.set result
17
+ end
18
+ nil
19
+ rescue => error
20
+ log Logging::ERROR, error
21
+ case error_strategy
22
+ when :terminate!
23
+ terminate!
24
+ when :pause!
25
+ behaviour!(Pausing).pause!(error)
26
+ when :just_log
27
+ # nothing
28
+ else
29
+ raise
30
+ end
31
+ envelope.ivar.fail error unless envelope.ivar.nil?
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Behaviour
4
+
5
+ # Sets nad holds the supervisor of the actor if any. There is only one or none supervisor
6
+ # for each actor. Each supervisor is automatically linked.
7
+ class Supervised < Abstract
8
+ attr_reader :supervisor
9
+
10
+ def initialize(core, subsequent)
11
+ super core, subsequent
12
+ @supervisor = nil
13
+ end
14
+
15
+ def on_envelope(envelope)
16
+ case envelope.message
17
+ when :supervise
18
+ supervise envelope.sender
19
+ when :supervisor
20
+ supervisor
21
+ when :un_supervise
22
+ un_supervise envelope.sender
23
+ when :pause!, :resume!, :reset!, :restart!
24
+ # allow only supervisor to control the actor
25
+ if @supervisor == envelope.sender
26
+ pass envelope
27
+ else
28
+ false
29
+ end
30
+ else
31
+ pass envelope
32
+ end
33
+ end
34
+
35
+ def supervise(ref)
36
+ @supervisor = ref
37
+ behaviour!(Linking).link ref
38
+ true
39
+ end
40
+
41
+ def un_supervise(ref)
42
+ if @supervisor == ref
43
+ behaviour!(Linking).unlink ref
44
+ @supervisor = nil
45
+ true
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ def on_event(event)
52
+ @supervisor = nil if event == :terminated
53
+ super event
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,34 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Behaviour
4
+ class Supervising < Abstract
5
+ def initialize(core, subsequent, handle, strategy)
6
+ super core, subsequent
7
+ @handle = Match! handle, :terminate!, :resume!, :reset!, :restart!
8
+ @strategy = case @handle
9
+ when :terminate!
10
+ Match! strategy, nil
11
+ when :resume!
12
+ Match! strategy, :one_for_one
13
+ when :reset!, :restart!
14
+ Match! strategy, :one_for_one, :one_for_all
15
+ end
16
+ end
17
+
18
+ def on_envelope(envelope)
19
+ case envelope.message
20
+ when Exception, :paused
21
+ receivers = if @strategy == :one_for_all
22
+ children
23
+ else
24
+ [envelope.sender]
25
+ end
26
+ receivers.each { |ch| ch << @handle }
27
+ else
28
+ pass envelope
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
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(event)
7
+ children.each { |ch| ch << :terminate! } if event == :terminated
8
+ super event
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,54 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Behaviour
4
+
5
+ # Handles actor termination.
6
+ # @note Actor rejects envelopes when terminated.
7
+ class Termination < Abstract
8
+
9
+ # @!attribute [r] terminated
10
+ # @return [Event] event which will become set when actor is terminated.
11
+ attr_reader :terminated
12
+
13
+ def initialize(core, subsequent)
14
+ super core, subsequent
15
+ @terminated = Event.new
16
+ end
17
+
18
+ # @note Actor rejects envelopes when terminated.
19
+ # @return [true, false] if actor is terminated
20
+ def terminated?
21
+ @terminated.set?
22
+ end
23
+
24
+ def on_envelope(envelope)
25
+ case envelope.message
26
+ when :terminated?
27
+ terminated?
28
+ when :terminate!
29
+ terminate!
30
+ when :terminated_event
31
+ terminated
32
+ else
33
+ if terminated?
34
+ reject_envelope envelope
35
+ MESSAGE_PROCESSED
36
+ else
37
+ pass envelope
38
+ end
39
+ end
40
+ end
41
+
42
+ # Terminates the actor. Any Envelope received after termination is rejected.
43
+ # Terminates all its children, does not wait until they are terminated.
44
+ def terminate!
45
+ return true if terminated?
46
+ terminated.set
47
+ broadcast(:terminated)
48
+ parent << :remove_child if parent
49
+ true
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,153 @@
1
+ module Concurrent
2
+ module Actor
3
+
4
+ # Abstract implementation of Actor context. Children has to implement
5
+ # {AbstractContext#on_message} and {AbstractContext#behaviour_definition} methods.
6
+ # There are two implementations:
7
+ #
8
+ # - {Context}
9
+ #
10
+ # > {include:Actor::Context}
11
+ #
12
+ # - {RestartingContext}.
13
+ #
14
+ # > {include:Actor::RestartingContext}
15
+ class AbstractContext
16
+ include TypeCheck
17
+ include InternalDelegations
18
+
19
+ attr_reader :core
20
+
21
+ # @abstract override to define Actor's behaviour
22
+ # @param [Object] message
23
+ # @return [Object] a result which will be used to set the IVar supplied to Reference#ask
24
+ # @note self should not be returned (or sent to other actors), {#reference} should be used
25
+ # instead
26
+ def on_message(message)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ # override to add custom code invocation on events like `:terminated`, `:resumed`, `anError`.
31
+ def on_event(event)
32
+ end
33
+
34
+ # @api private
35
+ def on_envelope(envelope)
36
+ @envelope = envelope
37
+ on_message envelope.message
38
+ ensure
39
+ @envelope = nil
40
+ end
41
+
42
+ # if you want to pass the message to next behaviour, usually {Behaviour::ErrorsOnUnknownMessage}
43
+ def pass
44
+ core.behaviour!(Behaviour::ExecutesContext).pass envelope
45
+ end
46
+
47
+ # Defines an actor responsible for dead letters. Any rejected message send with
48
+ # {Reference#tell} is sent there, a message with ivar is considered already monitored for
49
+ # failures. Default behaviour is to use {AbstractContext#dead_letter_routing} of the parent,
50
+ # so if no {AbstractContext#dead_letter_routing} method is overridden in parent-chain the message ends up in
51
+ # `Actor.root.dead_letter_routing` agent which will log warning.
52
+ # @return [Reference]
53
+ def dead_letter_routing
54
+ parent.dead_letter_routing
55
+ end
56
+
57
+ # @return [Array<Array(Behavior::Abstract, Array<Object>)>]
58
+ def behaviour_definition
59
+ raise NotImplementedError
60
+ end
61
+
62
+ # @return [Envelope] current envelope, accessible inside #on_message processing
63
+ def envelope
64
+ @envelope or raise 'envelope not set'
65
+ end
66
+
67
+ # override if different class for reference is needed
68
+ # @return [CLass] descendant of {Reference}
69
+ def default_reference_class
70
+ Reference
71
+ end
72
+
73
+ def tell(message)
74
+ reference.tell message
75
+ end
76
+
77
+ def ask(message)
78
+ raise 'actor cannot ask itself'
79
+ end
80
+
81
+ alias_method :<<, :tell
82
+ alias_method :ask!, :ask
83
+
84
+ private
85
+
86
+ def initialize_core(core)
87
+ @core = Type! core, Core
88
+ end
89
+
90
+ # behaves as {Concurrent::Actor.spawn} but :class is auto-inserted based on receiver
91
+ def self.spawn(name_or_opts, *args, &block)
92
+ Actor.spawn spawn_optionify(name_or_opts, *args), &block
93
+ end
94
+
95
+ # behaves as {Concurrent::Actor.spawn!} but :class is auto-inserted based on receiver
96
+ def self.spawn!(name_or_opts, *args, &block)
97
+ Actor.spawn! spawn_optionify(name_or_opts, *args), &block
98
+ end
99
+
100
+ private
101
+
102
+ def self.spawn_optionify(name_or_opts, *args)
103
+ if name_or_opts.is_a? Hash
104
+ if name_or_opts.key?(:class) && name_or_opts[:class] != self
105
+ raise ArgumentError,
106
+ ':class option is ignored when calling on context class, use Actor.spawn instead'
107
+ end
108
+ name_or_opts.merge class: self
109
+ else
110
+ { class: self, name: name_or_opts, args: args }
111
+ end
112
+ end
113
+
114
+ # to avoid confusion with Kernel.spawn
115
+ undef_method :spawn
116
+ end
117
+
118
+ # Basic Context of an Actor.
119
+ #
120
+ # - linking
121
+ # - terminates on error
122
+ #
123
+ # TODO describe behaviour
124
+ # TODO usage
125
+ # @example ping
126
+ # class Ping < Context
127
+ # def on_message(message)
128
+ # message
129
+ # end
130
+ # end
131
+ #
132
+ # Ping.spawn(:ping1).ask(:m).value #=> :m
133
+ class Context < AbstractContext
134
+ def behaviour_definition
135
+ Behaviour.basic_behaviour_definition
136
+ end
137
+ end
138
+
139
+ # Context of an Actor for complex robust systems.
140
+ #
141
+ # - linking
142
+ # - supervising
143
+ # - pauses on error
144
+ #
145
+ # TODO describe behaviour
146
+ # TODO usage
147
+ class RestartingContext < AbstractContext
148
+ def behaviour_definition
149
+ Behaviour.restarting_behaviour_definition
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,213 @@
1
+ module Concurrent
2
+ module Actor
3
+
4
+ require 'set'
5
+
6
+ # Core of the actor
7
+ # @note Whole class should be considered private. An user should use {Context}s and {Reference}s only.
8
+ # @note devel: core should not block on anything, e.g. it cannot wait on children to terminate
9
+ # that would eat up all threads in task pool and deadlock
10
+ class Core
11
+ include TypeCheck
12
+ include Concurrent::Logging
13
+ include Synchronization
14
+
15
+ # @!attribute [r] reference
16
+ # @return [Reference] reference to this actor which can be safely passed around
17
+ # @!attribute [r] name
18
+ # @return [String] the name of this instance, it should be uniq (not enforced right now)
19
+ # @!attribute [r] path
20
+ # @return [String] a path of this actor. It is used for easier orientation and logging.
21
+ # Path is constructed recursively with: `parent.path + self.name` up to a {Actor.root},
22
+ # e.g. `/an_actor/its_child`.
23
+ # (It will also probably form a supervision path (failures will be reported up to parents)
24
+ # in future versions.)
25
+ # @!attribute [r] executor
26
+ # @return [Executor] which is used to process messages
27
+ # @!attribute [r] actor_class
28
+ # @return [Context] a class including {Context} representing Actor's behaviour
29
+ attr_reader :reference, :name, :path, :executor, :context_class, :context, :behaviour_definition
30
+
31
+ # @option opts [String] name
32
+ # @option opts [Reference, nil] parent of an actor spawning this one
33
+ # @option opts [Class] reference a custom descendant of {Reference} to use
34
+ # @option opts [Context] actor_class a class to be instantiated defining Actor's behaviour
35
+ # @option opts [Array<Object>] args arguments for actor_class instantiation
36
+ # @option opts [Executor] executor, default is `Concurrent.configuration.global_task_pool`
37
+ # @option opts [true, false] link, atomically link the actor to its parent
38
+ # @option opts [true, false] supervise, atomically supervise the actor by its parent
39
+ # @option opts [Array<Array(Behavior::Abstract, Array<Object>)>] behaviour_definition, array of pairs
40
+ # where each pair is behaviour class and its args, see {Behaviour.basic_behaviour}
41
+ # @option opts [IVar, nil] initialized, if present it'll be set or failed after {Context} initialization
42
+ # @option opts [Proc, nil] logger a proc accepting (level, progname, message = nil, &block) params,
43
+ # can be used to hook actor instance to any logging system
44
+ # @param [Proc] block for class instantiation
45
+ def initialize(opts = {}, &block)
46
+ synchronize do
47
+ @mailbox = Array.new
48
+ @serialized_execution = SerializedExecution.new
49
+ @executor = Type! opts.fetch(:executor, Concurrent.configuration.global_task_pool), Executor
50
+ @children = Set.new
51
+ @context_class = Child! opts.fetch(:class), AbstractContext
52
+ allocate_context
53
+ @reference = (Child! opts[:reference_class] || @context.default_reference_class, Reference).new self
54
+ @name = (Type! opts.fetch(:name), String, Symbol).to_s
55
+
56
+ parent = opts[:parent]
57
+ @parent_core = (Type! parent, Reference, NilClass) && parent.send(:core)
58
+ if @parent_core.nil? && @name != '/'
59
+ raise 'only root has no parent'
60
+ end
61
+
62
+ @path = @parent_core ? File.join(@parent_core.path, @name) : @name
63
+ @logger = opts[:logger]
64
+
65
+ @parent_core.add_child reference if @parent_core
66
+
67
+ initialize_behaviours opts
68
+
69
+ @args = opts.fetch(:args, [])
70
+ @block = block
71
+ initialized = Type! opts[:initialized], IVar, NilClass
72
+
73
+ messages = []
74
+ messages << :link if opts[:link]
75
+ messages << :supervise if opts[:supervise]
76
+
77
+ schedule_execution do
78
+ begin
79
+ build_context
80
+
81
+ messages.each do |message|
82
+ handle_envelope Envelope.new(message, nil, parent, reference)
83
+ end
84
+
85
+ initialized.set true if initialized
86
+ rescue => ex
87
+ log ERROR, ex
88
+ @first_behaviour.terminate!
89
+ initialized.fail ex if initialized
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # @return [Reference, nil] of parent actor
96
+ def parent
97
+ @parent_core && @parent_core.reference
98
+ end
99
+
100
+ # @see AbstractContext#dead_letter_routing
101
+ def dead_letter_routing
102
+ @context.dead_letter_routing
103
+ end
104
+
105
+ # @return [Array<Reference>] of children actors
106
+ def children
107
+ guard!
108
+ @children.to_a
109
+ end
110
+
111
+ # @api private
112
+ def add_child(child)
113
+ guard!
114
+ Type! child, Reference
115
+ @children.add child
116
+ nil
117
+ end
118
+
119
+ # @api private
120
+ def remove_child(child)
121
+ guard!
122
+ Type! child, Reference
123
+ @children.delete child
124
+ nil
125
+ end
126
+
127
+ # is executed by Reference scheduling processing of new messages
128
+ # can be called from other alternative Reference implementations
129
+ # @param [Envelope] envelope
130
+ def on_envelope(envelope)
131
+ schedule_execution { handle_envelope envelope }
132
+ nil
133
+ end
134
+
135
+ # ensures that we are inside of the executor
136
+ def guard!
137
+ unless Actor.current == reference
138
+ raise "can be called only inside actor #{reference} but was #{Actor.current}"
139
+ end
140
+ end
141
+
142
+ def log(level, message = nil, &block)
143
+ super level, @path, message, &block
144
+ end
145
+
146
+ # Schedules blocks to be executed on executor sequentially,
147
+ # sets Actress.current
148
+ def schedule_execution
149
+ @serialized_execution.post(@executor) do
150
+ synchronize do
151
+ begin
152
+ Thread.current[:__current_actor__] = reference
153
+ yield
154
+ rescue => e
155
+ log FATAL, e
156
+ ensure
157
+ Thread.current[:__current_actor__] = nil
158
+ end
159
+ end
160
+ end
161
+
162
+ nil
163
+ end
164
+
165
+ def broadcast(event)
166
+ @first_behaviour.on_event(event)
167
+ end
168
+
169
+ # @param [Class] behaviour_class
170
+ # @return [Behaviour::Abstract, nil] based on behaviour_class
171
+ def behaviour(behaviour_class)
172
+ @behaviours[behaviour_class]
173
+ end
174
+
175
+ # @param [Class] behaviour_class
176
+ # @return [Behaviour::Abstract] based on behaviour_class
177
+ # @raise [KeyError] when no behaviour
178
+ def behaviour!(behaviour_class)
179
+ @behaviours.fetch behaviour_class
180
+ end
181
+
182
+ # @api private
183
+ def allocate_context
184
+ @context = @context_class.allocate
185
+ end
186
+
187
+ # @api private
188
+ def build_context
189
+ @context.send :initialize_core, self
190
+ @context.send :initialize, *@args, &@block
191
+ end
192
+
193
+ private
194
+
195
+ def handle_envelope(envelope)
196
+ log DEBUG, "received #{envelope.message.inspect} from #{envelope.sender}"
197
+ @first_behaviour.on_envelope envelope
198
+ end
199
+
200
+ def initialize_behaviours(opts)
201
+ @behaviour_definition = (Type! opts[:behaviour_definition] || @context.behaviour_definition, Array).each do |v|
202
+ Type! v, Array
203
+ Match! v.size, 2
204
+ Child! v[0], Behaviour::Abstract
205
+ Type! v[1], Array
206
+ end
207
+ @behaviours = {}
208
+ @first_behaviour = @behaviour_definition.reverse.
209
+ reduce(nil) { |last, (behaviour, args)| @behaviours[behaviour] = behaviour.new(self, last, *args) }
210
+ end
211
+ end
212
+ end
213
+ end