concurrent-ruby 0.7.0.rc1 → 0.7.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/lib/concurrent.rb +2 -1
  4. data/lib/concurrent/actor.rb +104 -0
  5. data/lib/concurrent/{actress → actor}/ad_hoc.rb +2 -3
  6. data/lib/concurrent/actor/behaviour.rb +70 -0
  7. data/lib/concurrent/actor/behaviour/abstract.rb +48 -0
  8. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  9. data/lib/concurrent/actor/behaviour/buffer.rb +54 -0
  10. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  11. data/lib/concurrent/actor/behaviour/executes_context.rb +18 -0
  12. data/lib/concurrent/actor/behaviour/linking.rb +42 -0
  13. data/lib/concurrent/actor/behaviour/pausing.rb +77 -0
  14. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  15. data/lib/concurrent/actor/behaviour/sets_results.rb +36 -0
  16. data/lib/concurrent/actor/behaviour/supervised.rb +58 -0
  17. data/lib/concurrent/actor/behaviour/supervising.rb +34 -0
  18. data/lib/concurrent/actor/behaviour/terminates_children.rb +13 -0
  19. data/lib/concurrent/actor/behaviour/termination.rb +54 -0
  20. data/lib/concurrent/actor/context.rb +153 -0
  21. data/lib/concurrent/actor/core.rb +213 -0
  22. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  23. data/lib/concurrent/{actress → actor}/envelope.rb +1 -1
  24. data/lib/concurrent/actor/errors.rb +27 -0
  25. data/lib/concurrent/actor/internal_delegations.rb +49 -0
  26. data/lib/concurrent/{actress/core_delegations.rb → actor/public_delegations.rb} +11 -13
  27. data/lib/concurrent/{actress → actor}/reference.rb +25 -8
  28. data/lib/concurrent/actor/root.rb +37 -0
  29. data/lib/concurrent/{actress → actor}/type_check.rb +1 -1
  30. data/lib/concurrent/actor/utills.rb +7 -0
  31. data/lib/concurrent/actor/utils/broadcast.rb +36 -0
  32. data/lib/concurrent/actress.rb +2 -224
  33. data/lib/concurrent/agent.rb +10 -12
  34. data/lib/concurrent/atomic.rb +32 -1
  35. data/lib/concurrent/atomic/atomic_boolean.rb +55 -13
  36. data/lib/concurrent/atomic/atomic_fixnum.rb +54 -16
  37. data/lib/concurrent/atomic/synchronization.rb +51 -0
  38. data/lib/concurrent/atomic/thread_local_var.rb +15 -50
  39. data/lib/concurrent/atomic_reference/mutex_atomic.rb +1 -1
  40. data/lib/concurrent/atomic_reference/ruby.rb +15 -0
  41. data/lib/concurrent/atomics.rb +1 -0
  42. data/lib/concurrent/channel/unbuffered_channel.rb +2 -1
  43. data/lib/concurrent/configuration.rb +6 -3
  44. data/lib/concurrent/dataflow.rb +20 -3
  45. data/lib/concurrent/delay.rb +23 -31
  46. data/lib/concurrent/executor/executor.rb +7 -2
  47. data/lib/concurrent/executor/timer_set.rb +1 -1
  48. data/lib/concurrent/future.rb +2 -1
  49. data/lib/concurrent/lazy_register.rb +58 -0
  50. data/lib/concurrent/options_parser.rb +4 -2
  51. data/lib/concurrent/promise.rb +2 -1
  52. data/lib/concurrent/scheduled_task.rb +6 -5
  53. data/lib/concurrent/tvar.rb +6 -10
  54. data/lib/concurrent/utility/processor_count.rb +4 -2
  55. data/lib/concurrent/version.rb +1 -1
  56. data/lib/concurrent_ruby_ext.so +0 -0
  57. metadata +32 -10
  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
