concurrent-ruby 0.6.0.pre.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -8
  3. data/lib/concurrent.rb +2 -0
  4. data/lib/concurrent/actor/actor.rb +3 -3
  5. data/lib/concurrent/actor/postable.rb +1 -1
  6. data/lib/concurrent/actors.rb +0 -3
  7. data/lib/concurrent/actress.rb +75 -0
  8. data/lib/concurrent/actress/ad_hoc.rb +14 -0
  9. data/lib/concurrent/actress/context.rb +96 -0
  10. data/lib/concurrent/actress/core.rb +204 -0
  11. data/lib/concurrent/actress/core_delegations.rb +37 -0
  12. data/lib/concurrent/actress/doc.md +53 -0
  13. data/lib/concurrent/actress/envelope.rb +25 -0
  14. data/lib/concurrent/actress/errors.rb +14 -0
  15. data/lib/concurrent/actress/reference.rb +64 -0
  16. data/lib/concurrent/actress/type_check.rb +48 -0
  17. data/lib/concurrent/agent.rb +20 -11
  18. data/lib/concurrent/async.rb +54 -25
  19. data/lib/concurrent/atomic/atomic.rb +48 -0
  20. data/lib/concurrent/atomic/atomic_boolean.rb +13 -13
  21. data/lib/concurrent/atomic/atomic_fixnum.rb +9 -17
  22. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +7 -4
  23. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +16 -14
  24. data/lib/concurrent/atomic/event.rb +11 -16
  25. data/lib/concurrent/atomics.rb +1 -0
  26. data/lib/concurrent/channel/channel.rb +4 -2
  27. data/lib/concurrent/collection/blocking_ring_buffer.rb +1 -1
  28. data/lib/concurrent/configuration.rb +59 -47
  29. data/lib/concurrent/delay.rb +28 -12
  30. data/lib/concurrent/dereferenceable.rb +6 -6
  31. data/lib/concurrent/errors.rb +30 -0
  32. data/lib/concurrent/executor/executor.rb +11 -4
  33. data/lib/concurrent/executor/immediate_executor.rb +1 -0
  34. data/lib/concurrent/executor/java_thread_pool_executor.rb +4 -0
  35. data/lib/concurrent/executor/one_by_one.rb +24 -12
  36. data/lib/concurrent/executor/per_thread_executor.rb +1 -0
  37. data/lib/concurrent/executor/ruby_single_thread_executor.rb +2 -1
  38. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +7 -2
  39. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +3 -0
  40. data/lib/concurrent/executor/timer_set.rb +1 -1
  41. data/lib/concurrent/future.rb +0 -2
  42. data/lib/concurrent/ivar.rb +31 -6
  43. data/lib/concurrent/logging.rb +17 -0
  44. data/lib/concurrent/mvar.rb +45 -0
  45. data/lib/concurrent/obligation.rb +61 -20
  46. data/lib/concurrent/observable.rb +7 -0
  47. data/lib/concurrent/promise.rb +1 -0
  48. data/lib/concurrent/runnable.rb +2 -2
  49. data/lib/concurrent/supervisor.rb +1 -2
  50. data/lib/concurrent/timer_task.rb +17 -13
  51. data/lib/concurrent/tvar.rb +113 -73
  52. data/lib/concurrent/utility/processor_count.rb +141 -116
  53. data/lib/concurrent/utility/timeout.rb +4 -5
  54. data/lib/concurrent/version.rb +1 -1
  55. data/spec/concurrent/actor/postable_shared.rb +1 -1
  56. data/spec/concurrent/actress_spec.rb +191 -0
  57. data/spec/concurrent/async_spec.rb +35 -3
  58. data/spec/concurrent/atomic/atomic_boolean_spec.rb +1 -1
  59. data/spec/concurrent/atomic/atomic_fixnum_spec.rb +1 -1
  60. data/spec/concurrent/atomic/atomic_spec.rb +133 -0
  61. data/spec/concurrent/atomic/count_down_latch_spec.rb +1 -1
  62. data/spec/concurrent/collection/priority_queue_spec.rb +1 -1
  63. data/spec/concurrent/configuration_spec.rb +5 -2
  64. data/spec/concurrent/delay_spec.rb +14 -0
  65. data/spec/concurrent/exchanger_spec.rb +4 -9
  66. data/spec/concurrent/executor/java_cached_thread_pool_spec.rb +1 -1
  67. data/spec/concurrent/executor/java_fixed_thread_pool_spec.rb +1 -1
  68. data/spec/concurrent/executor/java_single_thread_executor_spec.rb +1 -1
  69. data/spec/concurrent/executor/java_thread_pool_executor_spec.rb +1 -1
  70. data/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb +4 -4
  71. data/spec/concurrent/ivar_spec.rb +2 -2
  72. data/spec/concurrent/obligation_spec.rb +113 -24
  73. data/spec/concurrent/observable_shared.rb +4 -0
  74. data/spec/concurrent/observable_spec.rb +8 -3
  75. data/spec/concurrent/runnable_spec.rb +2 -2
  76. data/spec/concurrent/scheduled_task_spec.rb +1 -0
  77. data/spec/concurrent/supervisor_spec.rb +26 -11
  78. data/spec/concurrent/timer_task_spec.rb +36 -35
  79. data/spec/concurrent/tvar_spec.rb +1 -1
  80. data/spec/concurrent/utility/timer_spec.rb +8 -8
  81. data/spec/spec_helper.rb +8 -18
  82. data/spec/support/example_group_extensions.rb +48 -0
  83. metadata +23 -16
  84. data/lib/concurrent/actor/actor_context.rb +0 -77
  85. data/lib/concurrent/actor/actor_ref.rb +0 -67
  86. data/lib/concurrent/actor/simple_actor_ref.rb +0 -94
  87. data/lib/concurrent_ruby_ext.bundle +0 -0
  88. data/spec/concurrent/actor/actor_context_spec.rb +0 -29
  89. data/spec/concurrent/actor/actor_ref_shared.rb +0 -263
  90. data/spec/concurrent/actor/simple_actor_ref_spec.rb +0 -135
  91. data/spec/support/functions.rb +0 -25
