concurrent-ruby 1.0.0.pre4 → 1.0.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -1
  3. data/lib/concurrent/agent.rb +5 -1
  4. data/lib/concurrent/async.rb +77 -38
  5. data/lib/concurrent/atom.rb +2 -1
  6. data/lib/concurrent/atomic/atomic_boolean.rb +1 -1
  7. data/lib/concurrent/atomic/atomic_fixnum.rb +1 -1
  8. data/lib/concurrent/atomic/atomic_reference.rb +1 -1
  9. data/lib/concurrent/atomic/semaphore.rb +1 -1
  10. data/lib/concurrent/atomic_reference/jruby+truffle.rb +1 -0
  11. data/lib/concurrent/atomic_reference/jruby.rb +1 -1
  12. data/lib/concurrent/atomic_reference/ruby.rb +1 -1
  13. data/lib/concurrent/concern/dereferenceable.rb +9 -24
  14. data/lib/concurrent/concern/obligation.rb +11 -8
  15. data/lib/concurrent/delay.rb +1 -1
  16. data/lib/concurrent/exchanger.rb +30 -17
  17. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +6 -1
  18. data/lib/concurrent/ivar.rb +1 -1
  19. data/lib/concurrent/lazy_register.rb +11 -8
  20. data/lib/concurrent/map.rb +1 -1
  21. data/lib/concurrent/maybe.rb +6 -3
  22. data/lib/concurrent/mvar.rb +26 -2
  23. data/lib/concurrent/promise.rb +1 -1
  24. data/lib/concurrent/synchronization.rb +3 -0
  25. data/lib/concurrent/synchronization/abstract_lockable_object.rb +1 -20
  26. data/lib/concurrent/synchronization/abstract_object.rb +1 -27
  27. data/lib/concurrent/synchronization/jruby_object.rb +26 -18
  28. data/lib/concurrent/synchronization/lockable_object.rb +29 -16
  29. data/lib/concurrent/synchronization/mri_object.rb +27 -19
  30. data/lib/concurrent/synchronization/object.rb +48 -53
  31. data/lib/concurrent/synchronization/rbx_lockable_object.rb +9 -8
  32. data/lib/concurrent/synchronization/rbx_object.rb +29 -21
  33. data/lib/concurrent/synchronization/volatile.rb +34 -0
  34. data/lib/concurrent/timer_task.rb +0 -1
  35. data/lib/concurrent/tvar.rb +3 -1
  36. data/lib/concurrent/utility/engine.rb +4 -0
  37. data/lib/concurrent/utility/native_extension_loader.rb +6 -3
  38. data/lib/concurrent/version.rb +2 -2
  39. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2c97013c9e44933fb0b71553938a6c582c540234
4
- data.tar.gz: 7c5e9eaa3ff9649b50384c5a68ea39dbe1bacd80
3
+ metadata.gz: ce52e17c1838d75c89822e42bdeee79df25ce25a
4
+ data.tar.gz: 801d6c3194364d5854ecc4d6a00165d5ae604494
5
5
  SHA512:
6
- metadata.gz: 8e138f95c21c8fdc05e9c3c107e5d58029acebef7dbad68a0fe3b6f7e79dd0faee9db8d255deef0871e7bdadf7e9c641a21dd13b88f1528d96d424ddcef7399f
7
- data.tar.gz: 8f860fef518fbd5ea8df55bd0e5da79eabeb3fe48e615cf98e48946c6dcdbd09010b84709678c41e26380c02905b4310fb875d73b8609ab474cd16c4ec27f160
6
+ metadata.gz: 3f170a4801f643440e0aa6d36f1a14f4285c83462b6d3b455ae0674ee3d30d9aa1c706edaf99b60ff158ca0bbf0679e87cab9ed12a9324e4bae4306ea5dd6d5b
7
+ data.tar.gz: 062520ed68fb0f9b7b322fc33c06d2f3cc2aa55d65126de60a79161976544fca78bb9db366c971c74d4b94682db38ad69d714cce9de3de8dba5f9497f5c9dd3d
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  ### Upcoming Release v1.0.0 (TBD)
2
2
 
3
- ## Current Release v1.0.0.pre4 (08 October 2015)
3
+ ## Current Release v1.0.0.pre5 (04 November 2015)
4
+
5
+ * Further updates and improvements to the synchronization layer.
6
+ * Performance and memory usage performance with `Actor` logging.
7
+ * Fixed `ThreadPoolExecutor` task count methods.
8
+ * Improved `Async` performance for both short and long-lived objects.
9
+ * Fixed bug in `LockFreeLinkedSet`.
10
+ * Fixed bug in which `Agent#await` triggered a validation failure.
11
+ * Further `Channel` updates.
12
+
13
+ ### Release v1.0.0.pre4 (08 October 2015)
4
14
 