@@ -0,0 +1,9 @@
1
+ module Concurrent
2
+ module Actor
3
+ class DefaultDeadLetterHandler < RestartingContext
4
+ def on_message(dead_letter)
5
+ log Logging::INFO, "got dead letter #{dead_letter.inspect}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  module Concurrent
2
- module Actress
2
+ module Actor
3
3
  class Envelope
4
4
  include TypeCheck
5
5
 
@@ -0,0 +1,27 @@
1
+ module Concurrent
2
+ module Actor
3
+ Error = Class.new(StandardError)
4
+
5
+ class ActorTerminated < Error
6
+ include TypeCheck
7
+
8
+ attr_reader :reference
9
+
10
+ def initialize(reference)
11
+ @reference = Type! reference, Reference
12
+ super reference.path
13
+ end
14
+ end
15
+
16
+ class UnknownMessage < Error
17
+ include TypeCheck
18
+
19
+ attr_reader :envelope
20
+
21
+ def initialize(envelope)
22
+ @envelope = Type! envelope, Envelope
23
+ super envelope.message.inspect
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ module Concurrent
2
+ module Actor
3
+ module InternalDelegations
4
+ include PublicDelegations
5
+
6
+ # @see Core#children
7
+ def children
8
+ core.children
9
+ end
10
+
11
+ # @see Core#terminate!
12
+ def terminate!
13
+ behaviour!(Behaviour::Termination).terminate!
14
+ end
15
+
16
+ # delegates to core.log
17
+ # @see Logging#log
18
+ def log(level, message = nil, &block)
19
+ core.log(level, message, &block)
20
+ end
21
+
22
+ # @see AbstractContext#dead_letter_routing
23
+ def dead_letter_routing
24
+ context.dead_letter_routing
25
+ end
26
+
27
+ def redirect(reference, envelope = self.envelope)
28
+ reference.message(envelope.message, envelope.ivar)
29
+ Behaviour::MESSAGE_PROCESSED
30
+ end
31
+
32
+ # @return [AbstractContext]
33
+ def context
34
+ core.context
35
+ end
36
+
37
+ # see Core#behaviour
38
+ def behaviour(behaviour_class)
39
+ core.behaviour(behaviour_class)
40
+ end
41
+
42
+ # see Core#behaviour!
43
+ def behaviour!(behaviour_class)
44
+ core.behaviour!(behaviour_class)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -1,42 +1,40 @@
1
1
  module Concurrent
2
- module Actress
2
+ module Actor
3
3
 
4
4
  # Provides publicly expose-able methods from {Core}.
5
- module CoreDelegations
5
+ module PublicDelegations
6
+ # @see Core#name
6
7
  def name
7
8
  core.name
8
9
  end
9
10
 
11
+ # @see Core#path
10
12
  def path
11
13
  core.path
12
14
  end
13
15
 
16
+ # @see Core#parent
14
17
  def parent
15
18
  core.parent
16
19
  end
17
20
 
18
- def terminated?
19
- core.terminated?
20
- end
21
-
22
- def terminated
23
- core.terminated
24
- end
25
-
21
+ # @see Core#reference
26
22
  def reference
27
23
  core.reference
28
24
  end
29
25
 
26
+ # @see Core#executor
30
27
  def executor
31
28
  core.executor
32
29
  end
33
30
 
34
- def actor_class
35
- core.actor_class
31
+ # @see Core#context_class
32
+ def context_class
33
+ core.context_class
36
34
  end
37
35
 
38
36
  alias_method :ref, :reference
39
- alias_method :actress_class, :actor_class
37
+ alias_method :actor_class, :context_class
40
38
  end
41
39
  end
42
40
  end
@@ -1,12 +1,12 @@
1
1
  module Concurrent
2
- module Actress
2
+ module Actor
3
3
 
4
4
  # Reference is public interface of Actor instances. It is used for sending messages and can
5
5
  # be freely passed around the program. It also provides some basic information about the actor,
6
- # see {CoreDelegations}.
6
+ # see {PublicDelegations}.
7
7
  class Reference
