concurrent-ruby 1.0.0.pre1 → 1.0.0.pre2

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +16 -18
  4. data/lib/concurrent.rb +3 -3
  5. data/lib/concurrent/agent.rb +583 -0
  6. data/lib/concurrent/array.rb +1 -0
  7. data/lib/concurrent/async.rb +236 -111
  8. data/lib/concurrent/atom.rb +101 -46
  9. data/lib/concurrent/atomic/atomic_boolean.rb +2 -0
  10. data/lib/concurrent/atomic/atomic_fixnum.rb +2 -0
  11. data/lib/concurrent/atomic/cyclic_barrier.rb +1 -1
  12. data/lib/concurrent/atomic/event.rb +1 -1
  13. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +1 -1
  14. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +1 -1
  15. data/lib/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  16. data/lib/concurrent/atomic/mutex_semaphore.rb +2 -2
  17. data/lib/concurrent/atomic/read_write_lock.rb +5 -4
  18. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
  19. data/lib/concurrent/atomic/thread_local_var.rb +2 -0
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +1 -1
  21. data/lib/concurrent/atomics.rb +6 -4
  22. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +7 -15
  23. data/lib/concurrent/collection/copy_on_write_observer_set.rb +7 -15
  24. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +5 -0
  25. data/lib/concurrent/concern/observable.rb +38 -13
  26. data/lib/concurrent/configuration.rb +5 -4
  27. data/lib/concurrent/delay.rb +9 -8
  28. data/lib/concurrent/exchanger.rb +2 -0
  29. data/lib/concurrent/executor/abstract_executor_service.rb +2 -2
  30. data/lib/concurrent/executor/java_single_thread_executor.rb +0 -1
  31. data/lib/concurrent/executor/ruby_executor_service.rb +10 -4
  32. data/lib/concurrent/executor/ruby_single_thread_executor.rb +10 -68
  33. data/lib/concurrent/executor/safe_task_executor.rb +7 -8
  34. data/lib/concurrent/executor/serialized_execution.rb +4 -4
  35. data/lib/concurrent/executor/single_thread_executor.rb +20 -10
  36. data/lib/concurrent/executor/timer_set.rb +4 -2
  37. data/lib/concurrent/executors.rb +0 -1
  38. data/lib/concurrent/future.rb +3 -2
  39. data/lib/concurrent/hash.rb +1 -1
  40. data/lib/concurrent/immutable_struct.rb +5 -1
  41. data/lib/concurrent/ivar.rb +1 -1
  42. data/lib/concurrent/mutable_struct.rb +7 -6
  43. data/lib/concurrent/{executor/executor.rb → options.rb} +4 -3
  44. data/lib/concurrent/promise.rb +3 -2
  45. data/lib/concurrent/scheduled_task.rb +3 -2
  46. data/lib/concurrent/settable_struct.rb +5 -4
  47. data/lib/concurrent/synchronization.rb +11 -3
  48. data/lib/concurrent/synchronization/abstract_lockable_object.rb +117 -0
  49. data/lib/concurrent/synchronization/abstract_object.rb +16 -129
  50. data/lib/concurrent/synchronization/abstract_struct.rb +2 -3
  51. data/lib/concurrent/synchronization/condition.rb +6 -4
  52. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  53. data/lib/concurrent/synchronization/{java_object.rb → jruby_object.rb} +5 -3
  54. data/lib/concurrent/synchronization/lock.rb +3 -2
  55. data/lib/concurrent/synchronization/lockable_object.rb +59 -0
  56. data/lib/concurrent/synchronization/mri_lockable_object.rb +71 -0
  57. data/lib/concurrent/synchronization/mri_object.rb +35 -0
  58. data/lib/concurrent/synchronization/object.rb +111 -39
  59. data/lib/concurrent/synchronization/rbx_lockable_object.rb +64 -0
  60. data/lib/concurrent/synchronization/rbx_object.rb +17 -68
  61. data/lib/concurrent/thread_safe/util.rb +0 -9
  62. data/lib/concurrent/thread_safe/util/adder.rb +3 -0
  63. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +3 -1
  64. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +3 -0
  65. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +1 -0
  66. data/lib/concurrent/thread_safe/util/striped64.rb +6 -1
  67. data/lib/concurrent/thread_safe/util/volatile.rb +2 -0
  68. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +2 -0
  69. data/lib/concurrent/tvar.rb +36 -0
  70. data/lib/concurrent/utility/at_exit.rb +1 -1
  71. data/lib/concurrent/utility/monotonic_time.rb +3 -4
  72. data/lib/concurrent/utility/native_extension_loader.rb +1 -1
  73. data/lib/concurrent/version.rb +2 -2
  74. metadata +12 -7
  75. data/lib/concurrent/synchronization/monitor_object.rb +0 -27
  76. data/lib/concurrent/synchronization/mutex_object.rb +0 -43
