concurrent-ruby 0.9.2-java → 1.0.0-java

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/README.md +86 -120
  4. data/lib/concurrent.rb +14 -5
  5. data/lib/concurrent/agent.rb +587 -0
  6. data/lib/concurrent/array.rb +39 -0
  7. data/lib/concurrent/async.rb +296 -149
  8. data/lib/concurrent/atom.rb +135 -45
  9. data/lib/concurrent/atomic/abstract_thread_local_var.rb +38 -0
  10. data/lib/concurrent/atomic/atomic_boolean.rb +83 -118
  11. data/lib/concurrent/atomic/atomic_fixnum.rb +101 -163
  12. data/lib/concurrent/atomic/atomic_reference.rb +1 -8
  13. data/lib/concurrent/atomic/count_down_latch.rb +62 -103
  14. data/lib/concurrent/atomic/cyclic_barrier.rb +3 -1
  15. data/lib/concurrent/atomic/event.rb +1 -1
  16. data/lib/concurrent/atomic/java_count_down_latch.rb +39 -0
  17. data/lib/concurrent/atomic/java_thread_local_var.rb +50 -0
  18. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +60 -0
  19. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +91 -0
  20. data/lib/concurrent/atomic/mutex_count_down_latch.rb +43 -0
  21. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  22. data/lib/concurrent/atomic/read_write_lock.rb +5 -4
  23. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
  24. data/lib/concurrent/atomic/ruby_thread_local_var.rb +172 -0
  25. data/lib/concurrent/atomic/semaphore.rb +84 -178
  26. data/lib/concurrent/atomic/thread_local_var.rb +65 -294
  27. data/lib/concurrent/atomic_reference/jruby+truffle.rb +1 -0
  28. data/lib/concurrent/atomic_reference/jruby.rb +1 -1
  29. data/lib/concurrent/atomic_reference/mutex_atomic.rb +14 -8
  30. data/lib/concurrent/atomic_reference/ruby.rb +1 -1
  31. data/lib/concurrent/atomics.rb +7 -37
  32. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +7 -15
  33. data/lib/concurrent/collection/copy_on_write_observer_set.rb +7 -15
  34. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  35. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  36. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  37. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +144 -0
  38. data/lib/concurrent/collection/map/synchronized_map_backend.rb +86 -0
  39. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  40. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  41. data/lib/concurrent/concern/dereferenceable.rb +9 -24
  42. data/lib/concurrent/concern/logging.rb +1 -1
  43. data/lib/concurrent/concern/obligation.rb +11 -20
  44. data/lib/concurrent/concern/observable.rb +38 -13
  45. data/lib/concurrent/configuration.rb +23 -152
  46. data/lib/concurrent/constants.rb +8 -0
  47. data/lib/concurrent/delay.rb +14 -12
  48. data/lib/concurrent/exchanger.rb +339 -41
  49. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  50. data/lib/concurrent/executor/executor_service.rb +23 -359
  51. data/lib/concurrent/executor/immediate_executor.rb +3 -2
  52. data/lib/concurrent/executor/java_executor_service.rb +100 -0
  53. data/lib/concurrent/executor/java_single_thread_executor.rb +3 -3
  54. data/lib/concurrent/executor/java_thread_pool_executor.rb +3 -4
  55. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  56. data/lib/concurrent/executor/ruby_single_thread_executor.rb +10 -66
  57. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +25 -22
  58. data/lib/concurrent/executor/safe_task_executor.rb +6 -7
  59. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  60. data/lib/concurrent/executor/serialized_execution.rb +10 -33
  61. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  62. data/lib/concurrent/executor/simple_executor_service.rb +1 -10
  63. data/lib/concurrent/executor/single_thread_executor.rb +20 -10
  64. data/lib/concurrent/executor/timer_set.rb +8 -10
  65. data/lib/concurrent/executors.rb +12 -2
  66. data/lib/concurrent/future.rb +6 -4
  67. data/lib/concurrent/hash.rb +35 -0
  68. data/lib/concurrent/immutable_struct.rb +5 -1
  69. data/lib/concurrent/ivar.rb +12 -16
  70. data/lib/concurrent/lazy_register.rb +11 -8
  71. data/lib/concurrent/map.rb +180 -0
  72. data/lib/concurrent/maybe.rb +6 -3
  73. data/lib/concurrent/mutable_struct.rb +7 -6
  74. data/lib/concurrent/mvar.rb +26 -2
  75. data/lib/concurrent/{executor/executor.rb → options.rb} +5 -29
  76. data/lib/concurrent/promise.rb +7 -5
  77. data/lib/concurrent/scheduled_task.rb +13 -71
  78. data/lib/concurrent/settable_struct.rb +5 -4
  79. data/lib/concurrent/synchronization.rb +15 -3
  80. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  81. data/lib/concurrent/synchronization/abstract_object.rb +7 -146
  82. data/lib/concurrent/synchronization/abstract_struct.rb +2 -3
  83. data/lib/concurrent/synchronization/condition.rb +6 -4
  84. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  85. data/lib/concurrent/synchronization/jruby_object.rb +44 -0
  86. data/lib/concurrent/synchronization/lock.rb +3 -2
  87. data/lib/concurrent/synchronization/lockable_object.rb +72 -0
  88. data/lib/concurrent/synchronization/mri_lockable_object.rb +71 -0
  89. data/lib/concurrent/synchronization/mri_object.rb +43 -0
  90. data/lib/concurrent/synchronization/object.rb +140 -73
  91. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  92. data/lib/concurrent/synchronization/rbx_object.rb +30 -73
  93. data/lib/concurrent/synchronization/volatile.rb +34 -0
  94. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  95. data/lib/concurrent/thread_safe/util.rb +14 -0
  96. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  97. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +30 -0
  98. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  99. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  100. data/lib/concurrent/thread_safe/util/striped64.rb +241 -0
  101. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  102. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  103. data/lib/concurrent/timer_task.rb +3 -4
  104. data/lib/concurrent/tuple.rb +86 -0
  105. data/lib/concurrent/tvar.rb +5 -1
  106. data/lib/concurrent/utility/at_exit.rb +1 -1
  107. data/lib/concurrent/utility/engine.rb +4 -0
  108. data/lib/concurrent/utility/monotonic_time.rb +3 -4
  109. data/lib/concurrent/utility/native_extension_loader.rb +50 -30
  110. data/lib/concurrent/version.rb +2 -2
  111. data/lib/concurrent_ruby_ext.jar +0 -0
  112. metadata +47 -12
  113. data/lib/concurrent/atomic/condition.rb +0 -78
  114. data/lib/concurrent/collection/priority_queue.rb +0 -360
  115. data/lib/concurrent/synchronization/java_object.rb +0 -34
  116. data/lib/concurrent/synchronization/monitor_object.rb +0 -27
  117. data/lib/concurrent/synchronization/mutex_object.rb +0 -43
  118. data/lib/concurrent/utilities.rb +0 -5
  119. data/lib/concurrent/utility/timeout.rb +0 -39
  120. data/lib/concurrent/utility/timer.rb +0 -26
  121. data/lib/concurrent_ruby.rb +0 -2
