concurrent-ruby 0.6.0.pre.2 → 0.6.0

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 (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