8
8
  include TypeCheck
9
- include CoreDelegations
9
+ include PublicDelegations
10
10
 
11
11
  attr_reader :core
12
12
  private :core
@@ -16,7 +16,7 @@ module Concurrent
16
16
  @core = Type! core, Core
17
17
  end
18
18
 
19
- # tells message to the actor
19
+ # tells message to the actor, returns immediately
20
20
  # @param [Object] message
21
21
  # @return [Reference] self
22
22
  def tell(message)
@@ -25,7 +25,12 @@ module Concurrent
25
25
 
26
26
  alias_method :<<, :tell
27
27
 
28
- # tells message to the actor
28
+ # @note it's a good practice to use tell whenever possible. Ask should be used only for
29
+ # testing and when it returns very shortly. It can lead to deadlock if all threads in
30
+ # global_task_pool will block on while asking. It's fine to use it form outside of actors and
31
+ # global_task_pool.
32
+ #
33
+ # sends message to the actor and asks for the result of its processing, returns immediately
29
34
  # @param [Object] message
30
35
  # @param [Ivar] ivar to be fulfilled be message's processing result
31
36
  # @return [IVar] supplied ivar
@@ -33,8 +38,12 @@ module Concurrent
33
38
  message message, ivar
34
39
  end
35
40
 
36
- # @note can lead to deadlocks, use only in tests or when you are sure it won't deadlock
37
- # tells message to the actor
41
+ # @note it's a good practice to use tell whenever possible. Ask should be used only for
42
+ # testing and when it returns very shortly. It can lead to deadlock if all threads in
43
+ # global_task_pool will block on while asking. It's fine to use it form outside of actors and
44
+ # global_task_pool.
45
+ #
46
+ # sends message to the actor and asks for the result of its processing, blocks
38
47
  # @param [Object] message
39
48
  # @param [Ivar] ivar to be fulfilled be message's processing result
40
49
  # @return [Object] message's processing result
@@ -45,10 +54,15 @@ module Concurrent
45
54
 
46
55
  # behaves as {#tell} when no ivar and as {#ask} when ivar
47
56
  def message(message, ivar = nil)
48
- core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current, self)
57
+ core.on_envelope Envelope.new(message, ivar, Actor.current || Thread.current, self)
49
58
  return ivar || self
50
59
  end
51
60
 
61
+ # @see AbstractContext#dead_letter_routing
62
+ def dead_letter_routing
63
+ core.dead_letter_routing
64
+ end
65
+
52
66
  def to_s
53
67
  "#<#{self.class} #{path} (#{actor_class})>"
54
68
  end
@@ -58,6 +72,9 @@ module Concurrent
58
72
  def ==(other)
59
73
  Type? other, self.class and other.send(:core) == core
60
74
  end
75
+
76
+ # to avoid confusion with Kernel.spawn
77
+ undef_method :spawn
61
78
  end
62
79
 
63
80
  end
@@ -0,0 +1,37 @@
1
+ module Concurrent
2
+ module Actor
3
+ # implements the root actor
4
+ class Root < AbstractContext
5
+
6
+ def initialize
7
+ # noinspection RubyArgCount
8
+ @dead_letter_router = Core.new(parent: reference,
9
+ class: DefaultDeadLetterHandler,
10
+ supervise: true,
11
+ name: :default_dead_letter_handler).reference
12
+ end
13
+
14
+ # to allow spawning of new actors, spawn needs to be called inside the parent Actor
15
+ def on_message(message)
16
+ case
17
+ when message.is_a?(Array) && message.first == :spawn
18
+ Actor.spawn message[1], &message[2]
19
+ when message == :dead_letter_routing
20
+ @dead_letter_router
21
+ else
22
+ # ignore
23
+ end
24
+ end
25
+
26
+ def dead_letter_routing
27
+ @dead_letter_router
28
+ end
29
+
30
+ def behaviour_definition
31
+ [*Behaviour.base,
32
+ [Behaviour::Supervising, [:reset!, :one_for_one]],
33
+ *Behaviour.user_messages(:just_log)]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
1
  module Concurrent
