concurrent-ruby 0.9.2 → 1.0.0.pre1
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 +4 -4
- data/CHANGELOG.md +15 -1
- data/README.md +67 -68
- data/lib/concurrent.rb +14 -1
- data/lib/concurrent/array.rb +38 -0
- data/lib/concurrent/async.rb +0 -17
- data/lib/concurrent/atomic/abstract_thread_local_var.rb +40 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +81 -118
- data/lib/concurrent/atomic/atomic_fixnum.rb +98 -162
- data/lib/concurrent/atomic/atomic_reference.rb +0 -7
- data/lib/concurrent/atomic/count_down_latch.rb +62 -103
- data/lib/concurrent/atomic/cyclic_barrier.rb +2 -0
- data/lib/concurrent/atomic/java_count_down_latch.rb +39 -0
- data/lib/concurrent/atomic/java_thread_local_var.rb +50 -0
- data/lib/concurrent/atomic/mutex_atomic_boolean.rb +60 -0
- data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +91 -0
- data/lib/concurrent/atomic/mutex_count_down_latch.rb +43 -0
- data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
- data/lib/concurrent/atomic/ruby_thread_local_var.rb +172 -0
- data/lib/concurrent/atomic/semaphore.rb +84 -178
- data/lib/concurrent/atomic/thread_local_var.rb +63 -294
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +14 -8
- data/lib/concurrent/atomics.rb +0 -33
- data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
- data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +921 -0
- data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
- data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +142 -0
- data/lib/concurrent/collection/map/synchronized_map_backend.rb +86 -0
- data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
- data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
- data/lib/concurrent/concern/logging.rb +1 -1
- data/lib/concurrent/concern/obligation.rb +0 -12
- data/lib/concurrent/configuration.rb +18 -148
- data/lib/concurrent/delay.rb +5 -4
- data/lib/concurrent/exchanger.rb +327 -41
- data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
- data/lib/concurrent/executor/executor.rb +4 -29
- data/lib/concurrent/executor/executor_service.rb +23 -359
- data/lib/concurrent/executor/immediate_executor.rb +3 -2
- data/lib/concurrent/executor/java_executor_service.rb +100 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +3 -2
- data/lib/concurrent/executor/java_thread_pool_executor.rb +3 -4
- data/lib/concurrent/executor/ruby_executor_service.rb +72 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +7 -5
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +3 -11
- data/lib/concurrent/executor/safe_task_executor.rb +1 -1
- data/lib/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent/executor/serialized_execution.rb +8 -31
- data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent/executor/simple_executor_service.rb +1 -10
- data/lib/concurrent/executor/timer_set.rb +4 -8
- data/lib/concurrent/executors.rb +13 -2
- data/lib/concurrent/future.rb +2 -2
- data/lib/concurrent/hash.rb +35 -0
- data/lib/concurrent/ivar.rb +9 -14
- data/lib/concurrent/map.rb +178 -0
- data/lib/concurrent/promise.rb +2 -2
- data/lib/concurrent/scheduled_task.rb +9 -69
- data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent/thread_safe/util.rb +23 -0
- data/lib/concurrent/thread_safe/util/adder.rb +71 -0
- data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +28 -0
- data/lib/concurrent/thread_safe/util/cheap_lockable.rb +115 -0
- data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +37 -0
- data/lib/concurrent/thread_safe/util/striped64.rb +236 -0
- data/lib/concurrent/thread_safe/util/volatile.rb +73 -0
- data/lib/concurrent/thread_safe/util/xor_shift_random.rb +48 -0
- data/lib/concurrent/timer_task.rb +3 -3
- data/lib/concurrent/tuple.rb +86 -0
- data/lib/concurrent/version.rb +2 -2
- metadata +37 -10
- data/lib/concurrent/atomic/condition.rb +0 -78
- data/lib/concurrent/collection/priority_queue.rb +0 -360
- data/lib/concurrent/utilities.rb +0 -5
- data/lib/concurrent/utility/timeout.rb +0 -39
- data/lib/concurrent/utility/timer.rb +0 -26
- data/lib/concurrent_ruby.rb +0 -2
@@ -9,7 +9,7 @@ module Concurrent
|
|
9
9
|
module Logging
|
10
10
|
include Logger::Severity
|
11
11
|
|
12
|
-
# Logs through {
|
12
|
+
# Logs through {Concurrent.global_logger}, it can be overridden by setting @logger
|
13
13
|
# @param [Integer] level one of Logger::Severity constants
|
14
14
|
# @param [String] progname e.g. a path of an Actor
|
15
15
|
# @param [String, nil] message when nil block is used to generate the message
|
@@ -3,14 +3,12 @@ require 'timeout'
|
|
3
3
|
|
4
4
|
require 'concurrent/atomic/event'
|
5
5
|
require 'concurrent/concern/dereferenceable'
|
6
|
-
require 'concurrent/concern/deprecation'
|
7
6
|
|
8
7
|
module Concurrent
|
9
8
|
module Concern
|
10
9
|
|
11
10
|
module Obligation
|
12
11
|
include Concern::Dereferenceable
|
13
|
-
include Concern::Deprecation
|
14
12
|
|
15
13
|
# Has the obligation been fulfilled?
|
16
14
|
#
|
@@ -48,16 +46,6 @@ module Concurrent
|
|
48
46
|
[:fulfilled, :rejected].include? state
|
49
47
|
end
|
50
48
|
|
51
|
-
# Has the obligation completed processing?
|
52
|
-
#
|
53
|
-
# @return [Boolean]
|
54
|
-
#
|
55
|
-
# @deprecated
|
56
|
-
def completed?
|
57
|
-
deprecated_method 'completed?', 'complete?'
|
58
|
-
complete?
|
59
|
-
end
|
60
|
-
|
61
49
|
# Is the obligation still awaiting completion of processing?
|
62
50
|
#
|
63
51
|
# @return [Boolean]
|
@@ -2,7 +2,6 @@ require 'thread'
|
|
2
2
|
require 'concurrent/delay'
|
3
3
|
require 'concurrent/errors'
|
4
4
|
require 'concurrent/atomic/atomic_reference'
|
5
|
-
require 'concurrent/concern/deprecation'
|
6
5
|
require 'concurrent/concern/logging'
|
7
6
|
require 'concurrent/executor/timer_set'
|
8
7
|
require 'concurrent/executor/immediate_executor'
|
@@ -13,7 +12,6 @@ require 'concurrent/utility/processor_counter'
|
|
13
12
|
|
14
13
|
module Concurrent
|
15
14
|
extend Concern::Logging
|
16
|
-
extend Concern::Deprecation
|
17
15
|
|
18
16
|
# @return [Logger] Logger with provided level and output.
|
19
17
|
def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
|
@@ -25,19 +23,19 @@ module Concurrent
|
|
25
23
|
msg
|
26
24
|
when Exception
|
27
25
|
format "%s (%s)\n%s",
|
28
|
-
|
26
|
+
msg.message, msg.class, (msg.backtrace || []).join("\n")
|
29
27
|
else
|
30
28
|
msg.inspect
|
31
29
|
end
|
32
30
|
format "[%s] %5s -- %s: %s\n",
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
|
32
|
+
severity,
|
33
|
+
progname,
|
34
|
+
formatted_message
|
37
35
|
end
|
38
36
|
|
39
37
|
lambda do |loglevel, progname, message = nil, &block|
|
40
|
-
|
38
|
+
logger.add loglevel, message, progname, &block
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
@@ -95,25 +93,6 @@ module Concurrent
|
|
95
93
|
AtExit.enabled = false
|
96
94
|
end
|
97
95
|
|
98
|
-
def self.disable_executor_auto_termination!
|
99
|
-
deprecated_method 'disable_executor_auto_termination!', 'disable_at_exit_handlers!'
|
100
|
-
disable_at_exit_handlers!
|
101
|
-
end
|
102
|
-
|
103
|
-
# @return [true,false]
|
104
|
-
# @see .disable_executor_auto_termination!
|
105
|
-
def self.disable_executor_auto_termination?
|
106
|
-
deprecated_method 'disable_executor_auto_termination?', 'Concurrent::AtExit.enabled?'
|
107
|
-
AtExit.enabled?
|
108
|
-
end
|
109
|
-
|
110
|
-
# terminates all pools and blocks until they are terminated
|
111
|
-
# @see .disable_executor_auto_termination!
|
112
|
-
def self.terminate_pools!
|
113
|
-
deprecated_method 'terminate_pools!', 'Concurrent::AtExit.run'
|
114
|
-
AtExit.run
|
115
|
-
end
|
116
|
-
|
117
96
|
# Global thread pool optimized for short, fast *operations*.
|
118
97
|
#
|
119
98
|
# @return [ThreadPoolExecutor] the thread pool
|
@@ -151,132 +130,23 @@ module Concurrent
|
|
151
130
|
|
152
131
|
def self.new_fast_executor(opts = {})
|
153
132
|
FixedThreadPool.new(
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
133
|
+
[2, Concurrent.processor_count].max,
|
134
|
+
auto_terminate: opts.fetch(:auto_terminate, true),
|
135
|
+
idletime: 60, # 1 minute
|
136
|
+
max_queue: 0, # unlimited
|
137
|
+
fallback_policy: :abort # shouldn't matter -- 0 max queue
|
159
138
|
)
|
160
139
|
end
|
161
140
|
|
162
141
|
def self.new_io_executor(opts = {})
|
163
142
|
ThreadPoolExecutor.new(
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
143
|
+
min_threads: [2, Concurrent.processor_count].max,
|
144
|
+
max_threads: ThreadPoolExecutor::DEFAULT_MAX_POOL_SIZE,
|
145
|
+
# max_threads: 1000,
|
146
|
+
auto_terminate: opts.fetch(:auto_terminate, true),
|
147
|
+
idletime: 60, # 1 minute
|
148
|
+
max_queue: 0, # unlimited
|
149
|
+
fallback_policy: :abort # shouldn't matter -- 0 max queue
|
171
150
|
)
|
172
151
|
end
|
173
|
-
|
174
|
-
# A gem-level configuration object.
|
175
|
-
class Configuration
|
176
|
-
include Concern::Deprecation
|
177
|
-
|
178
|
-
# Create a new configuration object.
|
179
|
-
def initialize
|
180
|
-
end
|
181
|
-
|
182
|
-
# if assigned to {#logger}, it will log nothing.
|
183
|
-
# @deprecated Use Concurrent::NULL_LOGGER instead
|
184
|
-
def no_logger
|
185
|
-
deprecated_method 'Concurrent.configuration.no_logger', 'Concurrent::NULL_LOGGER'
|
186
|
-
NULL_LOGGER
|
187
|
-
end
|
188
|
-
|
189
|
-
# a proc defining how to log messages, its interface has to be:
|
190
|
-
# lambda { |level, progname, message = nil, &block| _ }
|
191
|
-
#
|
192
|
-
# @deprecated Use Concurrent.global_logger instead
|
193
|
-
def logger
|
194
|
-
deprecated_method 'Concurrent.configuration.logger', 'Concurrent.global_logger'
|
195
|
-
Concurrent.global_logger.value
|
196
|
-
end
|
197
|
-
|
198
|
-
# a proc defining how to log messages, its interface has to be:
|
199
|
-
# lambda { |level, progname, message = nil, &block| _ }
|
200
|
-
#
|
201
|
-
# @deprecated Use Concurrent.global_logger instead
|
202
|
-
def logger=(value)
|
203
|
-
deprecated_method 'Concurrent.configuration.logger=', 'Concurrent.global_logger='
|
204
|
-
Concurrent.global_logger = value
|
205
|
-
end
|
206
|
-
|
207
|
-
# @deprecated Use Concurrent.global_io_executor instead
|
208
|
-
def global_task_pool
|
209
|
-
deprecated_method 'Concurrent.configuration.global_task_pool', 'Concurrent.global_io_executor'
|
210
|
-
Concurrent.global_io_executor
|
211
|
-
end
|
212
|
-
|
213
|
-
# @deprecated Use Concurrent.global_fast_executor instead
|
214
|
-
def global_operation_pool
|
215
|
-
deprecated_method 'Concurrent.configuration.global_operation_pool', 'Concurrent.global_fast_executor'
|
216
|
-
Concurrent.global_fast_executor
|
217
|
-
end
|
218
|
-
|
219
|
-
# @deprecated Use Concurrent.global_timer_set instead
|
220
|
-
def global_timer_set
|
221
|
-
deprecated_method 'Concurrent.configuration.global_timer_set', 'Concurrent.global_timer_set'
|
222
|
-
Concurrent.global_timer_set
|
223
|
-
end
|
224
|
-
|
225
|
-
# @deprecated Replacing global thread pools is deprecated.
|
226
|
-
# Use the :executor constructor option instead.
|
227
|
-
def global_task_pool=(executor)
|
228
|
-
deprecated 'Replacing global thread pools is deprecated. Use the :executor constructor option instead.'
|
229
|
-
GLOBAL_IO_EXECUTOR.reconfigure { executor } or
|
230
|
-
raise ConfigurationError.new('global task pool was already set')
|
231
|
-
end
|
232
|
-
|
233
|
-
# @deprecated Replacing global thread pools is deprecated.
|
234
|
-
# Use the :executor constructor option instead.
|
235
|
-
def global_operation_pool=(executor)
|
236
|
-
deprecated 'Replacing global thread pools is deprecated. Use the :executor constructor option instead.'
|
237
|
-
GLOBAL_FAST_EXECUTOR.reconfigure { executor } or
|
238
|
-
raise ConfigurationError.new('global operation pool was already set')
|
239
|
-
end
|
240
|
-
|
241
|
-
# @deprecated Use Concurrent.new_io_executor instead
|
242
|
-
def new_task_pool
|
243
|
-
deprecated_method 'Concurrent.configuration.new_task_pool', 'Concurrent.new_io_executor'
|
244
|
-
Concurrent.new_io_executor
|
245
|
-
end
|
246
|
-
|
247
|
-
# @deprecated Use Concurrent.new_fast_executor instead
|
248
|
-
def new_operation_pool
|
249
|
-
deprecated_method 'Concurrent.configuration.new_operation_pool', 'Concurrent.new_fast_executor'
|
250
|
-
Concurrent.new_fast_executor
|
251
|
-
end
|
252
|
-
|
253
|
-
# @deprecated Use Concurrent.disable_executor_auto_termination! instead
|
254
|
-
def auto_terminate=(value)
|
255
|
-
deprecated_method 'Concurrent.configuration.auto_terminate=', 'Concurrent.disable_executor_auto_termination!'
|
256
|
-
Concurrent.disable_executor_auto_termination! if !value
|
257
|
-
end
|
258
|
-
|
259
|
-
# @deprecated Use Concurrent.auto_terminate_global_executors? instead
|
260
|
-
def auto_terminate
|
261
|
-
deprecated_method 'Concurrent.configuration.auto_terminate', 'Concurrent.auto_terminate_global_executors?'
|
262
|
-
Concurrent.auto_terminate_global_executors?
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# create the default configuration on load
|
267
|
-
CONFIGURATION = AtomicReference.new(Configuration.new)
|
268
|
-
private_constant :CONFIGURATION
|
269
|
-
|
270
|
-
# @return [Configuration]
|
271
|
-
def self.configuration
|
272
|
-
CONFIGURATION.value
|
273
|
-
end
|
274
|
-
|
275
|
-
# Perform gem-level configuration.
|
276
|
-
#
|
277
|
-
# @yield the configuration commands
|
278
|
-
# @yieldparam [Configuration] the current configuration object
|
279
|
-
def self.configure
|
280
|
-
yield(configuration)
|
281
|
-
end
|
282
152
|
end
|
data/lib/concurrent/delay.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'concurrent/configuration'
|
2
3
|
require 'concurrent/concern/obligation'
|
3
4
|
require 'concurrent/executor/executor'
|
4
5
|
require 'concurrent/executor/immediate_executor'
|
@@ -78,7 +79,7 @@ module Concurrent
|
|
78
79
|
else
|
79
80
|
# this function has been optimized for performance and
|
80
81
|
# should not be modified without running new benchmarks
|
81
|
-
|
82
|
+
synchronize do
|
82
83
|
execute = @computing = true unless @computing
|
83
84
|
if execute
|
84
85
|
begin
|
@@ -140,7 +141,7 @@ module Concurrent
|
|
140
141
|
# @yield the delayed operation to perform
|
141
142
|
# @return [true, false] if success
|
142
143
|
def reconfigure(&block)
|
143
|
-
|
144
|
+
synchronize do
|
144
145
|
raise ArgumentError.new('no block given') unless block_given?
|
145
146
|
unless @computing
|
146
147
|
@task = block
|
@@ -170,7 +171,7 @@ module Concurrent
|
|
170
171
|
# this function has been optimized for performance and
|
171
172
|
# should not be modified without running new benchmarks
|
172
173
|
execute = task = nil
|
173
|
-
|
174
|
+
synchronize do
|
174
175
|
execute = @computing = true unless @computing
|
175
176
|
task = @task
|
176
177
|
end
|
@@ -183,7 +184,7 @@ module Concurrent
|
|
183
184
|
rescue => ex
|
184
185
|
reason = ex
|
185
186
|
end
|
186
|
-
|
187
|
+
synchronize do
|
187
188
|
set_state(success, result, reason)
|
188
189
|
event.set
|
189
190
|
end
|
data/lib/concurrent/exchanger.rb
CHANGED
@@ -1,58 +1,344 @@
|
|
1
|
+
require 'concurrent/errors'
|
2
|
+
require 'concurrent/maybe'
|
3
|
+
require 'concurrent/atomic/atomic_reference'
|
4
|
+
require 'concurrent/atomic/count_down_latch'
|
5
|
+
require 'concurrent/utility/engine'
|
6
|
+
require 'concurrent/utility/monotonic_time'
|
7
|
+
|
1
8
|
module Concurrent
|
2
9
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
10
|
+
# @!macro [attach] exchanger
|
11
|
+
#
|
12
|
+
# A synchronization point at which threads can pair and swap elements within
|
13
|
+
# pairs. Each thread presents some object on entry to the exchange method,
|
14
|
+
# matches with a partner thread, and receives its partner's object on return.
|
15
|
+
#
|
16
|
+
# This implementation is very simple, using only a single slot for each
|
17
|
+
# exchanger (unlike more advanced implementations which use an "arena").
|
18
|
+
# This approach will work perfectly fine when there are only a few threads
|
19
|
+
# accessing a single `Exchanger`. Beyond a handful of threads the performance
|
20
|
+
# will degrade rapidly due to contention on the single slot, but the algorithm
|
21
|
+
# will remain correct.
|
22
|
+
#
|
23
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html java.util.concurrent.Exchanger
|
24
|
+
#
|
25
|
+
# @!macro edge_warning
|
6
26
|
#
|
7
|
-
#
|
8
|
-
# Since `MVar` is also a `Dereferenceable`, the exchanged values support all
|
9
|
-
# dereferenceable options. The constructor options hash will be passed to
|
10
|
-
# the `MVar` constructors.
|
11
|
-
#
|
12
|
-
# @see Concurrent::MVar
|
13
|
-
# @see Concurrent::Concern::Dereferenceable
|
14
|
-
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html java.util.concurrent.Exchanger
|
27
|
+
# @example
|
15
28
|
#
|
16
|
-
#
|
17
|
-
|
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
|
18
39
|
|
19
|
-
|
40
|
+
# @!visibility private
|
41
|
+
CANCEL = Object.new
|
42
|
+
private_constant :CANCEL
|
20
43
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
# will be processed
|
25
|
-
def initialize(opts = {})
|
26
|
-
@first = MVar.new(EMPTY, opts)
|
27
|
-
@second = MVar.new(MVar::EMPTY, opts)
|
44
|
+
# @!macro [attach] exchanger_method_initialize
|
45
|
+
def initialize
|
46
|
+
raise NotImplementedError
|
28
47
|
end
|
29
48
|
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
49
|
+
# @!macro [attach] exchanger_method_do_exchange
|
50
|
+
#
|
51
|
+
# Waits for another thread to arrive at this exchange point (unless the
|
52
|
+
# current thread is interrupted), and then transfers the given object to
|
53
|
+
# it, receiving its object in return. The timeout value indicates the
|
54
|
+
# approximate number of seconds the method should block while waiting
|
55
|
+
# for the exchange. When the timeout value is `nil` the method will
|
56
|
+
# block indefinitely.
|
33
57
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
58
|
+
# @param [Object] value the value to exchange with another thread
|
59
|
+
# @param [Numeric, nil] timeout in seconds, `nil` blocks indefinitely
|
60
|
+
#
|
61
|
+
# @!macro [attach] exchanger_method_exchange
|
62
|
+
#
|
63
|
+
# In some edge cases when a `timeout` is given a return value of `nil` may be
|
64
|
+
# ambiguous. Specifically, if `nil` is a valid value in the exchange it will
|
65
|
+
# be impossible to tell whether `nil` is the actual return value or if it
|
66
|
+
# signifies timeout. When `nil` is a valid value in the exchange consider
|
67
|
+
# using {#exchange!} or {#try_exchange} instead.
|
68
|
+
#
|
69
|
+
# @return [Object] the value exchanged by the other thread or `nil` on timeout
|
38
70
|
def exchange(value, timeout = nil)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
71
|
+
(value = do_exchange(value, timeout)) == CANCEL ? nil : value
|
72
|
+
end
|
73
|
+
|
74
|
+
# @!macro exchanger_method_do_exchange
|
75
|
+
#
|
76
|
+
# @!macro [attach] exchanger_method_exchange_bang
|
77
|
+
#
|
78
|
+
# On timeout a {Concurrent::TimeoutError} exception will be raised.
|
79
|
+
#
|
80
|
+
# @return [Object] the value exchanged by the other thread
|
81
|
+
# @raise [Concurrent::TimeoutError] on timeout
|
82
|
+
def exchange!(value, timeout = nil)
|
83
|
+
if (value = do_exchange(value, timeout)) == CANCEL
|
84
|
+
raise Concurrent::TimeoutError
|
85
|
+
else
|
86
|
+
value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @!macro exchanger_method_do_exchange
|
91
|
+
#
|
92
|
+
# @!macro [attach] exchanger_method_try_exchange
|
93
|
+
#
|
94
|
+
# The return value will be a {Concurrent::Maybe} set to `Just` on success or
|
95
|
+
# `Nothing` on timeout.
|
96
|
+
#
|
97
|
+
# @return [Concurrent::Maybe] on success a `Just` maybe will be returned with
|
98
|
+
# the item exchanged by the other thread as `#value`; on timeout a
|
99
|
+
# `Nothing` maybe will be returned with {Concurrent::TimeoutError} as `#reason`
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
#
|
103
|
+
# exchanger = Concurrent::Exchanger.new
|
104
|
+
#
|
105
|
+
# result = exchanger.exchange(:foo, 0.5)
|
106
|
+
#
|
107
|
+
# if result.just?
|
108
|
+
# puts result.value #=> :bar
|
109
|
+
# else
|
110
|
+
# puts 'timeout'
|
111
|
+
# end
|
112
|
+
def try_exchange(value, timeout = nil)
|
113
|
+
if (value = do_exchange(value, timeout)) == CANCEL
|
114
|
+
Concurrent::Maybe.nothing(Concurrent::TimeoutError)
|
115
|
+
else
|
116
|
+
Concurrent::Maybe.just(value)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# @!macro exchanger_method_do_exchange
|
123
|
+
#
|
124
|
+
# @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
|
125
|
+
def do_exchange(value, timeout)
|
126
|
+
raise NotImplementedError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# @!macro exchanger
|
131
|
+
# @!macro internal_implementation_note
|
132
|
+
# @!visibility private
|
133
|
+
class RubyExchanger < AbstractExchanger
|
134
|
+
# A simplified version of java.util.concurrent.Exchanger written by
|
135
|
+
# Doug Lea, Bill Scherer, and Michael Scott with assistance from members
|
136
|
+
# of JCP JSR-166 Expert Group and released to the public domain. It does
|
137
|
+
# not include the arena or the multi-processor spin loops.
|
138
|
+
# http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java
|
139
|
+
|
140
|
+
# @!visibility private
|
141
|
+
NIL_SENTINEL = Object.new
|
142
|
+
private_constant :NIL_SENTINEL
|
143
|
+
|
144
|
+
# @!visibility private
|
145
|
+
class Node < Concurrent::AtomicReference
|
146
|
+
attr_reader :item, :latch
|
147
|
+
def initialize(item)
|
148
|
+
@item = item
|
149
|
+
@latch = Concurrent::CountDownLatch.new
|
150
|
+
super(nil)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
private_constant :Node
|
154
|
+
|
155
|
+
# @!macro exchanger_method_initialize
|
156
|
+
def initialize
|
157
|
+
@slot = Concurrent::AtomicReference.new
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# @!macro exchanger_method_do_exchange
|
163
|
+
#
|
164
|
+
# @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
|
165
|
+
def do_exchange(value, timeout)
|
166
|
+
|
167
|
+
# ALGORITHM
|
168
|
+
#
|
169
|
+
# From the original Java version:
|
170
|
+
#
|
171
|
+
# > The basic idea is to maintain a "slot", which is a reference to
|
172
|
+
# > a Node containing both an Item to offer and a "hole" waiting to
|
173
|
+
# > get filled in. If an incoming "occupying" thread sees that the
|
174
|
+
# > slot is null, it CAS'es (compareAndSets) a Node there and waits
|
175
|
+
# > for another to invoke exchange. That second "fulfilling" thread
|
176
|
+
# > sees that the slot is non-null, and so CASes it back to null,
|
177
|
+
# > also exchanging items by CASing the hole, plus waking up the
|
178
|
+
# > occupying thread if it is blocked. In each case CAS'es may
|
179
|
+
# > fail because a slot at first appears non-null but is null upon
|
180
|
+
# > CAS, or vice-versa. So threads may need to retry these
|
181
|
+
# > actions.
|
182
|
+
#
|
183
|
+
# This version:
|
184
|
+
#
|
185
|
+
# An exchange occurs between an "occupier" thread and a "fulfiller" thread.
|
186
|
+
# The "slot" is used to setup this interaction. The first thread in the
|
187
|
+
# exchange puts itself into the slot (occupies) and waits for a fulfiller.
|
188
|
+
# The second thread removes the occupier from the slot and attempts to
|
189
|
+
# perform the exchange. Removing the occupier also frees the slot for
|
190
|
+
# another occupier/fulfiller pair.
|
191
|
+
#
|
192
|
+
# Because the occupier and the fulfiller are operating independently and
|
193
|
+
# because there may be contention with other threads, any failed operation
|
194
|
+
# indicates contention. Both the occupier and the fulfiller operate within
|
195
|
+
# spin loops. Any failed actions along the happy path will cause the thread
|
196
|
+
# to repeat the loop and try again.
|
197
|
+
#
|
198
|
+
# When a timeout value is given the thread must be cognizant of time spent
|
199
|
+
# in the spin loop. The remaining time is checked every loop. When the time
|
200
|
+
# runs out the thread will exit.
|
201
|
+
#
|
202
|
+
# A "node" is the data structure used to perform the exchange. Only the
|
203
|
+
# occupier's node is necessary. It's the node used for the exchange.
|
204
|
+
# Each node has an "item," a "hole" (self), and a "latch." The item is the
|
205
|
+
# node's initial value. It never changes. It's what the fulfiller returns on
|
206
|
+
# success. The occupier's hole is where the fulfiller put its item. It's the
|
207
|
+
# item that the occupier returns on success. The latch is used for synchronization.
|
208
|
+
# Becuase a thread may act as either an occupier or fulfiller (or possibly
|
209
|
+
# both in periods of high contention) every thread creates a node when
|
210
|
+
# the exchange method is first called.
|
211
|
+
#
|
212
|
+
# The following steps occur within the spin loop. If any actions fail
|
213
|
+
# the thread will loop and try again, so long as there is time remaining.
|
214
|
+
# If time runs out the thread will return CANCEL.
|
215
|
+
#
|
216
|
+
# Check the slot for an occupier:
|
217
|
+
#
|
218
|
+
# * If the slot is empty try to occupy
|
219
|
+
# * If the slot is full try to fulfill
|
220
|
+
#
|
221
|
+
# Attempt to occupy:
|
222
|
+
#
|
223
|
+
# * Attempt to CAS myself into the slot
|
224
|
+
# * Go to sleep and wait to be woken by a fulfiller
|
225
|
+
# * If the sleep is successful then the fulfiller completed its happy path
|
226
|
+
# - Return the value from my hole (the value given by the fulfiller)
|
227
|
+
# * When the sleep fails (time ran out) attempt to cancel the operation
|
228
|
+
# - Attempt to CAS myself out of the hole
|
229
|
+
# - If successful there is no contention
|
230
|
+
# - Return CANCEL
|
231
|
+
# - On failure, I am competing with a fulfiller
|
232
|
+
# - Attempt to CAS my hole to CANCEL
|
233
|
+
# - On success
|
234
|
+
# - Let the fulfiller deal with my cancel
|
235
|
+
# - Return CANCEL
|
236
|
+
# - On failure the fulfiller has completed its happy path
|
237
|
+
# - Return th value from my hole (the fulfiller's value)
|
238
|
+
#
|
239
|
+
# Attempt to fulfill:
|
240
|
+
#
|
241
|
+
# * Attempt to CAS the occupier out of the slot
|
242
|
+
# - On failure loop again
|
243
|
+
# * Attempt to CAS my item into the occupier's hole
|
244
|
+
# - On failure the occupier is trying to cancel
|
245
|
+
# - Loop again
|
246
|
+
# - On success we are on the happy path
|
247
|
+
# - Wake the sleeping occupier
|
248
|
+
# - Return the occupier's item
|
249
|
+
|
250
|
+
value = NIL_SENTINEL if value.nil? # The sentinel allows nil to be a valid value
|
251
|
+
slot = @slot # Convenience to minimize typing @
|
252
|
+
me = Node.new(value) # create my node in case I need to occupy
|
253
|
+
end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up
|
254
|
+
|
255
|
+
result = loop do
|
256
|
+
other = slot.get
|
257
|
+
if other && slot.compare_and_set(other, nil)
|
258
|
+
# try to fulfill
|
259
|
+
if other.compare_and_set(nil, value)
|
260
|
+
# happy path
|
261
|
+
other.latch.count_down
|
262
|
+
break other.item
|
263
|
+
end
|
264
|
+
elsif other.nil? && slot.compare_and_set(nil, me)
|
265
|
+
# try to occupy
|
266
|
+
timeout = end_at - Concurrent.monotonic_time if timeout
|
267
|
+
if me.latch.wait(timeout)
|
268
|
+
# happy path
|
269
|
+
break me.value
|
270
|
+
else
|
271
|
+
# attempt to remove myself from the slot
|
272
|
+
if slot.compare_and_set(me, nil)
|
273
|
+
break CANCEL
|
274
|
+
elsif !me.compare_and_set(nil, CANCEL)
|
275
|
+
# I've failed to block the fulfiller
|
276
|
+
break me.get
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
break CANCEL if timeout && Concurrent.monotonic_time >= end_at
|
281
|
+
end
|
282
|
+
|
283
|
+
result == NIL_SENTINEL ? nil : result
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
if Concurrent.on_jruby?
|
288
|
+
|
289
|
+
# @!macro exchanger
|
290
|
+
# @!macro internal_implementation_note
|
291
|
+
# @!visibility private
|
292
|
+
class JavaExchanger < AbstractExchanger
|
293
|
+
|
294
|
+
# @!macro exchanger_method_initialize
|
295
|
+
def initialize
|
296
|
+
@exchanger = java.util.concurrent.Exchanger.new
|
297
|
+
end
|
298
|
+
|
299
|
+
private
|
300
|
+
|
301
|
+
# @!macro exchanger_method_do_exchange
|
302
|
+
#
|
303
|
+
# @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
|
304
|
+
def do_exchange(value, timeout)
|
305
|
+
if timeout.nil?
|
306
|
+
@exchanger.exchange(value)
|
47
307
|
else
|
48
|
-
|
308
|
+
@exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
|
49
309
|
end
|
50
|
-
|
51
|
-
|
52
|
-
@second.put value
|
53
|
-
first
|
310
|
+
rescue java.util.concurrent.TimeoutException
|
311
|
+
CANCEL
|
54
312
|
end
|
55
313
|
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# @!visibility private
|
317
|
+
# @!macro internal_implementation_note
|
318
|
+
ExchangerImplementation = case
|
319
|
+
when Concurrent.on_jruby?
|
320
|
+
JavaExchanger
|
321
|
+
else
|
322
|
+
RubyExchanger
|
323
|
+
end
|
324
|
+
private_constant :ExchangerImplementation
|
325
|
+
|
326
|
+
# @!macro exchanger
|
327
|
+
class Exchanger < ExchangerImplementation
|
328
|
+
|
329
|
+
# @!method initialize
|
330
|
+
# @!macro exchanger_method_initialize
|
331
|
+
|
332
|
+
# @!method exchange(value, timeout = nil)
|
333
|
+
# @!macro exchanger_method_do_exchange
|
334
|
+
# @!macro exchanger_method_exchange
|
335
|
+
|
336
|
+
# @!method exchange!(value, timeout = nil)
|
337
|
+
# @!macro exchanger_method_do_exchange
|
338
|
+
# @!macro exchanger_method_exchange_bang
|
56
339
|
|
340
|
+
# @!method try_exchange(value, timeout = nil)
|
341
|
+
# @!macro exchanger_method_do_exchange
|
342
|
+
# @!macro exchanger_method_try_exchange
|
57
343
|
end
|
58
344
|
end
|