@@ -1,4 +1,9 @@
1
1
  require 'concurrent/thread_safe/util'
2
+ require 'concurrent/thread_safe/util/adder'
3
+ require 'concurrent/thread_safe/util/cheap_lockable'
4
+ require 'concurrent/thread_safe/util/power_of_two_tuple'
5
+ require 'concurrent/thread_safe/util/volatile'
6
+ require 'concurrent/thread_safe/util/xor_shift_random'
2
7
 
3
8
  module Concurrent
4
9
 
@@ -21,8 +21,8 @@ module Concurrent
21
21
  #
22
22
  # In a multi threaded environment things are more complex. The `subject` must
23
23
  # synchronize the access to its data structure and to do so currently we're
24
- # using two specialized ObserverSet: CopyOnWriteObserverSet and
25
- # CopyOnNotifyObserverSet.
24
+ # using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet}
25
+ # and {Concurrent::Concern::CopyOnNotifyObserverSet}.
26
26
  #
27
27
  # When implementing and `observer` there's a very important rule to remember:
28
28
  # **there are no guarantees about the thread that will execute the callback**
@@ -49,30 +49,55 @@ module Concurrent
49
49
  # or an AtomicFixum)
50
50
  module Observable
51
51
 
52
- # @return [Object] the added observer
53
- def add_observer(*args, &block)
54
- observers.add_observer(*args, &block)
52
+ # @!macro [attach] observable_add_observer
53
+ #
54
+ # Adds an observer to this set. If a block is passed, the observer will be
55
+ # created by this method and no other params should be passed.
56
+ #
57
+ # @param [Object] observer the observer to add
58
+ # @param [Symbol] func the function to call on the observer during notification.
59
+ # Default is :update
60
+ # @return [Object] the added observer
61
+ def add_observer(observer = nil, func = :update, &block)
62
+ observers.add_observer(observer, func, &block)
55
63
  end
56
64
 
57
- # as #add_observer but it can be used for chaining
65
+ # As `#add_observer` but can be used for chaining.
66
+ #
67
+ # @param [Object] observer the observer to add
68
+ # @param [Symbol] func the function to call on the observer during notification.
58
69
  # @return [Observable] self
59
- def with_observer(*args, &block)
60
- add_observer(*args, &block)
70
+ def with_observer(observer = nil, func = :update, &block)
71
+ add_observer(observer, func, &block)
61
72
  self
62
73
  end
63
74
 
64
- # @return [Object] the deleted observer
65
- def delete_observer(*args)
66
- observers.delete_observer(*args)
75
+ # @!macro [attach] observable_delete_observer
76
+ #
77
+ # Remove `observer` as an observer on this object so that it will no
78
+ # longer receive notifications.
79
+ #
80
+ # @param [Object] observer the observer to remove
81
+ # @return [Object] the deleted observer
82
+ def delete_observer(observer)
83
+ observers.delete_observer(observer)
67
84
  end
68
85
 
69
- # @return [Observable] self
86
+ # @!macro [attach] observable_delete_observers
87
+ #
88
+ # Remove all observers associated with this object.
89
+ #
90
+ # @return [Observable] self
70
91
  def delete_observers
71
92
  observers.delete_observers
72
93
  self
73
94
  end
74
95
 
75
- # @return [Integer] the observers count
96
+ # @!macro [attach] observable_count_observers
97
+ #
98
+ # Return the number of observers associated with this object.
99
+ #
100
+ # @return [Integer] the observers count
76
101
  def count_observers
77
102
  observers.count_observers
78
103
  end
@@ -3,16 +3,17 @@ require 'concurrent/delay'
3
3
  require 'concurrent/errors'
4
4
  require 'concurrent/atomic/atomic_reference'
5
5
  require 'concurrent/concern/logging'
6
- require 'concurrent/executor/timer_set'
7
6
  require 'concurrent/executor/immediate_executor'
8
- require 'concurrent/executor/fixed_thread_pool'
9
- require 'concurrent/executor/thread_pool_executor'
10
7
  require 'concurrent/utility/at_exit'
11
8
  require 'concurrent/utility/processor_counter'
12
9
 
13
10
  module Concurrent
14
11
  extend Concern::Logging
