concurrent-ruby 0.9.2-java → 1.0.0-java

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