concurrent-ruby 0.9.2 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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