@@ -0,0 +1,8 @@
1
+ module Concurrent
2
+
3
+ # Various classes within allows for +nil+ values to be stored,
4
+ # so a special +NULL+ token is required to indicate the "nil-ness".
5
+ # @!visibility private
6
+ NULL = Object.new
7
+
8
+ end
@@ -1,11 +1,12 @@
1
1
  require 'thread'
2
2
  require 'concurrent/concern/obligation'
3
- require 'concurrent/executor/executor'
4
3
  require 'concurrent/executor/immediate_executor'
5
4
  require 'concurrent/synchronization'
6
5
 
7
6
  module Concurrent
8
7
 
8
+ autoload :Options, 'concurrent/options'
9
+
9
10
  # Lazy evaluation of a block yielding an immutable result. Useful for
10
11
  # expensive operations that may never be needed. It may be non-blocking,
11
12
  # supports the `Concern::Obligation` interface, and accepts the injection of
@@ -39,7 +40,7 @@ module Concurrent
39
40
  # execute on the given executor, allowing the call to timeout.
40
41
  #
41
42
  # @see Concurrent::Concern::Dereferenceable
42
- class Delay < Synchronization::Object
43
+ class Delay < Synchronization::LockableObject
43
44
  include Concern::Obligation
44
45
 
45
46
  # NOTE: Because the global thread pools are lazy-loaded with these objects