2
- module Actress
2
+ module Actor
3
3
 
4
4
  # taken from Algebrick
5
5
  # supplies type-checking helpers whenever included
@@ -0,0 +1,7 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Utils
4
+ require 'concurrent/actor/utils/broadcast'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ require 'set'
2
+
3
+ module Concurrent
4
+ module Actor
5
+ module Utils
6
+
7
+ # TODO doc
8
+ class Broadcast < Context
9
+
10
+ def initialize
11
+ @receivers = Set.new
12
+ end
13
+
14
+ def on_message(message)
15
+ case message
16
+ when :subscribe
17
+ @receivers.add envelope.sender
18
+ true
19
+ when :unsubscribe
20
+ @receivers.delete envelope.sender
21
+ true
22
+ when :subscribed?
23
+ @receivers.include? envelope.sender
24
+ else
25
+ filtered_receivers.each { |r| r << message }
26
+ end
27
+ end
28
+
29
+ # override to define different behaviour, filtering etc
30
+ def filtered_receivers
31
+ @receivers
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,225 +1,3 @@
1
- require 'concurrent/configuration'
2
- require 'concurrent/executor/serialized_execution'
3
- require 'concurrent/ivar'
4
- require 'concurrent/logging'
1
+ require 'concurrent/actor'
5
2
 
