concurrent-ruby 1.1.5

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 (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +478 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE.md +23 -0
  5. data/README.md +381 -0
  6. data/Rakefile +327 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby.rb +1 -0
  23. data/lib/concurrent.rb +134 -0
  24. data/lib/concurrent/agent.rb +587 -0
  25. data/lib/concurrent/array.rb +66 -0
  26. data/lib/concurrent/async.rb +459 -0
  27. data/lib/concurrent/atom.rb +222 -0
  28. data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  29. data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
  30. data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
  31. data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
  32. data/lib/concurrent/atomic/atomic_reference.rb +204 -0
  33. data/lib/concurrent/atomic/count_down_latch.rb +100 -0
  34. data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
  35. data/lib/concurrent/atomic/event.rb +109 -0
  36. data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
  37. data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
  38. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  39. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  40. data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  41. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  42. data/lib/concurrent/atomic/read_write_lock.rb +254 -0
  43. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
  44. data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
  45. data/lib/concurrent/atomic/semaphore.rb +145 -0
  46. data/lib/concurrent/atomic/thread_local_var.rb +104 -0
  47. data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  48. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  49. data/lib/concurrent/atomics.rb +10 -0
  50. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  51. data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  52. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  53. data/lib/concurrent/collection/lock_free_stack.rb +158 -0
  54. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  55. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  56. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  57. data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  58. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  59. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  60. data/lib/concurrent/concern/deprecation.rb +34 -0
  61. data/lib/concurrent/concern/dereferenceable.rb +73 -0
  62. data/lib/concurrent/concern/logging.rb +32 -0
  63. data/lib/concurrent/concern/obligation.rb +220 -0
  64. data/lib/concurrent/concern/observable.rb +110 -0
  65. data/lib/concurrent/concurrent_ruby.jar +0 -0
  66. data/lib/concurrent/configuration.rb +184 -0
  67. data/lib/concurrent/constants.rb +8 -0
  68. data/lib/concurrent/dataflow.rb +81 -0
  69. data/lib/concurrent/delay.rb +199 -0
  70. data/lib/concurrent/errors.rb +69 -0
  71. data/lib/concurrent/exchanger.rb +352 -0
  72. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  73. data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
  74. data/lib/concurrent/executor/executor_service.rb +185 -0
  75. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
  76. data/lib/concurrent/executor/immediate_executor.rb +66 -0
  77. data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
  78. data/lib/concurrent/executor/java_executor_service.rb +91 -0
  79. data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
  80. data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
  81. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
  84. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  85. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +107 -0
  87. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  88. data/lib/concurrent/executor/simple_executor_service.rb +100 -0
  89. data/lib/concurrent/executor/single_thread_executor.rb +56 -0
  90. data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
  91. data/lib/concurrent/executor/timer_set.rb +173 -0
  92. data/lib/concurrent/executors.rb +20 -0
  93. data/lib/concurrent/future.rb +141 -0
  94. data/lib/concurrent/hash.rb +59 -0
  95. data/lib/concurrent/immutable_struct.rb +93 -0
  96. data/lib/concurrent/ivar.rb +207 -0
  97. data/lib/concurrent/map.rb +337 -0
  98. data/lib/concurrent/maybe.rb +229 -0
  99. data/lib/concurrent/mutable_struct.rb +229 -0
  100. data/lib/concurrent/mvar.rb +242 -0
  101. data/lib/concurrent/options.rb +42 -0
  102. data/lib/concurrent/promise.rb +579 -0
  103. data/lib/concurrent/promises.rb +2167 -0
  104. data/lib/concurrent/re_include.rb +58 -0
  105. data/lib/concurrent/scheduled_task.rb +318 -0
  106. data/lib/concurrent/set.rb +66 -0
  107. data/lib/concurrent/settable_struct.rb +129 -0
  108. data/lib/concurrent/synchronization.rb +30 -0
  109. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  110. data/lib/concurrent/synchronization/abstract_object.rb +24 -0
  111. data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
  112. data/lib/concurrent/synchronization/condition.rb +60 -0
  113. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  114. data/lib/concurrent/synchronization/jruby_object.rb +45 -0
  115. data/lib/concurrent/synchronization/lock.rb +36 -0
  116. data/lib/concurrent/synchronization/lockable_object.rb +74 -0
  117. data/lib/concurrent/synchronization/mri_object.rb +44 -0
  118. data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
  119. data/lib/concurrent/synchronization/object.rb +183 -0
  120. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  121. data/lib/concurrent/synchronization/rbx_object.rb +49 -0
  122. data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
  123. data/lib/concurrent/synchronization/volatile.rb +36 -0
  124. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  125. data/lib/concurrent/thread_safe/util.rb +16 -0
  126. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  127. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  128. data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
  129. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  130. data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
  131. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  132. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  133. data/lib/concurrent/timer_task.rb +334 -0
  134. data/lib/concurrent/tuple.rb +86 -0
  135. data/lib/concurrent/tvar.rb +258 -0
  136. data/lib/concurrent/utility/at_exit.rb +97 -0
  137. data/lib/concurrent/utility/engine.rb +56 -0
  138. data/lib/concurrent/utility/monotonic_time.rb +58 -0
  139. data/lib/concurrent/utility/native_extension_loader.rb +79 -0
  140. data/lib/concurrent/utility/native_integer.rb +53 -0
  141. data/lib/concurrent/utility/processor_counter.rb +158 -0
  142. data/lib/concurrent/version.rb +3 -0
  143. metadata +193 -0
@@ -0,0 +1,69 @@
1
+ module Concurrent
2
+
3
+ Error = Class.new(StandardError)
4
+
5
+ # Raised when errors occur during configuration.
6
+ ConfigurationError = Class.new(Error)
7
+
8
+ # Raised when an asynchronous operation is cancelled before execution.
9
+ CancelledOperationError = Class.new(Error)
10
+
11
+ # Raised when a lifecycle method (such as `stop`) is called in an improper
12
+ # sequence or when the object is in an inappropriate state.
13
+ LifecycleError = Class.new(Error)
14
+
15
+ # Raised when an attempt is made to violate an immutability guarantee.
16
+ ImmutabilityError = Class.new(Error)
17
+
18
+ # Raised when an operation is attempted which is not legal given the
19
+ # receiver's current state
20
+ IllegalOperationError = Class.new(Error)
21
+
22
+ # Raised when an object's methods are called when it has not been
23
+ # properly initialized.
24
+ InitializationError = Class.new(Error)
25
+
26
+ # Raised when an object with a start/stop lifecycle has been started an
27
+ # excessive number of times. Often used in conjunction with a restart
28
+ # policy or strategy.
29
+ MaxRestartFrequencyError = Class.new(Error)
30
+
31
+ # Raised when an attempt is made to modify an immutable object
32
+ # (such as an `IVar`) after its final state has been set.
33
+ class MultipleAssignmentError < Error
34
+ attr_reader :inspection_data
35
+
36
+ def initialize(message = nil, inspection_data = nil)
37
+ @inspection_data = inspection_data
38
+ super message
39
+ end
40
+
41
+ def inspect
42
+ format '%s %s>', super[0..-2], @inspection_data.inspect
43
+ end
44
+ end
45
+
46
+ # Raised by an `Executor` when it is unable to process a given task,
47
+ # possibly because of a reject policy or other internal error.
48
+ RejectedExecutionError = Class.new(Error)
49
+
50
+ # Raised when any finite resource, such as a lock counter, exceeds its
51
+ # maximum limit/threshold.
52
+ ResourceLimitError = Class.new(Error)
53
+
54
+ # Raised when an operation times out.
55
+ TimeoutError = Class.new(Error)
56
+
57
+ # Aggregates multiple exceptions.
58
+ class MultipleErrors < Error
59
+ attr_reader :errors
60
+
61
+ def initialize(errors, message = "#{errors.size} errors")
62
+ @errors = errors
63
+ super [*message,
64
+ *errors.map { |e| [format('%s (%s)', e.message, e.class), *e.backtrace] }.flatten(1)
65
+ ].join("\n")
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,352 @@
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
+
9
+ module Concurrent
10
+
11
+ # @!macro 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
27
+ # @example
28
+ #
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 < Synchronization::Object
39
+
40
+ # @!visibility private
41
+ CANCEL = ::Object.new
42
+ private_constant :CANCEL
43
+
44
+ def initialize
45
+ super
46
+ end
47
+
48
+ # @!macro exchanger_method_do_exchange
49
+ #
50
+ # Waits for another thread to arrive at this exchange point (unless the
51
+ # current thread is interrupted), and then transfers the given object to
52
+ # it, receiving its object in return. The timeout value indicates the
53
+ # approximate number of seconds the method should block while waiting
54
+ # for the exchange. When the timeout value is `nil` the method will
55
+ # block indefinitely.
56
+ #
57
+ # @param [Object] value the value to exchange with another thread
58
+ # @param [Numeric, nil] timeout in seconds, `nil` blocks indefinitely
59
+ #
60
+ # @!macro exchanger_method_exchange
61
+ #
62
+ # In some edge cases when a `timeout` is given a return value of `nil` may be
63
+ # ambiguous. Specifically, if `nil` is a valid value in the exchange it will
64
+ # be impossible to tell whether `nil` is the actual return value or if it
65
+ # signifies timeout. When `nil` is a valid value in the exchange consider
66
+ # using {#exchange!} or {#try_exchange} instead.
67
+ #
68
+ # @return [Object] the value exchanged by the other thread or `nil` on timeout
69
+ def exchange(value, timeout = nil)
70
+ (value = do_exchange(value, timeout)) == CANCEL ? nil : value
71
+ end
72
+
73
+ # @!macro exchanger_method_do_exchange
74
+ # @!macro exchanger_method_exchange_bang
75
+ #
76
+ # On timeout a {Concurrent::TimeoutError} exception will be raised.
77
+ #
78
+ # @return [Object] the value exchanged by the other thread
79
+ # @raise [Concurrent::TimeoutError] on timeout
80
+ def exchange!(value, timeout = nil)
81
+ if (value = do_exchange(value, timeout)) == CANCEL
82
+ raise Concurrent::TimeoutError
83
+ else
84
+ value
85
+ end
86
+ end
87
+
88
+ # @!macro exchanger_method_do_exchange
89
+ # @!macro exchanger_method_try_exchange
90
+ #
91
+ # The return value will be a {Concurrent::Maybe} set to `Just` on success or
92
+ # `Nothing` on timeout.
93
+ #
94
+ # @return [Concurrent::Maybe] on success a `Just` maybe will be returned with
95
+ # the item exchanged by the other thread as `#value`; on timeout a
96
+ # `Nothing` maybe will be returned with {Concurrent::TimeoutError} as `#reason`
97
+ #
98
+ # @example
99
+ #
100
+ # exchanger = Concurrent::Exchanger.new
101
+ #
102
+ # result = exchanger.exchange(:foo, 0.5)
103
+ #
104
+ # if result.just?
105
+ # puts result.value #=> :bar
106
+ # else
107
+ # puts 'timeout'
108
+ # end
109
+ def try_exchange(value, timeout = nil)
110
+ if (value = do_exchange(value, timeout)) == CANCEL
111
+ Concurrent::Maybe.nothing(Concurrent::TimeoutError)
112
+ else
113
+ Concurrent::Maybe.just(value)
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ # @!macro exchanger_method_do_exchange
120
+ #
121
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
122
+ def do_exchange(value, timeout)
123
+ raise NotImplementedError
124
+ end
125
+ end
126
+
127
+ # @!macro internal_implementation_note
128
+ # @!visibility private
129
+ class RubyExchanger < AbstractExchanger
130
+ # A simplified version of java.util.concurrent.Exchanger written by
131
+ # Doug Lea, Bill Scherer, and Michael Scott with assistance from members
132
+ # of JCP JSR-166 Expert Group and released to the public domain. It does
133
+ # not include the arena or the multi-processor spin loops.
134
+ # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java
135
+
136
+ safe_initialization!
137
+
138
+ class Node < Concurrent::Synchronization::Object
139
+ attr_atomic :value
140
+ safe_initialization!
141
+
142
+ def initialize(item)
143
+ super()
144
+ @Item = item
145
+ @Latch = Concurrent::CountDownLatch.new
146
+ self.value = nil
147
+ end
148
+
149
+ def latch
150
+ @Latch
151
+ end
152
+
153
+ def item
154
+ @Item
155
+ end
156
+ end
157
+ private_constant :Node
158
+
159
+ def initialize
160
+ super
161
+ end
162
+
163
+ private
164
+
165
+ attr_atomic(:slot)
166
+
167
+ # @!macro exchanger_method_do_exchange
168
+ #
169
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
170
+ def do_exchange(value, timeout)
171
+
172
+ # ALGORITHM
173
+ #
174
+ # From the original Java version:
175
+ #
176
+ # > The basic idea is to maintain a "slot", which is a reference to
177
+ # > a Node containing both an Item to offer and a "hole" waiting to
178
+ # > get filled in. If an incoming "occupying" thread sees that the
179
+ # > slot is null, it CAS'es (compareAndSets) a Node there and waits
180
+ # > for another to invoke exchange. That second "fulfilling" thread
181
+ # > sees that the slot is non-null, and so CASes it back to null,
182
+ # > also exchanging items by CASing the hole, plus waking up the
183
+ # > occupying thread if it is blocked. In each case CAS'es may
184
+ # > fail because a slot at first appears non-null but is null upon
185
+ # > CAS, or vice-versa. So threads may need to retry these
186
+ # > actions.
187
+ #
188
+ # This version:
189
+ #
190
+ # An exchange occurs between an "occupier" thread and a "fulfiller" thread.
191
+ # The "slot" is used to setup this interaction. The first thread in the
192
+ # exchange puts itself into the slot (occupies) and waits for a fulfiller.
193
+ # The second thread removes the occupier from the slot and attempts to
194
+ # perform the exchange. Removing the occupier also frees the slot for
195
+ # another occupier/fulfiller pair.
196
+ #
197
+ # Because the occupier and the fulfiller are operating independently and
198
+ # because there may be contention with other threads, any failed operation
199
+ # indicates contention. Both the occupier and the fulfiller operate within
200
+ # spin loops. Any failed actions along the happy path will cause the thread
201
+ # to repeat the loop and try again.
202
+ #
203
+ # When a timeout value is given the thread must be cognizant of time spent
204
+ # in the spin loop. The remaining time is checked every loop. When the time
205
+ # runs out the thread will exit.
206
+ #
207
+ # A "node" is the data structure used to perform the exchange. Only the
208
+ # occupier's node is necessary. It's the node used for the exchange.
209
+ # Each node has an "item," a "hole" (self), and a "latch." The item is the
210
+ # node's initial value. It never changes. It's what the fulfiller returns on
211
+ # success. The occupier's hole is where the fulfiller put its item. It's the
212
+ # item that the occupier returns on success. The latch is used for synchronization.
213
+ # Because a thread may act as either an occupier or fulfiller (or possibly
214
+ # both in periods of high contention) every thread creates a node when
215
+ # the exchange method is first called.
216
+ #
217
+ # The following steps occur within the spin loop. If any actions fail
218
+ # the thread will loop and try again, so long as there is time remaining.
219
+ # If time runs out the thread will return CANCEL.
220
+ #
221
+ # Check the slot for an occupier:
222
+ #
223
+ # * If the slot is empty try to occupy
224
+ # * If the slot is full try to fulfill
225
+ #
226
+ # Attempt to occupy:
227
+ #
228
+ # * Attempt to CAS myself into the slot
229
+ # * Go to sleep and wait to be woken by a fulfiller
230
+ # * If the sleep is successful then the fulfiller completed its happy path
231
+ # - Return the value from my hole (the value given by the fulfiller)
232
+ # * When the sleep fails (time ran out) attempt to cancel the operation
233
+ # - Attempt to CAS myself out of the hole
234
+ # - If successful there is no contention
235
+ # - Return CANCEL
236
+ # - On failure, I am competing with a fulfiller
237
+ # - Attempt to CAS my hole to CANCEL
238
+ # - On success
239
+ # - Let the fulfiller deal with my cancel
240
+ # - Return CANCEL
241
+ # - On failure the fulfiller has completed its happy path
242
+ # - Return th value from my hole (the fulfiller's value)
243
+ #
244
+ # Attempt to fulfill:
245
+ #
246
+ # * Attempt to CAS the occupier out of the slot
247
+ # - On failure loop again
248
+ # * Attempt to CAS my item into the occupier's hole
249
+ # - On failure the occupier is trying to cancel
250
+ # - Loop again
251
+ # - On success we are on the happy path
252
+ # - Wake the sleeping occupier
253
+ # - Return the occupier's item
254
+
255
+ value = NULL if value.nil? # The sentinel allows nil to be a valid value
256
+ me = Node.new(value) # create my node in case I need to occupy
257
+ end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up
258
+
259
+ result = loop do
260
+ other = slot
261
+ if other && compare_and_set_slot(other, nil)
262
+ # try to fulfill
263
+ if other.compare_and_set_value(nil, value)
264
+ # happy path
265
+ other.latch.count_down
266
+ break other.item
267
+ end
268
+ elsif other.nil? && compare_and_set_slot(nil, me)
269
+ # try to occupy
270
+ timeout = end_at - Concurrent.monotonic_time if timeout
271
+ if me.latch.wait(timeout)
272
+ # happy path
273
+ break me.value
274
+ else
275
+ # attempt to remove myself from the slot
276
+ if compare_and_set_slot(me, nil)
277
+ break CANCEL
278
+ elsif !me.compare_and_set_value(nil, CANCEL)
279
+ # I've failed to block the fulfiller
280
+ break me.value
281
+ end
282
+ end
283
+ end
284
+ break CANCEL if timeout && Concurrent.monotonic_time >= end_at
285
+ end
286
+
287
+ result == NULL ? nil : result
288
+ end
289
+ end
290
+
291
+ if Concurrent.on_jruby?
292
+
293
+ # @!macro internal_implementation_note
294
+ # @!visibility private
295
+ class JavaExchanger < AbstractExchanger
296
+
297
+ def initialize
298
+ @exchanger = java.util.concurrent.Exchanger.new
299
+ end
300
+
301
+ private
302
+
303
+ # @!macro exchanger_method_do_exchange
304
+ #
305
+ # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
306
+ def do_exchange(value, timeout)
307
+ result = nil
308
+ if timeout.nil?
309
+ Synchronization::JRuby.sleep_interruptibly do
310
+ result = @exchanger.exchange(value)
311
+ end
312
+ else
313
+ Synchronization::JRuby.sleep_interruptibly do
314
+ result = @exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
315
+ end
316
+ end
317
+ result
318
+ rescue java.util.concurrent.TimeoutException
319
+ CANCEL
320
+ end
321
+ end
322
+ end
323
+
324
+ # @!visibility private
325
+ # @!macro internal_implementation_note
326
+ ExchangerImplementation = case
327
+ when Concurrent.on_jruby?
328
+ JavaExchanger
329
+ else
330
+ RubyExchanger
331
+ end
332
+ private_constant :ExchangerImplementation
333
+
334
+ # @!macro exchanger
335
+ class Exchanger < ExchangerImplementation
336
+
337
+ # @!method initialize
338
+ # Creates exchanger instance
339
+
340
+ # @!method exchange(value, timeout = nil)
341
+ # @!macro exchanger_method_do_exchange
342
+ # @!macro exchanger_method_exchange
343
+
344
+ # @!method exchange!(value, timeout = nil)
345
+ # @!macro exchanger_method_do_exchange
346
+ # @!macro exchanger_method_exchange_bang
347
+
348
+ # @!method try_exchange(value, timeout = nil)
349
+ # @!macro exchanger_method_do_exchange
350
+ # @!macro exchanger_method_try_exchange
351
+ end
352
+ end
@@ -0,0 +1,134 @@
1
+ require 'concurrent/errors'
2
+ require 'concurrent/executor/executor_service'
3
+ require 'concurrent/synchronization'
4
+ require 'concurrent/utility/at_exit'
5
+
6
+ module Concurrent
7
+
8
+ # @!macro abstract_executor_service_public_api
9
+ # @!visibility private
10
+ class AbstractExecutorService < Synchronization::LockableObject
11
+ include ExecutorService
12
+
13
+ # The set of possible fallback policies that may be set at thread pool creation.
14
+ FALLBACK_POLICIES = [:abort, :discard, :caller_runs].freeze
15
+
16
+ # @!macro executor_service_attr_reader_fallback_policy
17
+ attr_reader :fallback_policy
18
+
19
+ # Create a new thread pool.
20
+ def initialize(*args, &block)
21
+ super(&nil)
22
+ synchronize { ns_initialize(*args, &block) }
23
+ end
24
+
25
+ # @!macro executor_service_method_shutdown
26
+ def shutdown
27
+ raise NotImplementedError
28
+ end
29
+
30
+ # @!macro executor_service_method_kill
31
+ def kill
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # @!macro executor_service_method_wait_for_termination
36
+ def wait_for_termination(timeout = nil)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # @!macro executor_service_method_running_question
41
+ def running?
42
+ synchronize { ns_running? }
43
+ end
44
+
45
+ # @!macro executor_service_method_shuttingdown_question
46
+ def shuttingdown?
47
+ synchronize { ns_shuttingdown? }
48
+ end
49
+
50
+ # @!macro executor_service_method_shutdown_question
51
+ def shutdown?
52
+ synchronize { ns_shutdown? }
53
+ end
54
+
55
+ # @!macro executor_service_method_auto_terminate_question
56
+ def auto_terminate?
57
+ synchronize { ns_auto_terminate? }
58
+ end
59
+
60
+ # @!macro executor_service_method_auto_terminate_setter
61
+ def auto_terminate=(value)
62
+ synchronize { self.ns_auto_terminate = value }
63
+ end
64
+
65
+ private
66
+
67
+ # Handler which executes the `fallback_policy` once the queue size
68
+ # reaches `max_queue`.
69
+ #
70
+ # @param [Array] args the arguments to the task which is being handled.
71
+ #
72
+ # @!visibility private
73
+ def handle_fallback(*args)
74
+ case fallback_policy
75
+ when :abort
76
+ raise RejectedExecutionError
77
+ when :discard
78
+ false
79
+ when :caller_runs
80
+ begin
81
+ yield(*args)
82
+ rescue => ex
83
+ # let it fail
84
+ log DEBUG, ex
85
+ end
86
+ true
87
+ else
88
+ fail "Unknown fallback policy #{fallback_policy}"
89
+ end
90
+ end
91
+
92
+ def ns_execute(*args, &task)
93
+ raise NotImplementedError
94
+ end
95
+
96
+ # @!macro executor_service_method_ns_shutdown_execution
97
+ #
98
+ # Callback method called when an orderly shutdown has completed.
99
+ # The default behavior is to signal all waiting threads.
100
+ def ns_shutdown_execution
101
+ # do nothing
102
+ end
103
+
104
+ # @!macro executor_service_method_ns_kill_execution
105
+ #
106
+ # Callback method called when the executor has been killed.
107
+ # The default behavior is to do nothing.
108
+ def ns_kill_execution
109
+ # do nothing
110
+ end
111
+
112
+ def ns_auto_terminate?
113
+ !!@auto_terminate
114
+ end
115
+
116
+ def ns_auto_terminate=(value)
117
+ case value
118
+ when true
119
+ AtExit.add(self) { terminate_at_exit }
120
+ @auto_terminate = true
121
+ when false
122
+ AtExit.delete(self)
123
+ @auto_terminate = false
124
+ else
125
+ raise ArgumentError
126
+ end
127
+ end
128
+
129
+ def terminate_at_exit
130
+ kill # TODO be gentle first
131
+ wait_for_termination(10)
132
+ end
133
+ end
134
+ end