15
12
 
13
+ autoload :Options, 'concurrent/options'
14
+ autoload :TimerSet, 'concurrent/executor/timer_set'
15
+ autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor'
16
+
16
17
  # @return [Logger] Logger with provided level and output.
17
18
  def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
18
19
  logger = Logger.new(output)
@@ -125,7 +126,7 @@ module Concurrent
125
126
  # - :immediate - {Concurrent.global_immediate_executor}
126
127
  # @return [Executor]
127
128
  def self.executor(executor_identifier)
128
- Executor.executor(executor_identifier)
129
+ Options.executor(executor_identifier)
129
130
  end
130
131
 
131
132
  def self.new_fast_executor(opts = {})
@@ -1,12 +1,12 @@
1
1
  require 'thread'
2
- require 'concurrent/configuration'
3
2
  require 'concurrent/concern/obligation'
4
- require 'concurrent/executor/executor'
5
3
  require 'concurrent/executor/immediate_executor'
6
4
  require 'concurrent/synchronization'
7
5
 
8
6
  module Concurrent
9
7
 
8
+ autoload :Options, 'concurrent/options'
9
+
10
10
  # Lazy evaluation of a block yielding an immutable result. Useful for
11
11
  # expensive operations that may never be needed. It may be non-blocking,
12
12
  # supports the `Concern::Obligation` interface, and accepts the injection of
@@ -40,7 +40,7 @@ module Concurrent
40
40
  # execute on the given executor, allowing the call to timeout.
41
41
  #
42
42
  # @see Concurrent::Concern::Dereferenceable
43
- class Delay < Synchronization::Object
43
+ class Delay < Synchronization::LockableObject
44
44
  include Concern::Obligation
45
45
 
46
46
  # NOTE: Because the global thread pools are lazy-loaded with these objects
@@ -74,7 +74,7 @@ module Concurrent
74
74
  #
75
75
  # @!macro delay_note_regarding_blocking
76
76
  def value(timeout = nil)
77
- if @task_executor
77
+ if @executor # TODO (pitr 12-Sep-2015): broken unsafe read?
78
78
  super
79
79
  else
80
80
  # this function has been optimized for performance and
@@ -108,7 +108,7 @@ module Concurrent
108
108
  #
109
109
  # @!macro delay_note_regarding_blocking
110
110
  def value!(timeout = nil)
111
- if @task_executor
111
+ if @executor
112
112
  super
113
113
  else
114
114
  result = value
@@ -127,7 +127,7 @@ module Concurrent
127
127
  #
128
128
  # @!macro delay_note_regarding_blocking
129
129
  def wait(timeout = nil)
130
- if @task_executor
130
+ if @executor
131
131
  execute_task_once
132
132
  super(timeout)
133
133
  else
@@ -157,7 +157,7 @@ module Concurrent
157
157
  def ns_initialize(opts, &block)
158
158
  init_obligation(self)
159
159
  set_deref_options(opts)
160
- @task_executor = Executor.executor_from_options(opts)
160
+ @executor = opts[:executor]
161
161
 
162
162
  @task = block
163
163
  @state = :pending
@@ -177,7 +177,8 @@ module Concurrent
177
177
  end
178
178
 
179
179
  if execute
180
- @task_executor.post do
180
+ executor = Options.executor_from_options(executor: @executor)
181
+ executor.post do
181
182
  begin
182
183
  result = task.call
183
184
  success = true
@@ -13,6 +13,8 @@ module Concurrent
13
13
  # pairs. Each thread presents some object on entry to the exchange method,
14
14
  # matches with a partner thread, and receives its partner's object on return.
15
15
  #
16
+ # @!macro thread_safe_variable_comparison
17
+ #
16
18
  # This implementation is very simple, using only a single slot for each
17
19
  # exchanger (unlike more advanced implementations which use an "arena").
18
20
  # This approach will work perfectly fine when there are only a few threads
@@ -1,13 +1,13 @@
1
1
  require 'concurrent/errors'
2
2
  require 'concurrent/executor/executor_service'
3
- require 'concurrent/synchronization/object'
3
+ require 'concurrent/synchronization'
4
4
  require 'concurrent/utility/at_exit'
5
5
 
6
6
  module Concurrent
7
7
 
8
8
  # @!macro abstract_executor_service_public_api
9
9
  # @!visibility private
10
- class AbstractExecutorService < Synchronization::Object
10
+ class AbstractExecutorService < Synchronization::LockableObject
11
11
  include ExecutorService
12
12
 