@@ -0,0 +1,37 @@
1
+ module Concurrent
2
+ module Actress
3
+
4
+ # Provides publicly expose-able methods from {Core}.
5
+ module CoreDelegations
6
+ def name
7
+ core.name
8
+ end
9
+
10
+ def path
11
+ core.path
12
+ end
13
+
14
+ def parent
15
+ core.parent
16
+ end
17
+
18
+ def terminated?
19
+ core.terminated?
20
+ end
21
+
22
+ def terminated
23
+ core.terminated
24
+ end
25
+
26
+ def reference
27
+ core.reference
28
+ end
29
+
30
+ def executor
31
+ core.executor
32
+ end
33
+
34
+ alias_method :ref, :reference
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ # Light-weighted implement of Actors. Inspired by Akka and Erlang.
2
+
3
+ Actors are using a thread-pool by default which makes them very cheap to create and discard.
4
+ Thousands of actors can be created allowing to brake the program to small maintainable pieces
5
+ without breaking single responsibility principles.
6
+
7
+ ## Quick example
8
+
9
+ class Counter
10
+ include Context
11
+
12
+ def initialize(initial_value)
13
+ @count = initial_value
14
+ end
15
+
16
+ def on_message(message)
17
+ case message
18
+ when Integer
19
+ @count += message
20
+ when :terminate
21
+ terminate!
22
+ else
23
+ raise 'unknown'
24
+ end
25
+ end
26
+ end
27
+
28
+ # create new actor
29
+ counter = Counter.spawn(:test_counter, 5) # => a Reference
30
+
31
+ # send messages
32
+ counter.tell(1) # => counter
33
+ counter << 1 # => counter
34
+
35
+ # send messages getting an IVar back for synchronization
36
+ counter.ask(0) # => an ivar
37
+ counter.ask(0).value # => 7
38
+
39
+ # terminate the actor
40
+ counter.ask(:terminate).wait
41
+ counter.terminated? # => true
42
+ counter.ask(5).wait.rejected? # => true
43
+
44
+ # failure on message processing will terminate the actor
45
+ counter = Counter.spawn(:test_counter, 0)
46
+ counter.ask('boom').wait.rejected? # => true
47
+ counter.terminated? # => true
48
+
49
+
50
+
51
+
52
+
53
+
@@ -0,0 +1,25 @@
1
+ module Concurrent
2
+ module Actress
3
+ Envelope = Struct.new :message, :ivar, :sender do
4
+ include TypeCheck
5
+
6
+ def initialize(message, ivar, sender)
7
+ super message,
8
+ (Type! ivar, IVar, NilClass),
9
+ (Type! sender, Reference, Thread)
10
+ end
11
+
12
+ def sender_path
13
+ if sender.is_a? Reference
14
+ sender.path
15
+ else
16
+ sender.to_s
17
+ end
18
+ end
19
+
20
+ def reject!(error)
21
+ ivar.fail error unless ivar.nil?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ module Concurrent
2
+ module Actress
3
+ Error = Class.new(StandardError)
4
+
5
+ class ActressTerminated < Error
6
+ include TypeCheck
7
+
8
+ def initialize(reference)
9
+ Type! reference, Reference
10
+ super reference.path
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,64 @@
1
+ module Concurrent
2
+ module Actress
3
+
4
+ # Reference is public interface of Actor instances. It is used for sending messages and can
5
+ # be freely passed around the program. It also provides some basic information about the actor
6
+ # see {CoreDelegations}
7
+ class Reference
8
+ include TypeCheck
9
+ include CoreDelegations
10
+
11
+ attr_reader :core
12
+ private :core
13
+
14
+ # @!visibility private
15
+ def initialize(core)
16
+ @core = Type! core, Core
17
+ end
18
+
19
+ # tells message to the actor
20
+ # @param [Object] message
21
+ # @return [Reference] self
22
+ def tell(message)
23
+ message message, nil
24
+ end
25
+
26
+ alias_method :<<, :tell
27
+
28
+ # tells message to the actor
29
+ # @param [Object] message
30
+ # @param [Ivar] ivar to be fulfilled be message's processing result
31
+ # @return [IVar] supplied ivar
32
+ def ask(message, ivar = IVar.new)
33
+ message message, ivar
34
+ end
35
+
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
38
+ # @param [Object] message
39
+ # @param [Ivar] ivar to be fulfilled be message's processing result
40
+ # @return [Object] message's processing result
41
+ # @raise [Exception] ivar.reason if ivar is #rejected?
42
+ def ask!(message, ivar = IVar.new)
43
+ ask(message, ivar).value!
44
+ end
45
+
46
+ # behaves as #tell when no ivar and as #ask when ivar
47
+ def message(message, ivar = nil)
48
+ core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current)
49
+ return ivar || self
50
+ end
51
+
52
+ def to_s
53
+ "#<#{self.class} #{path}>"
54
+ end
55
+
56
+ alias_method :inspect, :to_s
57
+
58
+ def ==(other)
59
+ Type? other, self.class and other.send(:core) == core
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,48 @@
1
+ module Concurrent
2
+ module Actress
3
+
4
+ # taken from Algebrick
5
+ # supplies type-checking helpers whenever included
6
+ module TypeCheck
7
+
8
+ def Type?(value, *types)
9
+ types.any? { |t| value.is_a? t }
10
+ end
11
+
12
+ def Type!(value, *types)
13
+ Type?(value, *types) or
14
+ TypeCheck.error(value, 'is not', types)
15
+ value
16
+ end
17
+
18
+ def Match?(value, *types)
19
+ types.any? { |t| t === value }
20
+ end
21
+
22
+ def Match!(value, *types)
23
+ Match?(value, *types) or
24
+ TypeCheck.error(value, 'is not matching', types)
25
+ value
26
+ end
27
+
28
+ def Child?(value, *types)
29
+ Type?(value, Class) &&
30
+ types.any? { |t| value <= t }
31
+ end
32
+
33
+ def Child!(value, *types)
34
+ Child?(value, *types) or
35
+ TypeCheck.error(value, 'is not child', types)
36
+ value
37
+ end
38
+
39
+ private
40
+
41
+ def self.error(value, message, types)
42
+ raise TypeError,
43
+ "Value (#{value.class}) '#{value}' #{message} any of: #{types.join('; ')}."
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -4,6 +4,7 @@ require 'concurrent/dereferenceable'
4
4
  require 'concurrent/observable'