6
- module Concurrent
7
-
8
- # # Actor model
9
- #
10
- # - Light-weighted.
11
- # - Inspired by Akka and Erlang.
12
- #
13
- # Actors are sharing a thread-pool by default which makes them very cheap to create and discard.
14
- # Thousands of actors can be created, allowing you to break the program into small maintainable pieces,
15
- # without violating the single responsibility principle.
16
- #
17
- # ## What is an actor model?
18
- #
19
- # [Wiki](http://en.wikipedia.org/wiki/Actor_model) says:
20
- # The actor model in computer science is a mathematical model of concurrent computation
21
- # that treats _actors_ as the universal primitives of concurrent digital computation:
22
- # in response to a message that it receives, an actor can make local decisions,
23
- # create more actors, send more messages, and determine how to respond to the next
24
- # message received.
25
- #
26
- # ## Why?
27
- #
28
- # Concurrency is hard this is one of many ways how to simplify the problem.
29
- # It is simpler to reason about actors than about locks (and all their possible states).
30
- #
31
- # ## How to use it
32
- #
33
- # {include:file:doc/actress/quick.out.rb}
34
- #
35
- # ## Messaging
36
- #
37
- # Messages are processed in same order as they are sent by a sender. It may interleaved with
38
- # messages form other senders though. There is also a contract in actor model that
39
- # messages sent between actors should be immutable. Gems like
40
- #
41
- # - [Algebrick](https://github.com/pitr-ch/algebrick) - Typed struct on steroids based on
42
- # algebraic types and pattern matching
43
- # - [Hamster](https://github.com/hamstergem/hamster) - Efficient, Immutable, Thread-Safe
44
- # Collection classes for Ruby
45
- #
46
- # are very useful.
47
- #
48
- # ## Architecture
49
- #
50
- # Actors are running on shared thread poll which allows user to create many actors cheaply.
51
- # Downside is that these actors cannot be directly used to do IO or other blocking operations.
52
- # Blocking operations could starve the `default_task_pool`. However there are two options:
53
- #
54
- # - Create an regular actor which will schedule blocking operations in `global_operation_pool`
55
- # (which is intended for blocking operations) sending results back to self in messages.
56
- # - Create an actor using `global_operation_pool` instead of `global_task_pool`, e.g.
57
- # `AnIOActor.spawn name: :blocking, executor: Concurrent.configuration.global_operation_pool`.
58
- #
59
- # Each actor is composed from 3 objects:
60
- #
61
- # ### {Reference}
62
- # {include:Actress::Reference}
63
- #
64
- # ### {Core}
65
- # {include:Actress::Core}
66
- #
67
- # ### {Context}
68
- # {include:Actress::Context}
69
- #
70
- # ## Speed
71
- #
72
- # Simple benchmark Actress vs Celluloid, the numbers are looking good
73
- # but you know how it is with benchmarks. Source code is in
74
- # `examples/actress/celluloid_benchmark.rb`. It sends numbers between x actors
75
- # and adding 1 until certain limit is reached.
76
- #
77
- # Benchmark legend:
78
- #
79
- # - mes. - number of messages send between the actors
80
- # - act. - number of actors exchanging the messages
81
- # - impl. - which gem is used
82
- #
83
- # ### JRUBY
84
- #
85
- # Rehearsal --------------------------------------------------------
86
- # 50000 2 actress 24.110000 0.800000 24.910000 ( 7.728000)
87
- # 50000 2 celluloid 28.510000 4.780000 33.290000 ( 14.782000)
88
- # 50000 500 actress 13.700000 0.280000 13.980000 ( 4.307000)
89
- # 50000 500 celluloid 14.520000 11.740000 26.260000 ( 12.258000)
90
- # 50000 1000 actress 10.890000 0.220000 11.110000 ( 3.760000)
91
- # 50000 1000 celluloid 15.600000 21.690000 37.290000 ( 18.512000)
92
- # 50000 1500 actress 10.580000 0.270000 10.850000 ( 3.646000)
93
- # 50000 1500 celluloid 14.490000 29.790000 44.280000 ( 26.043000)
94
- # --------------------------------------------- total: 201.970000sec
95
- #
96
- # mes. act. impl. user system total real
97
- # 50000 2 actress 9.820000 0.510000 10.330000 ( 5.735000)
98
- # 50000 2 celluloid 10.390000 4.030000 14.420000 ( 7.494000)
99
- # 50000 500 actress 9.880000 0.200000 10.080000 ( 3.310000)
100
- # 50000 500 celluloid 12.430000 11.310000 23.740000 ( 11.727000)
101
- # 50000 1000 actress 10.590000 0.190000 10.780000 ( 4.029000)
102
- # 50000 1000 celluloid 14.950000 23.260000 38.210000 ( 20.841000)
103
- # 50000 1500 actress 10.710000 0.250000 10.960000 ( 3.892000)
104
- # 50000 1500 celluloid 13.280000 30.030000 43.310000 ( 24.620000) (1)
105
- #
106
- # ### MRI 2.1.0
107
- #
108
- # Rehearsal --------------------------------------------------------
109
- # 50000 2 actress 4.640000 0.080000 4.720000 ( 4.852390)
110
- # 50000 2 celluloid 6.110000 2.300000 8.410000 ( 7.898069)
111
- # 50000 500 actress 6.260000 2.210000 8.470000 ( 7.400573)
112
- # 50000 500 celluloid 10.250000 4.930000 15.180000 ( 14.174329)
113
- # 50000 1000 actress 6.300000 1.860000 8.160000 ( 7.303162)
114
- # 50000 1000 celluloid 12.300000 7.090000 19.390000 ( 17.962621)
115
- # 50000 1500 actress 7.410000 2.610000 10.020000 ( 8.887396)
116
- # 50000 1500 celluloid 14.850000 10.690000 25.540000 ( 24.489796)
117
- # ---------------------------------------------- total: 99.890000sec
118
- #
119
- # mes. act. impl. user system total real
120
- # 50000 2 actress 4.190000 0.070000 4.260000 ( 4.306386)
121
- # 50000 2 celluloid 6.490000 2.210000 8.700000 ( 8.280051)
122
- # 50000 500 actress 7.060000 2.520000 9.580000 ( 8.518707)
123
- # 50000 500 celluloid 10.550000 4.980000 15.530000 ( 14.699962)
124
- # 50000 1000 actress 6.440000 1.870000 8.310000 ( 7.571059)
125
- # 50000 1000 celluloid 12.340000 7.510000 19.850000 ( 18.793591)
126
- # 50000 1500 actress 6.720000 2.160000 8.880000 ( 7.929630)
127
- # 50000 1500 celluloid 14.140000 10.130000 24.270000 ( 22.775288) (1)
128
- #
129
- # *Note (1):* Celluloid is using thread per actor so this bench is creating about 1500
130
- # native threads. Actress is using constant number of threads.
131
- module Actress
132
-
133
- require 'concurrent/actress/type_check'
134
- require 'concurrent/actress/errors'
135
- require 'concurrent/actress/core_delegations'
136
- require 'concurrent/actress/envelope'
137
- require 'concurrent/actress/reference'
138
- require 'concurrent/actress/core'
139
- require 'concurrent/actress/context'
140
-
141
- require 'concurrent/actress/ad_hoc'
142
-
143
- # @return [Reference, nil] current executing actor if any
144
- def self.current
145
- Thread.current[:__current_actor__]
146
- end
147
-
148
- # implements the root actor
149
- class Root
150
- include Context
151
- # to allow spawning of new actors, spawn needs to be called inside the parent Actor
152
- def on_message(message)
153
- if message.is_a?(Array) && message.first == :spawn
154
- spawn message[1], &message[2]
155
- else
156
- # ignore
157
- end
158
- end
159
- end
160
-
161
- @root = Delay.new { Core.new(parent: nil, name: '/', class: Root).reference }
162
-
163
- # A root actor, a default parent of all actors spawned outside an actor
164
- def self.root
165
- @root.value
166
- end
167
-
168
- # Spawns a new actor.
169
- #
170
- # @example simple
171
- # Actress.spawn(AdHoc, :ping1) { -> message { message } }
172
- #
173
- # @example complex
174
- # Actress.spawn name: :ping3,
175
- # class: AdHoc,
176
- # args: [1]
177
- # executor: Concurrent.configuration.global_task_pool do |add|
178
- # lambda { |number| number + add }
179
- # end
180
- #
181
- # @param block for actress_class instantiation
182
- # @param args see {.spawn_optionify}
183
- # @return [Reference] never the actual actor
184
- def self.spawn(*args, &block)
185
- experimental_acknowledged? or
186
- warn '[EXPERIMENTAL] A full release of `Actress`, renamed `Actor`, is expected in the 0.7.0 release.'
187
-
188
- if Actress.current
189
- Core.new(spawn_optionify(*args).merge(parent: Actress.current), &block).reference
190
- else
191
- root.ask([:spawn, spawn_optionify(*args), block]).value
192
- end
193
- end
194
-
195
- # as {.spawn} but it'll raise when Actor not initialized properly
196
- def self.spawn!(*args, &block)
197
- spawn(spawn_optionify(*args).merge(initialized: ivar = IVar.new), &block).tap { ivar.no_error! }
198
- end
199
-
200
- # @overload spawn_optionify(actress_class, name, *args)
201
- # @param [Context] actress_class to be spawned
202
- # @param [String, Symbol] name of the instance, it's used to generate the {Core#path} of the actor
203
- # @param args for actress_class instantiation
204
- # @overload spawn_optionify(opts)
205
- # see {Core#initialize} opts
206
- def self.spawn_optionify(*args)
207
- if args.size == 1 && args.first.is_a?(Hash)
208
- args.first
209
- else
210
- { class: args[0],
211
- name: args[1],
212
- args: args[2..-1] }
213
- end
214
- end
215
-
216
- # call this to disable experimental warning
217
- def self.i_know_it_is_experimental!
218
- @experimental_acknowledged = true
219
- end
220
-
221
- def self.experimental_acknowledged?
222
- !!@experimental_acknowledged
223
- end
224
- end
225
- end
3
+ Concurrent::Actress = Concurrent::Actor