o-concurrent-ruby 1.1.11

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +542 -0
  3. data/Gemfile +37 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +404 -0
  6. data/Rakefile +307 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
  23. data/lib/concurrent-ruby/concurrent/array.rb +66 -0
  24. data/lib/concurrent-ruby/concurrent/async.rb +449 -0
  25. data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
  26. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  27. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
  28. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
  29. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
  30. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
  31. data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
  32. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
  33. data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
  34. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
  35. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
  36. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  37. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  38. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  39. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
  40. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
  41. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
  42. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
  43. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
  44. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
  45. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  46. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  47. data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
  48. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  49. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  50. data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  51. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
  52. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  53. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
  54. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  55. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  56. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  57. data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  58. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
  59. data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
  60. data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
  61. data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
  62. data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
  63. data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
  64. data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
  65. data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
  66. data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
  67. data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
  68. data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
  69. data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
  70. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
  71. data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
  72. data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
  73. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
  74. data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
  75. data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
  76. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
  77. data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
  78. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
  79. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
  80. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
  81. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
  82. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
  83. data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
  84. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
  85. data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
  86. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
  87. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
  88. data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
  89. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
  90. data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
  91. data/lib/concurrent-ruby/concurrent/future.rb +141 -0
  92. data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
  93. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
  94. data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
  95. data/lib/concurrent-ruby/concurrent/map.rb +346 -0
  96. data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
  97. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
  98. data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
  99. data/lib/concurrent-ruby/concurrent/options.rb +42 -0
  100. data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
  101. data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
  102. data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
  103. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
  104. data/lib/concurrent-ruby/concurrent/set.rb +74 -0
  105. data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
  106. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  107. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
  108. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
  109. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
  110. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  111. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
  112. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
  113. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
  114. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
  115. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
  116. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
  117. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
  118. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
  119. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
  120. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
  121. data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
  122. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  123. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
  124. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  125. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
  126. data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  127. data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
  128. data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
  129. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  130. data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
  131. data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
  132. data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
  133. data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
  134. data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
  135. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
  136. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
  137. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
  138. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
  139. data/lib/concurrent-ruby/concurrent/version.rb +3 -0
  140. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
  141. data/lib/concurrent-ruby/concurrent.rb +134 -0
  142. metadata +192 -0