13
13
  # The set of possible fallback policies that may be set at thread pool creation.
@@ -6,7 +6,6 @@ if Concurrent.on_jruby?
6
6
  module Concurrent
7
7
 
8
8
  # @!macro single_thread_executor
9
- # @!macro thread_pool_options
10
9
  # @!macro abstract_executor_service_public_api
11
10
  # @!visibility private
12
11
  class JavaSingleThreadExecutor < JavaExecutorService
@@ -6,12 +6,12 @@ module Concurrent
6
6
  # @!macro abstract_executor_service_public_api
7
7
  # @!visibility private
8
8
  class RubyExecutorService < AbstractExecutorService
9
+ safe_initialization!
9
10
 
10
11
  def initialize(*args, &block)
11
12
  super
12
- @stop_event = Event.new
13
- @stopped_event = Event.new
14
- ensure_ivar_visibility!
13
+ @StopEvent = Event.new
14
+ @StoppedEvent = Event.new
15
15
  end
16
16
 
17
17
  def post(*args, &task)
@@ -51,7 +51,13 @@ module Concurrent
51
51
 
52
52
  private
53
53
 
54
- attr_reader :stop_event, :stopped_event
54
+ def stop_event
55
+ @StopEvent
56
+ end
57
+
58
+ def stopped_event
59
+ @StoppedEvent
60
+ end
55
61
 
56
62
  def ns_shutdown_execution
57
63
  stopped_event.set
@@ -1,80 +1,22 @@
1
- require 'thread'
2
- require 'concurrent/executor/ruby_executor_service'
3
- require 'concurrent/executor/serial_executor_service'
1
+ require 'concurrent/executor/ruby_thread_pool_executor'
4
2
 
5
3
  module Concurrent
6
4
 
7
5
  # @!macro single_thread_executor
8
- # @!macro thread_pool_options
9
6
  # @!macro abstract_executor_service_public_api
10
7
  # @!visibility private
11
- class RubySingleThreadExecutor < RubyExecutorService
12
- include SerialExecutorService
8
+ class RubySingleThreadExecutor < RubyThreadPoolExecutor
13
9
 
14
10
  # @!macro single_thread_executor_method_initialize
15
11
  def initialize(opts = {})
16
- super
17
- end
18
-
19
- private
20
-
21
- def ns_initialize(opts)
22
- @queue = Queue.new
23
- @thread = nil
24
- @fallback_policy = opts.fetch(:fallback_policy, :discard)
25
- raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
26
- self.auto_terminate = opts.fetch(:auto_terminate, true)
27
- end
28
-
29
- # @!visibility private
30
- def ns_execute(*args, &task)
31
- supervise
32
- @queue << [args, task]
33
- end
34
-
35
- # @!visibility private
36
- def ns_shutdown_execution
37
- @queue << :stop
38
- stopped_event.set unless alive?
39
- end
40
-
41
- # @!visibility private
42
- def ns_kill_execution
43
- @queue.clear
44
- @thread.kill if alive?
45
- end
46
-
47
- # @!visibility private
48
- def alive?
49
- @thread && @thread.alive?
50
- end
51
-
52
- # @!visibility private
53
- def supervise
54
- @thread = new_worker_thread unless alive?
55
- end
56
-
57
- # @!visibility private
58
- def new_worker_thread
59
- Thread.new do
60
- Thread.current.abort_on_exception = false
61
- work
62
- end
63
- end
64
-
65
- # @!visibility private
66
- def work
67
- loop do
68
- task = @queue.pop
69
- break if task == :stop
70
- begin
71
- task.last.call(*task.first)
72
- rescue => ex
73
- # let it fail
74
- log DEBUG, ex
75
- end
76
- end
77
- stopped_event.set
12
+ super(
13
+ min_threads: 1,
14
+ max_threads: 1,
15
+ max_queue: 0,
16
+ idletime: DEFAULT_THREAD_IDLETIMEOUT,
17
+ fallback_policy: opts.fetch(:fallback_policy, :discard),
18
+ auto_terminate: opts.fetch(:auto_terminate, true)
19
+ )
78
20
  end
79
21
  end
80
22
  end
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization/object'
1
+ require 'concurrent/synchronization'
2
2
 
3
3
  module Concurrent
4
4
 
@@ -6,26 +6,25 @@ module Concurrent
6
6
  # success - indicating if the callable has been executed without errors
7
7
  # value - filled by the callable result if it has been executed without errors, nil otherwise
8
8
  # reason - the error risen by the callable if it has been executed with errors, nil otherwise
