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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -1
  3. data/README.md +67 -68
  4. data/lib/concurrent.rb +14 -1
  5. data/lib/concurrent/array.rb +38 -0
  6. data/lib/concurrent/async.rb +0 -17
  7. data/lib/concurrent/atomic/abstract_thread_local_var.rb +40 -0
  8. data/lib/concurrent/atomic/atomic_boolean.rb +81 -118
  9. data/lib/concurrent/atomic/atomic_fixnum.rb +98 -162
  10. data/lib/concurrent/atomic/atomic_reference.rb +0 -7
  11. data/lib/concurrent/atomic/count_down_latch.rb +62 -103
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +2 -0
  13. data/lib/concurrent/atomic/java_count_down_latch.rb +39 -0
  14. data/lib/concurrent/atomic/java_thread_local_var.rb +50 -0
  15. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +60 -0
  16. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +91 -0
  17. data/lib/concurrent/atomic/mutex_count_down_latch.rb +43 -0
  18. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  19. data/lib/concurrent/atomic/ruby_thread_local_var.rb +172 -0
  20. data/lib/concurrent/atomic/semaphore.rb +84 -178
  21. data/lib/concurrent/atomic/thread_local_var.rb +63 -294
  22. data/lib/concurrent/atomic_reference/mutex_atomic.rb +14 -8
  23. data/lib/concurrent/atomics.rb +0 -33
  24. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  25. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +921 -0
  26. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  27. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +142 -0
  28. data/lib/concurrent/collection/map/synchronized_map_backend.rb +86 -0
  29. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  30. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  31. data/lib/concurrent/concern/logging.rb +1 -1
  32. data/lib/concurrent/concern/obligation.rb +0 -12
  33. data/lib/concurrent/configuration.rb +18 -148
  34. data/lib/concurrent/delay.rb +5 -4
  35. data/lib/concurrent/exchanger.rb +327 -41
  36. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  37. data/lib/concurrent/executor/executor.rb +4 -29
  38. data/lib/concurrent/executor/executor_service.rb +23 -359
  39. data/lib/concurrent/executor/immediate_executor.rb +3 -2
  40. data/lib/concurrent/executor/java_executor_service.rb +100 -0
  41. data/lib/concurrent/executor/java_single_thread_executor.rb +3 -2
  42. data/lib/concurrent/executor/java_thread_pool_executor.rb +3 -4
  43. data/lib/concurrent/executor/ruby_executor_service.rb +72 -0
  44. data/lib/concurrent/executor/ruby_single_thread_executor.rb +7 -5
  45. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +3 -11
  46. data/lib/concurrent/executor/safe_task_executor.rb +1 -1
  47. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  48. data/lib/concurrent/executor/serialized_execution.rb +8 -31
  49. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  50. data/lib/concurrent/executor/simple_executor_service.rb +1 -10
  51. data/lib/concurrent/executor/timer_set.rb +4 -8
  52. data/lib/concurrent/executors.rb +13 -2
  53. data/lib/concurrent/future.rb +2 -2
  54. data/lib/concurrent/hash.rb +35 -0
  55. data/lib/concurrent/ivar.rb +9 -14
  56. data/lib/concurrent/map.rb +178 -0
  57. data/lib/concurrent/promise.rb +2 -2
  58. data/lib/concurrent/scheduled_task.rb +9 -69
  59. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  60. data/lib/concurrent/thread_safe/util.rb +23 -0
  61. data/lib/concurrent/thread_safe/util/adder.rb +71 -0
  62. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +28 -0
  63. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +115 -0
  64. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +37 -0
  65. data/lib/concurrent/thread_safe/util/striped64.rb +236 -0
  66. data/lib/concurrent/thread_safe/util/volatile.rb +73 -0
  67. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +48 -0
  68. data/lib/concurrent/timer_task.rb +3 -3
  69. data/lib/concurrent/tuple.rb +86 -0
  70. data/lib/concurrent/version.rb +2 -2
  71. metadata +37 -10
  72. data/lib/concurrent/atomic/condition.rb +0 -78
  73. data/lib/concurrent/collection/priority_queue.rb +0 -360
  74. data/lib/concurrent/utilities.rb +0 -5
  75. data/lib/concurrent/utility/timeout.rb +0 -39
  76. data/lib/concurrent/utility/timer.rb +0 -26
  77. 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 {Configuration#logger}, it can be overridden by setting @logger
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
- msg.message, msg.class, (msg.backtrace || []).join("\n")
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
- datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
34
- severity,
35
- progname,
36
- formatted_message
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
- logger.add loglevel, message, progname, &block
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
- [2, Concurrent.processor_count].max,
155
- auto_terminate: opts.fetch(:auto_terminate, true),
156
- idletime: 60, # 1 minute
157
- max_queue: 0, # unlimited
158
- fallback_policy: :abort # shouldn't matter -- 0 max queue
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
- min_threads: [2, Concurrent.processor_count].max,
165
- max_threads: ThreadPoolExecutor::DEFAULT_MAX_POOL_SIZE,
166
- # max_threads: 1000,
167
- auto_terminate: opts.fetch(:auto_terminate, true),
168
- idletime: 60, # 1 minute
169
- max_queue: 0, # unlimited
170
- fallback_policy: :abort # shouldn't matter -- 0 max queue
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
@@ -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
- mutex.synchronize do
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
- mutex.synchronize do
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
- mutex.synchronize do
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
- mutex.synchronize do
187
+ synchronize do
187
188
  set_state(success, result, reason)
188
189
  event.set
189
190
  end
@@ -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
- # A synchronization point at which threads can pair and swap elements within
4
- # pairs. Each thread presents some object on entry to the exchange method,
5
- # matches with a partner thread, and receives its partner's object on return.
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
- # Uses `MVar` to manage synchronization of the individual elements.
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
- # @!macro edge_warning
17
- class Exchanger
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
- EMPTY = Object.new
40
+ # @!visibility private
41
+ CANCEL = Object.new
42
+ private_constant :CANCEL
20
43
 
21
- # Create a new `Exchanger` object.
22
- #
23
- # @param [Hash] opts the options controlling how the managed references
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
- # Waits for another thread to arrive at this exchange point (unless the
31
- # current thread is interrupted), and then transfers the given object to
32
- # it, receiving its object in return.
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
- # @param [Object] value the value to exchange with an other thread
35
- # @param [Numeric] timeout the maximum time in second to wait for one other
36
- # thread. nil (default value) means no timeout
37
- # @return [Object] the value exchanged by the other thread; nil if timed out
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
- first = @first.take(timeout)
40
- if first == MVar::TIMEOUT
41
- nil
42
- elsif first == EMPTY
43
- @first.put value
44
- second = @second.take timeout
45
- if second == MVar::TIMEOUT
46
- nil
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
- second
308
+ @exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
49
309
  end
50
- else
51
- @first.put EMPTY
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