5
15
  * Adopted a project Code of Conduct
6
16
  * Cleared interpreter warnings
@@ -166,6 +166,7 @@ module Concurrent
166
166
  class Error < StandardError
167
167
  def initialize(message = nil)
168
168
  message ||= 'agent must be restarted before jobs can post'
169
+ super(message)
169
170
  end
170
171
  end
171
172
 
@@ -174,6 +175,7 @@ module Concurrent
174
175
  class ValidationError < Error
175
176
  def initialize(message = nil)
176
177
  message ||= 'invalid value'
178
+ super(message)
177
179
  end
178
180
  end
179
181
 
@@ -545,7 +547,9 @@ module Concurrent
545
547
  new_value = job.action.call(old_value, *job.args)
546
548
  @caller.value = nil
547
549
 
548
- if new_value != AWAIT_FLAG && ns_validate(new_value)
550
+ return if new_value == AWAIT_FLAG
551
+
552
+ if ns_validate(new_value)
549
553
  @current.value = new_value
550
554
  observers.notify_observers(Time.now, old_value, new_value)
551
555
  else
@@ -1,5 +1,6 @@
1
+ require 'concurrent/configuration'
1
2
  require 'concurrent/ivar'
2
- require 'concurrent/executor/single_thread_executor'
3
+ require 'concurrent/synchronization/lockable_object'
3
4
 
4
5
  module Concurrent
5
6
 
@@ -10,14 +11,14 @@ module Concurrent
10
11
  #
11
12
  # A more feature-rich {Concurrent::Actor} is also available when the
12
13
  # capabilities of `Async` are too limited.
13
- #
14
+ #
14
15
  # ```cucumber
15
16
  # Feature:
16
17
  # As a stateful, plain old Ruby class
17
18
  # I want safe, asynchronous behavior
18
19
  # So my long-running methods don't block the main thread
19
20
  # ```
20
- #
21
+ #
21
22
  # The `Async` module is a way to mix simple yet powerful asynchronous
22
23
  # capabilities into any plain old Ruby object or class, turning each object
23
24
  # into a simple Actor. Method calls are processed on a background thread. The
@@ -37,14 +38,13 @@ module Concurrent
37
38
  # send messages to the `gen_server` via the `cast` and `call` methods. Unlike
38
39
  # Erlang's `gen_server`, however, `Async` classes do not support linking or
39
40
  # supervision trees.
40
- #
41
+ #
41
42
  # ## Basic Usage
42
43
  #
43
44
  # When this module is mixed into a class, objects of the class become inherently
44
- # asynchronous. Each object gets its own background thread (specifically,
45
- # `SingleThreadExecutor`) on which to post asynchronous method calls.
46
- # Asynchronous method calls are executed in the background one at a time in
47
- # the order they are received.
45
+ # asynchronous. Each object gets its own background thread on which to post
46
+ # asynchronous method calls. Asynchronous method calls are executed in the
47
+ # background one at a time in the order they are received.
48
48
  #
49
49
  # To create an asynchronous class, simply mix in the `Concurrent::Async` module:
50
50
  #
@@ -200,30 +200,30 @@ module Concurrent
200
200
  #
201
201
  # Class methods which are pure functions are safe. Class methods which modify
202
202
  # class variables should be avoided, for all the reasons listed above.
203
- #
203
+ #
204
204
  # ## An Important Note About Thread Safe Guarantees
205
- #
205
+ #
206
206
  # > Thread safe guarantees can only be made when asynchronous method calls
207
207
  # > are not mixed with direct method calls. Use only direct method calls
208
208
  # > when the object is used exclusively on a single thread. Use only
209
209
  # > `async` and `await` when the object is shared between threads. Once you
210
210
  # > call a method using `async` or `await`, you should no longer call methods
211
211
  # > directly on the object. Use `async` and `await` exclusively from then on.
212
- #
212
+ #
213
213
  # @example
214
- #
214
+ #
215
215
  # class Echo
216
216
  # include Concurrent::Async
217
- #
217
+ #
218
218
  # def echo(msg)
219
219
  # print "#{msg}\n"
220
220
  # end
221
221
  # end
222
- #
222
+ #
223
223
  # horn = Echo.new