5
5
  require 'concurrent/options_parser'
6
6
  require 'concurrent/utility/timeout'
7
+ require 'concurrent/logging'
7
8
 
8
9
  module Concurrent
9
10
 
@@ -35,6 +36,7 @@ module Concurrent
35
36
  class Agent
36
37
  include Dereferenceable
37
38
  include Concurrent::Observable
39
+ include Logging
38
40
 
39
41
  # The default timeout value (in seconds); used when no timeout option
40
42
  # is given at initialization
@@ -113,10 +115,14 @@ module Concurrent
113
115
  # @yieldparam [Object] value the result of the last update operation
114
116
  # @yieldreturn [Boolean] true if the value is valid else false
115
117
  def validate(&block)
118
+
116
119
  unless block.nil?
117
- mutex.lock
118
- @validator = block
119
- mutex.unlock
120
+ begin
121
+ mutex.lock
122
+ @validator = block
123
+ ensure
124
+ mutex.unlock
125
+ end
120
126
  end
121
127
  self
122
128
  end
@@ -188,7 +194,8 @@ module Concurrent
188
194
  end
189
195
  rescuer.block.call(ex) if rescuer
190
196
  rescue Exception => ex
191
- # supress
197
+ # suppress
198
+ log DEBUG, ex
192
199
  end
