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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +542 -0
- data/Gemfile +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +404 -0
- data/Rakefile +307 -0
- data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
- data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
- data/lib/concurrent-ruby/concurrent/array.rb +66 -0
- data/lib/concurrent-ruby/concurrent/async.rb +449 -0
- data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
- data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
- data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
- data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
- data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
- data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
- data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
- data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
- data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
- data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
- data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
- data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
- data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
- data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
- data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
- data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
- data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
- data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
- data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
- data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
- data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
- data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
- data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
- data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
- data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
- data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
- data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
- data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
- data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
- data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
- data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
- data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
- data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
- data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
- data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
- data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
- data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
- data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
- data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
- data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
- data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
- data/lib/concurrent-ruby/concurrent/future.rb +141 -0
- data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
- data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
- data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
- data/lib/concurrent-ruby/concurrent/map.rb +346 -0
- data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
- data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
- data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
- data/lib/concurrent-ruby/concurrent/options.rb +42 -0
- data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
- data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
- data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
- data/lib/concurrent-ruby/concurrent/set.rb +74 -0
- data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
- data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
- data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
- data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
- data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
- data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
- data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
- data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
- data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
- data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
- data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
- data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
- data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
- data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
- data/lib/concurrent-ruby/concurrent/version.rb +3 -0
- data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
- data/lib/concurrent-ruby/concurrent.rb +134 -0
- 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
|