224
224
  # horn.echo('zero') # synchronous, not thread-safe
225
225
  # # returns the actual return value of the method
226
- #
226
+ #
227
227
  # horn.async.echo('one') # asynchronous, non-blocking, thread-safe
228
228
  # # returns an IVar in the :pending state
229
229
  #
@@ -231,7 +231,6 @@ module Concurrent
231
231
  # # returns an IVar in the :complete state
232
232
  #
233
233
  # @see Concurrent::Actor
234
- # @see Concurrent::SingleThreadExecutor
235
234
  # @see https://en.wikipedia.org/wiki/Actor_model "Actor Model" at Wikipedia
236
235
  # @see http://www.erlang.org/doc/man/gen_server.html Erlang gen_server
237
236
  # @see http://c2.com/cgi/wiki?LetItCrash "Let It Crash" at http://c2.com/
@@ -299,24 +298,20 @@ module Concurrent
299
298
  # Delegates asynchronous, thread-safe method calls to the wrapped object.
300
299
  #
301
300
  # @!visibility private
302
- class AsyncDelegator
301
+ class AsyncDelegator < Synchronization::LockableObject
302
+ safe_initialization!
303
303
 
304
- # Create a new delegator object wrapping the given delegate,
305
- # protecting it with the given serializer, and executing it on the
306
- # given executor. Block if necessary.
304
+ # Create a new delegator object wrapping the given delegate.
307
305
  #
308
306
  # @param [Object] delegate the object to wrap and delegate method calls to
309
- # @param [Concurrent::ExecutorService] executor the executor on which to execute delegated method calls
310
- # @param [Boolean] blocking will block awaiting result when `true`
311
- def initialize(delegate, executor, blocking)
307
+ def initialize(delegate)
308
+ super()
312
309
  @delegate = delegate
313
- @executor = executor
314
- @blocking = blocking
310
+ @queue = []
311
+ @executor = Concurrent.global_io_executor
315
312
  end
316
313
 
317
- # Delegates method calls to the wrapped object. For performance,
318
- # dynamically defines the given method on the delegator so that
319
- # all future calls to `method` will not be directed here.
314
+ # Delegates method calls to the wrapped object.
320
315
  #
321
316
  # @param [Symbol] method the method being called
322
317
  # @param [Array] args zero or more arguments to the method
@@ -330,19 +325,67 @@ module Concurrent
330
325
  Async::validate_argc(@delegate, method, *args)
331
326
 
332
327
  ivar = Concurrent::IVar.new
333
- @executor.post(args) do |arguments|
328
+ synchronize do
329
+ @queue.push [ivar, method, args, block]
330
+ @executor.post { perform } if @queue.length == 1
331
+ end
332
+
333
+ ivar
334
+ end
335
+
336
+ # Perform all enqueued tasks.
337
+ #
338
+ # This method must be called from within the executor. It must not be
339
+ # called while already running. It will loop until the queue is empty.
340
+ def perform
341
+ loop do
342
+ ivar, method, args, block = synchronize { @queue.first }
343
+ break unless ivar # queue is empty
344
+
334
345
  begin
335
- ivar.set(@delegate.send(method, *arguments, &block))
346
+ ivar.set(@delegate.send(method, *args, &block))
336
347
  rescue => error
337
348
  ivar.fail(error)
338
349
  end
350
+
351
+ synchronize do
352
+ @queue.shift
353
+ return if @queue.empty?
354
+ end
339
355
  end
340
- ivar.wait if @blocking
341
- ivar
342
356
  end
343
357
  end
344
358
  private_constant :AsyncDelegator
345
359
 
360
+ # Delegates synchronous, thread-safe method calls to the wrapped object.
361
+ #
362
+ # @!visibility private
363
+ class AwaitDelegator
364
+
365
+ # Create a new delegator object wrapping the given delegate.
366
+ #
367
+ # @param [AsyncDelegator] delegate the object to wrap and delegate method calls to
368
+ def initialize(delegate)
369
+ @delegate = delegate
370
+ end
371
+
372
+ # Delegates method calls to the wrapped object.
373
+ #
374
+ # @param [Symbol] method the method being called
375
+ # @param [Array] args zero or more arguments to the method
376
+ #
377
+ # @return [IVar] the result of the method call
378
+ #
379
+ # @raise [NameError] the object does not respond to `method` method
380
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
381
+ def method_missing(method, *args, &block)
382
+ ivar = @delegate.send(method, *args, &block)
383
+ ivar.wait
384
+ ivar
385
+ end
386
+ end
387
+ private_constant :AwaitDelegator
388
+
346
389
  # Causes the chained method call to be performed asynchronously on the