@@ -73,12 +74,12 @@ module Concurrent
73
74
  #
74
75
  # @!macro delay_note_regarding_blocking
75
76
  def value(timeout = nil)
76
- if @task_executor
77
+ if @executor # TODO (pitr 12-Sep-2015): broken unsafe read?
77
78
  super
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
@@ -107,7 +108,7 @@ module Concurrent
107
108
  #
108
109
  # @!macro delay_note_regarding_blocking
109
110
  def value!(timeout = nil)
110
- if @task_executor
111
+ if @executor
111
112
  super
112
113
  else
113
114
  result = value
@@ -126,7 +127,7 @@ module Concurrent
126
127
  #
127
128
  # @!macro delay_note_regarding_blocking
128
129
  def wait(timeout = nil)
129
- if @task_executor
130
+ if @executor
130
131
  execute_task_once
131
132
  super(timeout)
132
133
  else
@@ -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
@@ -154,9 +155,9 @@ module Concurrent
154
155
  protected
155
156
 
156
157
  def ns_initialize(opts, &block)
157
- init_obligation(self)
158
+ init_obligation
158
159
  set_deref_options(opts)
159
- @task_executor = Executor.executor_from_options(opts)
160
+ @executor = opts[:executor]
160
161
 
161
162
  @task = block
162
163
  @state = :pending
@@ -170,20 +171,21 @@ 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
177
178
 
178
179
  if execute
179
- @task_executor.post do
180
+ executor = Options.executor_from_options(executor: @executor)
181
+ executor.post do
180
182
  begin
181
183
  result = task.call
182
184
  success = true
183
185
  rescue => ex
184
186
  reason = ex
185
187
  end
186
- mutex.synchronize do
188
+ synchronize do
187
189
  set_state(success, result, reason)
188
190
  event.set
189
191
  end
@@ -1,58 +1,356 @@
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
+
1
9
  module Concurrent
2
10
 
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.
11
+ # @!macro [attach] 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
6
27
  #
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
28
+ # @!macro edge_warning
15
29
  #
16
- # @!macro edge_warning
17
- class Exchanger
30
+ # @example
31
+ #
32
+ # exchanger = Concurrent::Exchanger.new
33
+ #
34
+ # threads = [
35
+ # Thread.new { puts "first: " << exchanger.exchange('foo', 1) }, #=> "first: bar"
36
+ # Thread.new { puts "second: " << exchanger.exchange('bar', 1) } #=> "second: foo"
37
+ # ]
38
+ # threads.each {|t| t.join(2) }
39
+ #
40
+ # @!visibility private
41
+ class AbstractExchanger < Synchronization::Object
18
42
 
19
- EMPTY = Object.new
43
+ # @!visibility private
44
+ CANCEL = Object.new
45
+ private_constant :CANCEL
20
46
 
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)
47
+ # @!macro [attach] exchanger_method_initialize
48
+ def initialize
49
+ super
28
50
  end