@@ -0,0 +1,81 @@
1
+ require 'concurrent/future'
2
+ require 'concurrent/atomic/atomic_fixnum'
3
+
4
+ module Concurrent
5
+
6
+ # @!visibility private
7
+ class DependencyCounter # :nodoc:
8
+
9
+ def initialize(count, &block)
10
+ @counter = AtomicFixnum.new(count)
11
+ @block = block
12
+ end
13
+
14
+ def update(time, value, reason)
15
+ if @counter.decrement == 0
16
+ @block.call
17
+ end
18
+ end
19
+ end
20
+
21
+ # Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available.
22
+ # {include:file:docs-source/dataflow.md}
23
+ #
24
+ # @param [Future] inputs zero or more `Future` operations that this dataflow depends upon
25
+ #
26
+ # @yield The operation to perform once all the dependencies are met
27
+ # @yieldparam [Future] inputs each of the `Future` inputs to the dataflow
28
+ # @yieldreturn [Object] the result of the block operation
29
+ #
30
+ # @return [Object] the result of all the operations
31
+ #
32
+ # @raise [ArgumentError] if no block is given
33
+ # @raise [ArgumentError] if any of the inputs are not `IVar`s
34
+ def dataflow(*inputs, &block)
35
+ dataflow_with(Concurrent.global_io_executor, *inputs, &block)
36
+ end
37
+ module_function :dataflow
38
+
39
+ def dataflow_with(executor, *inputs, &block)
40
+ call_dataflow(:value, executor, *inputs, &block)
41
+ end
42
+ module_function :dataflow_with
43
+
44
+ def dataflow!(*inputs, &block)
45
+ dataflow_with!(Concurrent.global_io_executor, *inputs, &block)
46
+ end
47
+ module_function :dataflow!
48
+
49
+ def dataflow_with!(executor, *inputs, &block)
50
+ call_dataflow(:value!, executor, *inputs, &block)
51
+ end
52
+ module_function :dataflow_with!
53
+
54
+ private
55
+
56
+ def call_dataflow(method, executor, *inputs, &block)
57
+ raise ArgumentError.new('an executor must be provided') if executor.nil?
58
+ raise ArgumentError.new('no block given') unless block_given?
59
+ unless inputs.all? { |input| input.is_a? IVar }
60
+ raise ArgumentError.new("Not all dependencies are IVars.\nDependencies: #{ inputs.inspect }")
61
+ end
62
+
63
+ result = Future.new(executor: executor) do
64
+ values = inputs.map { |input| input.send(method) }
65
+ block.call(*values)
66
+ end
67
+
68
+ if inputs.empty?
69
+ result.execute
70
+ else
71
+ counter = DependencyCounter.new(inputs.size) { result.execute }
72
+
73
+ inputs.each do |input|
74
+ input.add_observer counter
75
+ end
76
+ end
77
+
78
+ result
79
+ end
80
+ module_function :call_dataflow
81
+ end
@@ -0,0 +1,199 @@
1
+ require 'thread'
2
+ require 'concurrent/concern/obligation'
3
+ require 'concurrent/executor/immediate_executor'
4
+ require 'concurrent/synchronization'
5
+
6
+ module Concurrent
7
+
8
+ # This file has circular require issues. It must be autoloaded here.
9
+ autoload :Options, 'concurrent/options'
10
+
11
+ # Lazy evaluation of a block yielding an immutable result. Useful for
12
+ # expensive operations that may never be needed. It may be non-blocking,
13
+ # supports the `Concern::Obligation` interface, and accepts the injection of
14
+ # custom executor upon which to execute the block. Processing of
15
+ # block will be deferred until the first time `#value` is called.
16
+ # At that time the caller can choose to return immediately and let
17
+ # the block execute asynchronously, block indefinitely, or block
18
+ # with a timeout.
19
+ #
20
+ # When a `Delay` is created its state is set to `pending`. The value and
21
+ # reason are both `nil`. The first time the `#value` method is called the
22
+ # enclosed opration will be run and the calling thread will block. Other
23
+ # threads attempting to call `#value` will block as well. Once the operation
24
+ # is complete the *value* will be set to the result of the operation or the
25
+ # *reason* will be set to the raised exception, as appropriate. All threads
26
+ # blocked on `#value` will return. Subsequent calls to `#value` will immediately
27
+ # return the cached value. The operation will only be run once. This means that
28
+ # any side effects created by the operation will only happen once as well.
29
+ #
30
+ # `Delay` includes the `Concurrent::Concern::Dereferenceable` mixin to support thread
31
+ # safety of the reference returned by `#value`.
32
+ #
33
+ # @!macro copy_options
34
+ #
35
+ # @!macro delay_note_regarding_blocking
36
+ # @note The default behavior of `Delay` is to block indefinitely when
37
+ # calling either `value` or `wait`, executing the delayed operation on
38
+ # the current thread. This makes the `timeout` value completely
39
+ # irrelevant. To enable non-blocking behavior, use the `executor`
40
+ # constructor option. This will cause the delayed operation to be
41
+ # execute on the given executor, allowing the call to timeout.
42
+ #
43
+ # @see Concurrent::Concern::Dereferenceable
44
+ class Delay < Synchronization::LockableObject
45
+ include Concern::Obligation
46
+
47
+ # NOTE: Because the global thread pools are lazy-loaded with these objects
48
+ # there is a performance hit every time we post a new task to one of these
49
+ # thread pools. Subsequently it is critical that `Delay` perform as fast
50
+ # as possible post-completion. This class has been highly optimized using
51
+ # the benchmark script `examples/lazy_and_delay.rb`. Do NOT attempt to
52
+ # DRY-up this class or perform other refactoring with running the
53
+ # benchmarks and ensuring that performance is not negatively impacted.
54
+
55
+ # Create a new `Delay` in the `:pending` state.
56
+ #
57
+ # @!macro executor_and_deref_options
58
+ #
59
+ # @yield the delayed operation to perform
60
+ #
61
+ # @raise [ArgumentError] if no block is given
62
+ def initialize(opts = {}, &block)
63
+ raise ArgumentError.new('no block given') unless block_given?
64
+ super(&nil)
65
+ synchronize { ns_initialize(opts, &block) }
66
+ end
67
+
68
+ # Return the value this object represents after applying the options
69
+ # specified by the `#set_deref_options` method. If the delayed operation
70
+ # raised an exception this method will return nil. The execption object
71
+ # can be accessed via the `#reason` method.
72
+ #
73
+ # @param [Numeric] timeout the maximum number of seconds to wait
74
+ # @return [Object] the current value of the object
75
+ #
76
+ # @!macro delay_note_regarding_blocking
77
+ def value(timeout = nil)
78
+ if @executor # TODO (pitr 12-Sep-2015): broken unsafe read?
79
+ super
80
+ else
81
+ # this function has been optimized for performance and
82
+ # should not be modified without running new benchmarks
83
+ synchronize do
84
+ execute = @evaluation_started = true unless @evaluation_started
85
+ if execute
86
+ begin
87
+ set_state(true, @task.call, nil)
88
+ rescue => ex
89
+ set_state(false, nil, ex)
90
+ end
91
+ elsif incomplete?
92
+ raise IllegalOperationError, 'Recursive call to #value during evaluation of the Delay'
93
+ end
94
+ end
95
+ if @do_nothing_on_deref
96
+ @value
97
+ else
98
+ apply_deref_options(@value)
99
+ end
100
+ end
101
+ end
102
+
103
+ # Return the value this object represents after applying the options
104
+ # specified by the `#set_deref_options` method. If the delayed operation
105
+ # raised an exception, this method will raise that exception (even when)
106
+ # the operation has already been executed).
107
+ #
108
+ # @param [Numeric] timeout the maximum number of seconds to wait
109
+ # @return [Object] the current value of the object
110
+ # @raise [Exception] when `#rejected?` raises `#reason`
111
+ #
112
+ # @!macro delay_note_regarding_blocking
113
+ def value!(timeout = nil)
114
+ if @executor
115
+ super
116
+ else
117
+ result = value
118
+ raise @reason if @reason
119
+ result
120
+ end
121
+ end
122
+
123
+ # Return the value this object represents after applying the options
124
+ # specified by the `#set_deref_options` method.
125
+ #
126
+ # @param [Integer] timeout (nil) the maximum number of seconds to wait for
127
+ # the value to be computed. When `nil` the caller will block indefinitely.
128
+ #
129
+ # @return [Object] self
130
+ #
131
+ # @!macro delay_note_regarding_blocking
132
+ def wait(timeout = nil)
133
+ if @executor
134
+ execute_task_once
135
+ super(timeout)
136
+ else
137
+ value
138
+ end
139
+ self
140
+ end
141
+
142
+ # Reconfigures the block returning the value if still `#incomplete?`
143
+ #
144
+ # @yield the delayed operation to perform
145
+ # @return [true, false] if success
146
+ def reconfigure(&block)
147
+ synchronize do
148
+ raise ArgumentError.new('no block given') unless block_given?
149
+ unless @evaluation_started
150
+ @task = block
151
+ true
152
+ else
153
+ false
154
+ end
155
+ end
156
+ end
157
+
158
+ protected
159
+
160
+ def ns_initialize(opts, &block)
161
+ init_obligation
162
+ set_deref_options(opts)
163
+ @executor = opts[:executor]
164
+
165
+ @task = block
166
+ @state = :pending
167
+ @evaluation_started = false
168
+ end
169
+
170
+ private
171
+
172
+ # @!visibility private
173
+ def execute_task_once # :nodoc:
174
+ # this function has been optimized for performance and
175
+ # should not be modified without running new benchmarks
176
+ execute = task = nil
177
+ synchronize do
178
+ execute = @evaluation_started = true unless @evaluation_started
179
+ task = @task
180
+ end
181
+
182
+ if execute
183
+ executor = Options.executor_from_options(executor: @executor)
184
+ executor.post do
185
+ begin
186
+ result = task.call
187
+ success = true
188
+ rescue => ex
189
+ reason = ex
190
+ end
191
+ synchronize do
192
+ set_state(success, result, reason)
193
+ event.set
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,69 @@
1
+ module Concurrent
2
+
3
+ Error = Class.new(StandardError)
4
+
5
+ # Raised when errors occur during configuration.
6
+ ConfigurationError = Class.new(Error)
7
+
8
+ # Raised when an asynchronous operation is cancelled before execution.
9
+ CancelledOperationError = Class.new(Error)
10
+
11
+ # Raised when a lifecycle method (such as `stop`) is called in an improper
12
+ # sequence or when the object is in an inappropriate state.
13
+ LifecycleError = Class.new(Error)
14
+
15
+ # Raised when an attempt is made to violate an immutability guarantee.
16
+ ImmutabilityError = Class.new(Error)
17
+
18
+ # Raised when an operation is attempted which is not legal given the
19
+ # receiver's current state
20
+ IllegalOperationError = Class.new(Error)
21
+
22
+ # Raised when an object's methods are called when it has not been
23
+ # properly initialized.
24
+ InitializationError = Class.new(Error)
25
+
26
+ # Raised when an object with a start/stop lifecycle has been started an
27
+ # excessive number of times. Often used in conjunction with a restart
28
+ # policy or strategy.
29
+ MaxRestartFrequencyError = Class.new(Error)
30
+
31
+ # Raised when an attempt is made to modify an immutable object
32
+ # (such as an `IVar`) after its final state has been set.
33
+ class MultipleAssignmentError < Error
34
+ attr_reader :inspection_data
35
+
36
+ def initialize(message = nil, inspection_data = nil)
37
+ @inspection_data = inspection_data
38
+ super message
39
+ end
40
+
41
+ def inspect
42
+ format '%s %s>', super[0..-2], @inspection_data.inspect
43
+ end
44
+ end
45
+
46
+ # Raised by an `Executor` when it is unable to process a given task,
47
+ # possibly because of a reject policy or other internal error.
48
+ RejectedExecutionError = Class.new(Error)
49
+
50
+ # Raised when any finite resource, such as a lock counter, exceeds its
51
+ # maximum limit/threshold.
52
+ ResourceLimitError = Class.new(Error)
53
+
54
+ # Raised when an operation times out.
55
+ TimeoutError = Class.new(Error)
56
+
57
+ # Aggregates multiple exceptions.
58
+ class MultipleErrors < Error
59
+ attr_reader :errors
60
+
61
+ def initialize(errors, message = "#{errors.size} errors")
62
+ @errors = errors
63
+ super [*message,
64
+ *errors.map { |e| [format('%s (%s)', e.message, e.class), *e.backtrace] }.flatten(1)
65
+ ].join("\n")
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,352 @@
1
+ require 'concurrent/constants'
2
+ require 'concurrent/errors'
3
+ require 'concurrent/maybe'
4
+ require 'concurrent/atomic/atomic_reference'
5
+ require 'concurrent/atomic/count_down_latch'
6
+ require 'concurrent/utility/engine'
7
+ require 'concurrent/utility/monotonic_time'
8
+
9
+ module Concurrent
10
+
11
+ # @!macro exchanger
12
+ #
13
+ # A synchronization point at which threads can pair and swap elements within
14
+ # pairs. Each thread presents some object on entry to the exchange method,
15
+ # matches with a partner thread, and receives its partner's object on return.
16
+ #
17
+ # @!macro thread_safe_variable_comparison
18
+ #
19
+ # This implementation is very simple, using only a single slot for each
20
+ # exchanger (unlike more advanced implementations which use an "arena").
21
+ # This approach will work perfectly fine when there are only a few threads
22
+ # accessing a single `Exchanger`. Beyond a handful of threads the performance
23
+ # will degrade rapidly due to contention on the single slot, but the algorithm
24
+ # will remain correct.
25
+ #
26
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html java.util.concurrent.Exchanger
27
+ # @example
28
+ #
29
+ # exchanger = Concurrent::Exchanger.new
30
+ #
31
+ # threads = [
32
+ # Thread.new { puts "first: " << exchanger.exchange('foo', 1) }, #=> "first: bar"
33
+ # Thread.new { puts "second: " << exchanger.exchange('bar', 1) } #=> "second: foo"
34
+ # ]
35
+ # threads.each {|t| t.join(2) }
36
+
37
+ # @!visibility private
38
+ class AbstractExchanger < Synchronization::Object
39
+
40
+ # @!visibility private
41
+ CANCEL = ::Object.new
42
+ private_constant :CANCEL
43
+
44
+ def initialize
45
+ super
46
+ end
47
+
48
+ # @!macro exchanger_method_do_exchange
49
+ #
50
+ # Waits for another thread to arrive at this exchange point (unless the
51
+ # current thread is interrupted), and then transfers the given object to
52
+ # it, receiving its object in return. The timeout value indicates the
53
+ # approximate number of seconds the method should block while waiting
54
+ # for the exchange. When the timeout value is `nil` the method will
55
+ # block indefinitely.
56
+ #
57
+ # @param [Object] value the value to exchange with another thread
58
+ # @param [Numeric, nil] timeout in seconds, `nil` blocks indefinitely
59
+ #
60
+ # @!macro exchanger_method_exchange
61
+ #
62
+ # In some edge cases when a `timeout` is given a return value of `nil` may be
63
+ # ambiguous. Specifically, if `nil` is a valid value in the exchange it will
64
+ # be impossible to tell whether `nil` is the actual return value or if it
65
+ # signifies timeout. When `nil` is a valid value in the exchange consider
66
+ # using {#exchange!} or {#try_exchange} instead.
67
+ #
68
+ # @return [Object] the value exchanged by the other thread or `nil` on timeout
69
+ def exchange(value, timeout = nil)
70
+ (value = do_exchange(value, timeout)) == CANCEL ? nil : value
71
+ end
72
+
73
+ # @!macro exchanger_method_do_exchange
74
+ # @!macro exchanger_method_exchange_bang
75
+ #
76
+ # On timeout a {Concurrent::TimeoutError} exception will be raised.
77
+ #
78
+ # @return [Object] the value exchanged by the other thread
79
+ # @raise [Concurrent::TimeoutError] on timeout
80
+ def exchange!(value, timeout = nil)
81
+ if (value = do_exchange(value, timeout)) == CANCEL
82
+ raise Concurrent::TimeoutError
83
+ else
84
+ value
85
+ end
86
+ end
87
+
88
+ # @!macro exchanger_method_do_exchange
89
+ # @!macro exchanger_method_try_exchange
90
+ #
91
+ # The return value will be a {Concurrent::Maybe} set to `Just` on success or
92
+ # `Nothing` on timeout.
93
+ #
94
+ # @return [Concurrent::Maybe] on success a `Just` maybe will be returned with
95
+ # the item exchanged by the other thread as `#value`; on timeout a
96
+ # `Nothing` maybe will be returned with {Concurrent::TimeoutError} as `#reason`
97
+ #
98
+ # @example
99
+ #
100
+ # exchanger = Concurrent::Exchanger.new
101
+ #
102
+ # result = exchanger.exchange(:foo, 0.5)
103
+ #
104
+ # if result.just?
105
+ # puts result.value #=> :bar
106
+ # else
107
+ # puts 'timeout'
108
+ # end
109
+ def try_exchange(value, timeout = nil)
110
+ if (value = do_exchange(value, timeout)) == CANCEL
111
+ Concurrent::Maybe.nothing(Concurrent::TimeoutError)
112
+ else
113
+ Concurrent::Maybe.just(value)
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ # @!macro exchanger_method_do_exchange
120
+ #
121
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
122
+ def do_exchange(value, timeout)
123
+ raise NotImplementedError
124
+ end
125
+ end
126
+
127
+ # @!macro internal_implementation_note
128
+ # @!visibility private
129
+ class RubyExchanger < AbstractExchanger
130
+ # A simplified version of java.util.concurrent.Exchanger written by
131
+ # Doug Lea, Bill Scherer, and Michael Scott with assistance from members
132
+ # of JCP JSR-166 Expert Group and released to the public domain. It does
133
+ # not include the arena or the multi-processor spin loops.
134
+ # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java
135
+
136
+ safe_initialization!
137
+
138
+ class Node < Concurrent::Synchronization::Object
139
+ attr_atomic :value
140
+ safe_initialization!
141
+
142
+ def initialize(item)
143
+ super()
144
+ @Item = item
145
+ @Latch = Concurrent::CountDownLatch.new
146
+ self.value = nil
147
+ end
148
+
149
+ def latch
150
+ @Latch
151
+ end
152
+
153
+ def item
154
+ @Item
155
+ end
156
+ end
157
+ private_constant :Node
158
+
159
+ def initialize
160
+ super
161
+ end
162
+
163
+ private
164
+
165
+ attr_atomic(:slot)
166
+
167
+ # @!macro exchanger_method_do_exchange
168
+ #
169
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
170
+ def do_exchange(value, timeout)
171
+
172
+ # ALGORITHM
173
+ #
174
+ # From the original Java version:
175
+ #
176
+ # > The basic idea is to maintain a "slot", which is a reference to
177
+ # > a Node containing both an Item to offer and a "hole" waiting to
178
+ # > get filled in. If an incoming "occupying" thread sees that the
179
+ # > slot is null, it CAS'es (compareAndSets) a Node there and waits
180
+ # > for another to invoke exchange. That second "fulfilling" thread
181
+ # > sees that the slot is non-null, and so CASes it back to null,
182
+ # > also exchanging items by CASing the hole, plus waking up the
183
+ # > occupying thread if it is blocked. In each case CAS'es may
184
+ # > fail because a slot at first appears non-null but is null upon
185
+ # > CAS, or vice-versa. So threads may need to retry these
186
+ # > actions.
187
+ #
188
+ # This version:
189
+ #
190
+ # An exchange occurs between an "occupier" thread and a "fulfiller" thread.
191
+ # The "slot" is used to setup this interaction. The first thread in the
192
+ # exchange puts itself into the slot (occupies) and waits for a fulfiller.
193
+ # The second thread removes the occupier from the slot and attempts to
194
+ # perform the exchange. Removing the occupier also frees the slot for
195
+ # another occupier/fulfiller pair.
196
+ #
197
+ # Because the occupier and the fulfiller are operating independently and
198
+ # because there may be contention with other threads, any failed operation
199
+ # indicates contention. Both the occupier and the fulfiller operate within
200
+ # spin loops. Any failed actions along the happy path will cause the thread
201
+ # to repeat the loop and try again.
202
+ #
203
+ # When a timeout value is given the thread must be cognizant of time spent
204
+ # in the spin loop. The remaining time is checked every loop. When the time
205
+ # runs out the thread will exit.
206
+ #
207
+ # A "node" is the data structure used to perform the exchange. Only the
208
+ # occupier's node is necessary. It's the node used for the exchange.
209
+ # Each node has an "item," a "hole" (self), and a "latch." The item is the
210
+ # node's initial value. It never changes. It's what the fulfiller returns on
211
+ # success. The occupier's hole is where the fulfiller put its item. It's the
212
+ # item that the occupier returns on success. The latch is used for synchronization.
213
+ # Because a thread may act as either an occupier or fulfiller (or possibly
214
+ # both in periods of high contention) every thread creates a node when
215
+ # the exchange method is first called.
216
+ #
217
+ # The following steps occur within the spin loop. If any actions fail
218
+ # the thread will loop and try again, so long as there is time remaining.
219
+ # If time runs out the thread will return CANCEL.
220
+ #
221
+ # Check the slot for an occupier:
222
+ #
223
+ # * If the slot is empty try to occupy
224
+ # * If the slot is full try to fulfill
225
+ #
226
+ # Attempt to occupy:
227
+ #
228
+ # * Attempt to CAS myself into the slot
229
+ # * Go to sleep and wait to be woken by a fulfiller
230
+ # * If the sleep is successful then the fulfiller completed its happy path
231
+ # - Return the value from my hole (the value given by the fulfiller)
232
+ # * When the sleep fails (time ran out) attempt to cancel the operation
233
+ # - Attempt to CAS myself out of the hole
234
+ # - If successful there is no contention
235
+ # - Return CANCEL
236
+ # - On failure, I am competing with a fulfiller
237
+ # - Attempt to CAS my hole to CANCEL
238
+ # - On success
239
+ # - Let the fulfiller deal with my cancel
240
+ # - Return CANCEL
241
+ # - On failure the fulfiller has completed its happy path
242
+ # - Return th value from my hole (the fulfiller's value)
243
+ #
244
+ # Attempt to fulfill:
245
+ #
246
+ # * Attempt to CAS the occupier out of the slot
247
+ # - On failure loop again
248
+ # * Attempt to CAS my item into the occupier's hole
249
+ # - On failure the occupier is trying to cancel
250
+ # - Loop again
251
+ # - On success we are on the happy path
252
+ # - Wake the sleeping occupier
253
+ # - Return the occupier's item
254
+
255
+ value = NULL if value.nil? # The sentinel allows nil to be a valid value
256
+ me = Node.new(value) # create my node in case I need to occupy
257
+ end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up
258
+
259
+ result = loop do
260
+ other = slot
261
+ if other && compare_and_set_slot(other, nil)
262
+ # try to fulfill
263
+ if other.compare_and_set_value(nil, value)
264
+ # happy path
265
+ other.latch.count_down
266
+ break other.item
267
+ end
268
+ elsif other.nil? && compare_and_set_slot(nil, me)
269
+ # try to occupy
270
+ timeout = end_at - Concurrent.monotonic_time if timeout
271
+ if me.latch.wait(timeout)
272
+ # happy path
273
+ break me.value
274
+ else
275
+ # attempt to remove myself from the slot
276
+ if compare_and_set_slot(me, nil)
277
+ break CANCEL
278
+ elsif !me.compare_and_set_value(nil, CANCEL)
279
+ # I've failed to block the fulfiller
280
+ break me.value
281
+ end
282
+ end
283
+ end
284
+ break CANCEL if timeout && Concurrent.monotonic_time >= end_at
285
+ end
286
+
287
+ result == NULL ? nil : result
288
+ end
289
+ end
290
+
291
+ if Concurrent.on_jruby?
292
+
293
+ # @!macro internal_implementation_note
294
+ # @!visibility private
295
+ class JavaExchanger < AbstractExchanger
296
+
297
+ def initialize
298
+ @exchanger = java.util.concurrent.Exchanger.new
299
+ end
300
+
301
+ private
302
+
303
+ # @!macro exchanger_method_do_exchange
304
+ #
305
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
306
+ def do_exchange(value, timeout)
307
+ result = nil
308
+ if timeout.nil?
309
+ Synchronization::JRuby.sleep_interruptibly do
310
+ result = @exchanger.exchange(value)
311
+ end
312
+ else
313
+ Synchronization::JRuby.sleep_interruptibly do
314
+ result = @exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
315
+ end
316
+ end
317
+ result
318
+ rescue java.util.concurrent.TimeoutException
319
+ CANCEL
320
+ end
321
+ end
322
+ end
323
+
324
+ # @!visibility private
325
+ # @!macro internal_implementation_note
326
+ ExchangerImplementation = case
327
+ when Concurrent.on_jruby?
328
+ JavaExchanger
329
+ else
330
+ RubyExchanger
331
+ end
332
+ private_constant :ExchangerImplementation
333
+
334
+ # @!macro exchanger
335
+ class Exchanger < ExchangerImplementation
336
+
337
+ # @!method initialize
338
+ # Creates exchanger instance
339
+
340
+ # @!method exchange(value, timeout = nil)
341
+ # @!macro exchanger_method_do_exchange
342
+ # @!macro exchanger_method_exchange
343
+
344
+ # @!method exchange!(value, timeout = nil)
345
+ # @!macro exchanger_method_do_exchange
346
+ # @!macro exchanger_method_exchange_bang
347
+
348
+ # @!method try_exchange(value, timeout = nil)
349
+ # @!macro exchanger_method_do_exchange
350
+ # @!macro exchanger_method_try_exchange
351
+ end
352
+ end