347
390
  # object's thread. The delegated method will return a future in the
348
391
  # `:pending` state and the method call will have been scheduled on the
@@ -385,8 +428,6 @@ module Concurrent
385
428
  end
386
429
  alias_method :call, :await
387
430
 
388
- private
389
-
390
431
  # Initialize the internal serializer and other stnchronization mechanisms.
391
432
  #
392
433
  # @note This method *must* be called immediately upon object construction.
@@ -396,10 +437,8 @@ module Concurrent
396
437
  def init_synchronization
397
438
  return self if @__async_initialized__
398
439
  @__async_initialized__ = true
399
- @__async_executor__ = Concurrent::SingleThreadExecutor.new(
400
- fallback_policy: :caller_runs, auto_terminate: true)
401
- @__await_delegator__ = AsyncDelegator.new(self, @__async_executor__, true)
402
- @__async_delegator__ = AsyncDelegator.new(self, @__async_executor__, false)
440
+ @__async_delegator__ = AsyncDelegator.new(self)
441
+ @__await_delegator__ = AwaitDelegator.new(@__async_delegator__)
403
442
  self
404
443
  end
405
444
  end
@@ -111,9 +111,10 @@ module Concurrent
111
111
  #
112
112
  # @raise [ArgumentError] if the validator is not a `Proc` (when given)
113
113
  def initialize(value, opts = {})
114
+ super()
114
115
  @Validator = opts.fetch(:validator, -> v { true })
115
116
  self.observers = Collection::CopyOnNotifyObserverSet.new
116
- super(value)
117
+ self.value = value
117
118
  end
118
119
 
119
120
  # @!method value
@@ -1,5 +1,5 @@
1
1
  require 'concurrent/atomic/mutex_atomic_boolean'
2
- require 'concurrent/utility/native_extension_loader'
2
+ require 'concurrent/synchronization'
3
3
 
4
4
  module Concurrent
5
5
 
@@ -1,5 +1,5 @@
1
1
  require 'concurrent/atomic/mutex_atomic_fixnum'
2
- require 'concurrent/utility/native_extension_loader'
2
+ require 'concurrent/synchronization'
3
3
 
4
4
  module Concurrent
5
5
 
@@ -1,4 +1,4 @@
1
- require 'concurrent/utility/native_extension_loader'
1
+ require 'concurrent/synchronization'
2
2
  require 'concurrent/utility/engine'
3
3
  require 'concurrent/atomic_reference/concurrent_update_error'
4
4
  require 'concurrent/atomic_reference/mutex_atomic'
@@ -1,5 +1,5 @@
1
1
  require 'concurrent/atomic/mutex_semaphore'
2
- require 'concurrent/utility/native_extension_loader'
2
+ require 'concurrent/synchronization'
3
3
 
4
4
  module Concurrent
5
5
 
@@ -0,0 +1 @@
1
+ require 'concurrent/atomic_reference/rbx'
@@ -1,4 +1,4 @@
1
- require 'concurrent/utility/native_extension_loader'
1
+ require 'concurrent/synchronization'
2
2
 
3
3
  if defined?(Concurrent::JavaAtomicReference)
4
4
  require 'concurrent/atomic_reference/direct_update'
@@ -1,5 +1,5 @@
1
1
  if defined? Concurrent::CAtomicReference
2
- require 'concurrent/utility/native_extension_loader'
2
+ require 'concurrent/synchronization'
3
3
  require 'concurrent/atomic_reference/direct_update'
4
4
  require 'concurrent/atomic_reference/numeric_cas_wrapper'
5
5
 
@@ -9,13 +9,17 @@ module Concurrent
9
9
  #
10
10
  # @!macro copy_options
11
11
  module Dereferenceable
12
+ # NOTE: This module is going away in 2.0. In the mean time we need it to
13
+ # play nicely with the synchronization layer. This means that the
14
+ # including class SHOULD be synchronized and it MUST implement a
15
+ # `#synchronize` method. Not doing so will lead to runtime errors.
12
16
 
13
17
  # Return the value this object represents after applying the options specified
14
18
  # by the `#set_deref_options` method.
15
19
  #
16
20
  # @return [Object] the current value of the object
17
21
  def value
18
- mutex.synchronize { apply_deref_options(@value) }
22
+ synchronize { apply_deref_options(@value) }
19
23
  end