193
200
 
194
201
  # @!visibility private
@@ -196,7 +203,6 @@ module Concurrent
196
203
  validator, value = mutex.synchronize { [@validator, @value] }
197
204
 
198
205
  begin
199
- # FIXME creates second thread
200
206
  result, valid = Concurrent::timeout(@timeout) do
201
207
  result = handler.call(value)
202
208
  [result, validator.call(result)]
@@ -205,12 +211,15 @@ module Concurrent
205
211
  exception = ex
206
212
  end
207
213
 
208
- mutex.lock
209
- should_notify = if !exception && valid
210
- @value = result
211
- true
212
- end
213
- mutex.unlock
214
+ begin
215
+ mutex.lock
216
+ should_notify = if !exception && valid
217
+ @value = result
218
+ true
219
+ end
220
+ ensure
221
+ mutex.unlock
222
+ end
214
223
 
215
224
  if should_notify
216
225
  time = Time.now
@@ -1,5 +1,7 @@
1
1
  require 'thread'
2
2
  require 'concurrent/configuration'
3
+ require 'concurrent/delay'
4
+ require 'concurrent/errors'
3
5
  require 'concurrent/ivar'
4
6
  require 'concurrent/future'
5
7
  require 'concurrent/executor/thread_pool_executor'
@@ -36,6 +38,10 @@ module Concurrent
36
38
  # class Echo
37
39
  # include Concurrent::Async
38
40
  #
41
+ # def initialize
42
+ # init_mutex # initialize the internal synchronization objects
43
+ # end
44
+ #
39
45
  # def echo(msg)
40
46
  # sleep(rand)
41
47
  # print "#{msg}\n"
@@ -52,6 +58,7 @@ module Concurrent
52
58
  # @example Monkey-patching an existing object
53
59
  # numbers = 1_000_000.times.collect{ rand }
54
60
  # numbers.extend(Concurrent::Async)
61
+ # numbers.init_mutex # initialize the internal synchronization objects
55
62
  #
56
63
  # future = numbers.async.max
57
64
  # future.state #=> :pending
@@ -61,6 +68,16 @@ module Concurrent
61
68
  # future.state #=> :fulfilled
62
69
  # future.value #=> 0.999999138918843
63
70
  #
71
+ # @note This module depends on several internal synchronization objects that
72
+ # must be initialized prior to calling any of the async/await/executor methods.
73
+ # The best practice is to call `init_mutex` from within the constructor
74
+ # of the including class. A less ideal but acceptable practice is for the
75
+ # thread creating the asynchronous object to explicitly call the `init_mutex`
76
+ # method prior to calling any of the async/await/executor methods. If
77
+ # `init_mutex` is *not* called explicitly the async/await/executor methods
78
+ # will raize a `Concurrent::InitializationError`. This is the only way
79
+ # thread-safe initialization can be guaranteed.
80
+ #
64
81
  # @note Thread safe guarantees can only be made when asynchronous method calls
65
82
  # are not mixed with synchronous method calls. Use only synchronous calls
66
83
  # when the object is used exclusively on a single thread. Use only
@@ -142,7 +159,7 @@ module Concurrent
142
159
  ivar = Concurrent::IVar.new
143
160
  value, reason = nil, nil
144
161
  begin
145
- mutex.synchronize do
162
+ @mutex.synchronize do
146
163
  value = @delegate.send(method, *args, &block)
147
164
  end
148
165
  rescue => reason
@@ -154,11 +171,6 @@ module Concurrent
154
171
 
155
172
  self.send(method, *args)
156
173
  end
157
-
158
- # The lock used when delegating methods to the wrapped object.
159
- #
160
- # @!visibility private
161
- attr_reader :mutex # :nodoc:
162
174
  end
163
175
 
164
176
  # Delegates asynchronous, thread-safe method calls to the wrapped object.
@@ -194,8 +206,8 @@ module Concurrent
194
206
 
195
207
  self.define_singleton_method(method) do |*args|
196
208
  Async::validate_argc(@delegate, method, *args)
197
- Concurrent::Future.execute(executor: @executor) do
198
- mutex.synchronize do
209
+ Concurrent::Future.execute(executor: @executor.value) do
210
+ @mutex.synchronize do
199
211
  @delegate.send(method, *args, &block)