29
51
 
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.
52
+ # @!macro [attach] exchanger_method_do_exchange
53
+ #
54
+ # Waits for another thread to arrive at this exchange point (unless the
55
+ # current thread is interrupted), and then transfers the given object to
56
+ # it, receiving its object in return. The timeout value indicates the
57
+ # approximate number of seconds the method should block while waiting
58
+ # for the exchange. When the timeout value is `nil` the method will
59
+ # block indefinitely.
33
60
  #
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
61
+ # @param [Object] value the value to exchange with another thread
62
+ # @param [Numeric, nil] timeout in seconds, `nil` blocks indefinitely
63
+ #
64
+ # @!macro [attach] exchanger_method_exchange
65
+ #
66
+ # In some edge cases when a `timeout` is given a return value of `nil` may be
67
+ # ambiguous. Specifically, if `nil` is a valid value in the exchange it will
68
+ # be impossible to tell whether `nil` is the actual return value or if it
69
+ # signifies timeout. When `nil` is a valid value in the exchange consider
70
+ # using {#exchange!} or {#try_exchange} instead.
71
+ #
72
+ # @return [Object] the value exchanged by the other thread or `nil` on timeout
38
73
  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
74
+ (value = do_exchange(value, timeout)) == CANCEL ? nil : value
75
+ end
76
+
77
+ # @!macro exchanger_method_do_exchange
78
+ #
79
+ # @!macro [attach] exchanger_method_exchange_bang
80
+ #
81
+ # On timeout a {Concurrent::TimeoutError} exception will be raised.
82
+ #
83
+ # @return [Object] the value exchanged by the other thread
84
+ # @raise [Concurrent::TimeoutError] on timeout
85
+ def exchange!(value, timeout = nil)
86
+ if (value = do_exchange(value, timeout)) == CANCEL
87
+ raise Concurrent::TimeoutError
88
+ else
89
+ value
90
+ end
91
+ end
92
+
93
+ # @!macro exchanger_method_do_exchange
94
+ #
95
+ # @!macro [attach] exchanger_method_try_exchange
96
+ #
97
+ # The return value will be a {Concurrent::Maybe} set to `Just` on success or
98
+ # `Nothing` on timeout.
99
+ #
100
+ # @return [Concurrent::Maybe] on success a `Just` maybe will be returned with
101
+ # the item exchanged by the other thread as `#value`; on timeout a
102
+ # `Nothing` maybe will be returned with {Concurrent::TimeoutError} as `#reason`
103
+ #
104
+ # @example
105
+ #
106
+ # exchanger = Concurrent::Exchanger.new
107
+ #
108
+ # result = exchanger.exchange(:foo, 0.5)
109
+ #
110
+ # if result.just?
111
+ # puts result.value #=> :bar
112
+ # else
113
+ # puts 'timeout'
114
+ # end
115
+ def try_exchange(value, timeout = nil)
116
+ if (value = do_exchange(value, timeout)) == CANCEL
117
+ Concurrent::Maybe.nothing(Concurrent::TimeoutError)
118
+ else
119
+ Concurrent::Maybe.just(value)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ # @!macro exchanger_method_do_exchange
126
+ #
127
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
128
+ def do_exchange(value, timeout)
129
+ raise NotImplementedError
130
+ end
131
+ end
132
+
133
+ # @!macro exchanger
134
+ # @!macro internal_implementation_note
135
+ # @!visibility private
136
+ class RubyExchanger < AbstractExchanger
137
+ # A simplified version of java.util.concurrent.Exchanger written by
138
+ # Doug Lea, Bill Scherer, and Michael Scott with assistance from members
139
+ # of JCP JSR-166 Expert Group and released to the public domain. It does
140
+ # not include the arena or the multi-processor spin loops.
141
+ # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java
142
+
143
+ safe_initialization!
144
+
145
+ class Node < Concurrent::Synchronization::Object
146
+ attr_atomic :value
147
+ safe_initialization!
148
+
149
+ def initialize(item)
150
+ super()
151
+ @Item = item
152
+ @Latch = Concurrent::CountDownLatch.new
153
+ self.value = nil
154
+ end
155
+
156
+ def latch
157
+ @Latch
158
+ end
159
+
160
+ def item
161
+ @Item
162
+ end
163
+ end
164
+ private_constant :Node
165
+
166
+ # @!macro exchanger_method_initialize
167
+ def initialize
168
+ super
169
+ end
170
+
171
+ private
172
+
173
+ attr_atomic(:slot)
174
+
175
+ # @!macro exchanger_method_do_exchange
176
+ #
177
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
178
+ def do_exchange(value, timeout)
179
+
180
+ # ALGORITHM
181
+ #
182
+ # From the original Java version:
183
+ #
184
+ # > The basic idea is to maintain a "slot", which is a reference to
185
+ # > a Node containing both an Item to offer and a "hole" waiting to
186
+ # > get filled in. If an incoming "occupying" thread sees that the
187
+ # > slot is null, it CAS'es (compareAndSets) a Node there and waits
188
+ # > for another to invoke exchange. That second "fulfilling" thread
189
+ # > sees that the slot is non-null, and so CASes it back to null,
190
+ # > also exchanging items by CASing the hole, plus waking up the
191
+ # > occupying thread if it is blocked. In each case CAS'es may
192
+ # > fail because a slot at first appears non-null but is null upon
193
+ # > CAS, or vice-versa. So threads may need to retry these
194
+ # > actions.
195
+ #
196
+ # This version:
197
+ #
198
+ # An exchange occurs between an "occupier" thread and a "fulfiller" thread.
199
+ # The "slot" is used to setup this interaction. The first thread in the
200
+ # exchange puts itself into the slot (occupies) and waits for a fulfiller.
201
+ # The second thread removes the occupier from the slot and attempts to
202
+ # perform the exchange. Removing the occupier also frees the slot for
203
+ # another occupier/fulfiller pair.
204
+ #
205
+ # Because the occupier and the fulfiller are operating independently and
206
+ # because there may be contention with other threads, any failed operation
207
+ # indicates contention. Both the occupier and the fulfiller operate within
208
+ # spin loops. Any failed actions along the happy path will cause the thread
209
+ # to repeat the loop and try again.
210
+ #
211
+ # When a timeout value is given the thread must be cognizant of time spent
212
+ # in the spin loop. The remaining time is checked every loop. When the time
213
+ # runs out the thread will exit.
214
+ #
215
+ # A "node" is the data structure used to perform the exchange. Only the
216
+ # occupier's node is necessary. It's the node used for the exchange.
217
+ # Each node has an "item," a "hole" (self), and a "latch." The item is the
218
+ # node's initial value. It never changes. It's what the fulfiller returns on
219
+ # success. The occupier's hole is where the fulfiller put its item. It's the
220
+ # item that the occupier returns on success. The latch is used for synchronization.
221
+ # Becuase a thread may act as either an occupier or fulfiller (or possibly
222
+ # both in periods of high contention) every thread creates a node when
223
+ # the exchange method is first called.
224
+ #
225
+ # The following steps occur within the spin loop. If any actions fail
226
+ # the thread will loop and try again, so long as there is time remaining.
227
+ # If time runs out the thread will return CANCEL.
228
+ #
229
+ # Check the slot for an occupier:
230
+ #
231
+ # * If the slot is empty try to occupy
232
+ # * If the slot is full try to fulfill
233
+ #
234
+ # Attempt to occupy:
235
+ #
236
+ # * Attempt to CAS myself into the slot
237
+ # * Go to sleep and wait to be woken by a fulfiller
238
+ # * If the sleep is successful then the fulfiller completed its happy path
239
+ # - Return the value from my hole (the value given by the fulfiller)
240
+ # * When the sleep fails (time ran out) attempt to cancel the operation
241
+ # - Attempt to CAS myself out of the hole
242
+ # - If successful there is no contention
243
+ # - Return CANCEL
244
+ # - On failure, I am competing with a fulfiller
245
+ # - Attempt to CAS my hole to CANCEL
246
+ # - On success
247
+ # - Let the fulfiller deal with my cancel
248
+ # - Return CANCEL
249
+ # - On failure the fulfiller has completed its happy path
250
+ # - Return th value from my hole (the fulfiller's value)
251
+ #
252
+ # Attempt to fulfill:
253
+ #
254
+ # * Attempt to CAS the occupier out of the slot
255
+ # - On failure loop again
256
+ # * Attempt to CAS my item into the occupier's hole
257
+ # - On failure the occupier is trying to cancel
258
+ # - Loop again
259
+ # - On success we are on the happy path
260
+ # - Wake the sleeping occupier
261
+ # - Return the occupier's item
262
+
263
+ value = NULL if value.nil? # The sentinel allows nil to be a valid value
264
+ me = Node.new(value) # create my node in case I need to occupy
265
+ end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up
266
+
267
+ result = loop do
268
+ other = slot
269
+ if other && compare_and_set_slot(other, nil)
270
+ # try to fulfill
271
+ if other.compare_and_set_value(nil, value)
272
+ # happy path
273
+ other.latch.count_down
274
+ break other.item
275
+ end
276
+ elsif other.nil? && compare_and_set_slot(nil, me)
277
+ # try to occupy
278
+ timeout = end_at - Concurrent.monotonic_time if timeout
279
+ if me.latch.wait(timeout)
280
+ # happy path
281
+ break me.value
282
+ else
283
+ # attempt to remove myself from the slot
284
+ if compare_and_set_slot(me, nil)
285
+ break CANCEL
286
+ elsif !me.compare_and_set_value(nil, CANCEL)
287
+ # I've failed to block the fulfiller
288
+ break me.value
289
+ end
290
+ end
291
+ end
292
+ break CANCEL if timeout && Concurrent.monotonic_time >= end_at
293
+ end
294
+
295
+ result == NULL ? nil : result
296
+ end
297
+ end
298
+
299
+ if Concurrent.on_jruby?
300
+
301
+ # @!macro exchanger
302
+ # @!macro internal_implementation_note
303
+ # @!visibility private
304
+ class JavaExchanger < AbstractExchanger
305
+
306
+ # @!macro exchanger_method_initialize
307
+ def initialize
308
+ @exchanger = java.util.concurrent.Exchanger.new
309
+ end
310
+
311
+ private
312
+
313
+ # @!macro exchanger_method_do_exchange
314
+ #
315
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
316
+ def do_exchange(value, timeout)
317
+ if timeout.nil?
318
+ @exchanger.exchange(value)
47
319
  else