20
24
  alias_method :deref, :value
21
25
 
@@ -25,43 +29,24 @@ module Concurrent
25
29
  #
26
30
  # @param [Object] value the new value
27
31
  def value=(value)
28
- mutex.synchronize{ @value = value }
29
- end
30
-
31
- # A mutex lock used for synchronizing thread-safe operations. Methods defined
32
- # by `Dereferenceable` are synchronized using the `Mutex` returned from this
33
- # method. Operations performed by the including class that operate on the
34
- # `@value` instance variable should be locked with this `Mutex`.
35
- #
36
- # @return [Mutex] the synchronization object
37
- def mutex
38
- @mutex
39
- end
40
-
41
- # Initializes the internal `Mutex`.
42
- #
43
- # @note This method *must* be called from within the constructor of the including class.
44
- #
45
- # @see #mutex
46
- def init_mutex(mutex = Mutex.new)
47
- @mutex = mutex
32
+ synchronize{ @value = value }
48
33
  end
49
34
 
50
35
  # @!macro [attach] dereferenceable_set_deref_options
51
36
  # Set the options which define the operations #value performs before
52
37
  # returning data to the caller (dereferencing).
53
- #
38
+ #
54
39
  # @note Most classes that include this module will call `#set_deref_options`
55
40
  # from within the constructor, thus allowing these options to be set at
56
41
  # object creation.
57
- #
42
+ #
58
43
  # @param [Hash] opts the options defining dereference behavior.
59
44
  # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
60
45
  # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
61
46
  # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
62
47
  # the internal value and returning the value returned from the proc
63
48
  def set_deref_options(opts = {})
64
- mutex.synchronize{ ns_set_deref_options(opts) }
49
+ synchronize{ ns_set_deref_options(opts) }
65
50
  end
66
51
 
67
52
  # @!macro dereferenceable_set_deref_options
@@ -9,6 +9,10 @@ module Concurrent
9
9
 
10
10
  module Obligation
11
11
  include Concern::Dereferenceable
12
+ # NOTE: The Dereferenceable module is going away in 2.0. In the mean time
13
+ # we need it to place nicely with the synchronization layer. This means
14
+ # that the including class SHOULD be synchronized and it MUST implement a
15
+ # `#synchronize` method. Not doing so will lead to runtime errors.
12
16
 
13
17
  # Has the obligation been fulfilled?
14
18
  #
@@ -104,7 +108,7 @@ module Concurrent
104
108
  #
105
109
  # @return [Symbol] the current state
106
110
  def state
107
- mutex.synchronize { @state }
111
+ synchronize { @state }
108
112
  end
109
113
 
110
114
  # If an exception was raised during processing this will return the
@@ -113,7 +117,7 @@ module Concurrent
113
117
  #
114
118
  # @return [Exception] the exception raised during processing or `nil`
115
119
  def reason
116
- mutex.synchronize { @reason }
120
+ synchronize { @reason }
117
121
  end
118
122
 
119
123
  # @example allows Obligation to be risen
@@ -132,8 +136,7 @@ module Concurrent
132
136
  end
133
137
 
134
138
  # @!visibility private
135
- def init_obligation(*args)
136
- init_mutex(*args)
139
+ def init_obligation
137
140
  @event = Event.new
138
141
  end
139
142
 
@@ -155,7 +158,7 @@ module Concurrent
155
158
 
156
159
  # @!visibility private
157
160
  def state=(value)
158
- mutex.synchronize { ns_set_state(value) }
161
+ synchronize { ns_set_state(value) }
159
162
  end
160
163
 
161
164
  # Atomic compare and set operation
@@ -163,12 +166,12 @@ module Concurrent
163
166
  #
164
167
  # @param [Symbol] next_state
165
168
  # @param [Symbol] expected_current
166
- #
169
+ #
167
170
  # @return [Boolean] true is state is changed, false otherwise
168
171
  #
169
172
  # @!visibility private
170
173
  def compare_and_set_state(next_state, *expected_current)
171
- mutex.synchronize do
174
+ synchronize do
172
175
  if expected_current.include? @state
173
176
  @state = next_state
174
177
  true
@@ -184,7 +187,7 @@ module Concurrent
184
187
  #
185
188
  # @!visibility private
186
189
  def if_state(*expected_states)
187
- mutex.synchronize do
190
+ synchronize do
188
191
  raise ArgumentError.new('no block given') unless block_given?
189
192
 
190
193
  if expected_states.include? @state