200
212
  end
201
213
  end
@@ -203,13 +215,6 @@ module Concurrent
203
215
 
204
216
  self.send(method, *args)
205
217
  end
206
-
207
- private
208
-
209
- # The lock used when delegating methods to the wrapped object.
210
- #
211
- # @!visibility private
212
- attr_reader :mutex # :nodoc:
213
218
  end
214
219
 
215
220
  # Causes the chained method call to be performed asynchronously on the
@@ -230,17 +235,19 @@ module Concurrent
230
235
  # either `async` or `await`. The mutable nature of Ruby references
231
236
  # (and object orientation in general) prevent any other thread safety
232
237
  # guarantees. Do NOT mix non-protected method calls with protected
233
- # method call. Use ONLY protected method calls when sharing the object
238
+ # method call. Use *only* protected method calls when sharing the object
234
239
  # between threads.
235
240
  #
236
241
  # @return [Concurrent::Future] the pending result of the asynchronous operation
237
242
  #
243
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
238
244
  # @raise [NameError] the object does not respond to `method` method
239
245
  # @raise [ArgumentError] the given `args` do not match the arity of `method`
240
246
  #
241
247
  # @see Concurrent::Future
242
248
  def async
243
- @__async_delegator__ ||= AsyncDelegator.new(self, executor, await.mutex)
249
+ raise InitializationError.new('#init_mutex was never called') unless @__async__mutex__
250
+ @__async_delegator__.value
244
251
  end
245
252
  alias_method :future, :async
246
253
 
@@ -262,29 +269,51 @@ module Concurrent
262
269
  # either `async` or `await`. The mutable nature of Ruby references
263
270
  # (and object orientation in general) prevent any other thread safety
264
271
  # guarantees. Do NOT mix non-protected method calls with protected
265
- # method call. Use ONLY protected method calls when sharing the object
272
+ # method call. Use *only* protected method calls when sharing the object
266
273
  # between threads.
267
274
  #
268
275
  # @return [Concurrent::IVar] the completed result of the synchronous operation
269
276
  #
277
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
270
278
  # @raise [NameError] the object does not respond to `method` method
271
279
  # @raise [ArgumentError] the given `args` do not match the arity of `method`
272
280
  #
273
281
  # @see Concurrent::IVar
274
282
  def await
275
- @__await_delegator__ ||= AwaitDelegator.new(self, Mutex.new)
283
+ raise InitializationError.new('#init_mutex was never called') unless @__async__mutex__
284
+ @__await_delegator__.value
276
285
  end
277
286
  alias_method :delay, :await
278
287
 
288
+ # Set a new executor
289
+ #
290
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
291
+ # @raise [ArgumentError] executor has already been set
279
292
  def executor=(executor)
280
- raise ArgumentError.new('executor has already been set') unless @__async__executor__.nil?
281
- @__async__executor__ = executor
293
+ raise InitializationError.new('#init_mutex was never called') unless @__async__mutex__
294
+ @__async__executor__.reconfigure { executor } or
295
+ raise ArgumentError.new('executor has already been set')
282
296
  end
283
297
 
284
- private
285
-
286
- def executor
287
- @__async__executor__ ||= Concurrent.configuration.global_task_pool
298
+ # Initialize the internal mutex and other synchronization objects. This method
299
+ # *must* be called from the constructor of the including class or explicitly
300
+ # by the caller prior to calling any other methods. If `init_mutex` is *not*
301
+ # called explicitly the async/await/executor methods will raize a
302
+ # `Concurrent::InitializationError`. This is the only way thread-safe
303
+ # initialization can be guaranteed.
304
+ #
305
+ # @note This method *must* be called from the constructor of the including
306
+ # class or explicitly by the caller prior to calling any other methods.
307
+ # This is the only way thread-safe initialization can be guaranteed.
308
+ #
309
+ # @raise [Concurrent::InitializationError] when called more than once
310
+ def init_mutex
311
+ raise InitializationError.new('#init_mutex was already called') if @__async__mutex__
312
+ (@__async__mutex__ = Mutex.new).lock
313
+ @__async__executor__ = Delay.new{ Concurrent.configuration.global_operation_pool }
314
+ @__await_delegator__ = Delay.new{ AwaitDelegator.new(self, @__async__mutex__) }
315
+ @__async_delegator__ = Delay.new{ AsyncDelegator.new(self, @__async__executor__, @__async__mutex__) }
316
+ @__async__mutex__.unlock
288
317
  end
289
318
  end
290
319
  end