9
- class SafeTaskExecutor < Synchronization::Object
9
+ class SafeTaskExecutor < Synchronization::LockableObject
10
10
 
11
11
  def initialize(task, opts = {})
12
- super()
13
- @task = task
12
+ @task = task
14
13
  @exception_class = opts.fetch(:rescue_exception, false) ? Exception : StandardError
15
- ensure_ivar_visibility!
14
+ super() # ensures visibility
16
15
  end
17
16
 
18
17
  # @return [Array]
19
18
  def execute(*args)
20
19
  synchronize do
21
20
  success = false
22
- value = reason = nil
21
+ value = reason = nil
23
22
 
24
23
  begin
25
- value = @task.call(*args)
24
+ value = @task.call(*args)
26
25
  success = true
27
26
  rescue @exception_class => ex
28
- reason = ex
27
+ reason = ex
29
28
  success = false
30
29
  end
31
30
 
@@ -1,11 +1,11 @@
1
1
  require 'concurrent/errors'
2
2
  require 'concurrent/concern/logging'
3
- require 'concurrent/synchronization/object'
3
+ require 'concurrent/synchronization'
4
4
 
5
5
  module Concurrent
6
6
 
7
7
  # Ensures passed jobs in a serialized order never running at the same time.
8
- class SerializedExecution < Synchronization::Object
8
+ class SerializedExecution < Synchronization::LockableObject
9
9
  include Concern::Logging
10
10
 
11
11
  def initialize()
@@ -39,8 +39,8 @@ module Concurrent
39
39
  # As {#post} but allows to submit multiple tasks at once, it's guaranteed that they will not
40
40
  # be interleaved by other tasks.
41
41
  #
42
- # @param [Array<Array(Executor, Array<Object>, Proc)>] posts array of triplets where
43
- # first is a {Executor}, second is array of args for task, third is a task (Proc)
42
+ # @param [Array<Array(ExecutorService, Array<Object>, Proc)>] posts array of triplets where
43
+ # first is a {ExecutorService}, second is array of args for task, third is a task (Proc)
44
44
  def posts(posts)
45
45
  # if can_overflow?
46
46
  # raise ArgumentError, 'SerializedExecution does not support thread-pools which can overflow'
@@ -16,15 +16,22 @@ module Concurrent
16
16
 
17
17
  # @!macro [attach] single_thread_executor
18
18
  #
19
- # A thread pool with a set number of threads. The number of threads in the pool
20
- # is set on construction and remains constant. When all threads are busy new
21
- # tasks `#post` to the thread pool are enqueued until a thread becomes available.
22
- # Should a thread crash for any reason the thread will immediately be removed
23
- # from the pool and replaced.
19
+ # A thread pool with a single thread an unlimited queue. Should the thread
20
+ # die for any reason it will be removed and replaced, thus ensuring that
21
+ # the executor will always remain viable and available to process jobs.
24
22
  #
25
- # The API and behavior of this class are based on Java's `SingleThreadExecutor`
23
+ # A common pattern for background processing is to create a single thread
24
+ # on which an infinite loop is run. The thread's loop blocks on an input
25
+ # source (perhaps blocking I/O or a queue) and processes each input as it
26
+ # is received. This pattern has several issues. The thread itself is highly
27
+ # susceptible to errors during processing. Also, the thread itself must be
28
+ # constantly monitored and restarted should it die. `SingleThreadExecutor`
29
+ # encapsulates all these bahaviors. The task processor is highly resilient
30
+ # to errors from within tasks. Also, should the thread die it will
31
+ # automatically be restarted.
32
+ #
33
+ # The API and behavior of this class are based on Java's `SingleThreadExecutor`.
26
34
  #
27
- # @!macro thread_pool_options
28
35
  # @!macro abstract_executor_service_public_api
29
36
  class SingleThreadExecutor < SingleThreadExecutorImplementation
30
37
 
@@ -32,9 +39,12 @@ module Concurrent
32
39
  #
33
40
  # Create a new thread pool.
34
41
  #
35
- # @option opts [Symbol] :fallback_policy (:discard) the policy for
36
- # handling new tasks that are received when the queue size has
37
- # reached `max_queue` or after the executor has shut down
42
+ # @option opts [Symbol] :fallback_policy (:discard) the policy for handling new
43
+ # tasks that are received when the queue size has reached
44
+ # `max_queue` or the executor has shut down
45
+ #
46
+ # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
47
+ # in `FALLBACK_POLICIES`
38
48
  #
39
49
  # @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
40
50
  # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html