48
- second
320
+ @exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
49
321
  end
50
- else
51
- @first.put EMPTY
52
- @second.put value
53
- first
322
+ rescue java.util.concurrent.TimeoutException
323
+ CANCEL
54
324
  end
55
325
  end
326
+ end
327
+
328
+ # @!visibility private
329
+ # @!macro internal_implementation_note
330
+ ExchangerImplementation = case
331
+ when Concurrent.on_jruby?
332
+ JavaExchanger
333
+ else
334
+ RubyExchanger
335
+ end
336
+ private_constant :ExchangerImplementation
337
+
338
+ # @!macro exchanger
339
+ class Exchanger < ExchangerImplementation
340
+
341
+ # @!method initialize
342
+ # @!macro exchanger_method_initialize
343
+
344
+ # @!method exchange(value, timeout = nil)
345
+ # @!macro exchanger_method_do_exchange
346
+ # @!macro exchanger_method_exchange
347
+
348
+ # @!method exchange!(value, timeout = nil)
349
+ # @!macro exchanger_method_do_exchange
350
+ # @!macro exchanger_method_exchange_bang
56
351
 
352
+ # @!method try_exchange(value, timeout = nil)
353
+ # @!macro exchanger_method_do_exchange
354
+ # @!macro exchanger_method_try_exchange
57
355
  end
58
356
  end