o-concurrent-ruby 1.1.11

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +542 -0
  3. data/Gemfile +37 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +404 -0
  6. data/Rakefile +307 -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 +189 -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/concurrent/agent.rb +587 -0
  23. data/lib/concurrent-ruby/concurrent/array.rb +66 -0
  24. data/lib/concurrent-ruby/concurrent/async.rb +449 -0
  25. data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
  26. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  27. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
  28. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
  29. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
  30. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
  31. data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
  32. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
  33. data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
  34. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
  35. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
  36. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  37. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  38. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  39. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
  40. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
  41. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
  42. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
  43. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
  44. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
  45. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  46. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  47. data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
  48. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  49. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  50. data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  51. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
  52. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  53. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
  54. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  55. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  56. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  57. data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  58. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
  59. data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
  60. data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
  61. data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
  62. data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
  63. data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
  64. data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
  65. data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
  66. data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
  67. data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
  68. data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
  69. data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
  70. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
  71. data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
  72. data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
  73. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
  74. data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
  75. data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
  76. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
  77. data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
  78. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
  79. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
  80. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
  81. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
  82. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
  83. data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
  84. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
  85. data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
  86. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
  87. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
  88. data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
  89. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
  90. data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
  91. data/lib/concurrent-ruby/concurrent/future.rb +141 -0
  92. data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
  93. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
  94. data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
  95. data/lib/concurrent-ruby/concurrent/map.rb +346 -0
  96. data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
  97. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
  98. data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
  99. data/lib/concurrent-ruby/concurrent/options.rb +42 -0
  100. data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
  101. data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
  102. data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
  103. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
  104. data/lib/concurrent-ruby/concurrent/set.rb +74 -0
  105. data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
  106. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  107. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
  108. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
  109. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
  110. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  111. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
  112. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
  113. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
  114. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
  115. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
  116. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
  117. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
  118. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
  119. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
  120. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
  121. data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
  122. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  123. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
  124. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  125. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
  126. data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  127. data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
  128. data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
  129. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  130. data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
  131. data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
  132. data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
  133. data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
  134. data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
  135. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
  136. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
  137. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
  138. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
  139. data/lib/concurrent-ruby/concurrent/version.rb +3 -0
  140. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
  141. data/lib/concurrent-ruby/concurrent.rb +134 -0
  142. metadata +192 -0
@@ -0,0 +1,587 @@
1
+ require 'concurrent/configuration'
2
+ require 'concurrent/atomic/atomic_reference'
3
+ require 'concurrent/atomic/thread_local_var'
4
+ require 'concurrent/collection/copy_on_write_observer_set'
5
+ require 'concurrent/concern/observable'
6
+ require 'concurrent/synchronization'
7
+
8
+ module Concurrent
9
+
10
+ # `Agent` is inspired by Clojure's [agent](http://clojure.org/agents)
11
+ # function. An agent is a shared, mutable variable providing independent,
12
+ # uncoordinated, *asynchronous* change of individual values. Best used when
13
+ # the value will undergo frequent, complex updates. Suitable when the result
14
+ # of an update does not need to be known immediately. `Agent` is (mostly)
15
+ # functionally equivalent to Clojure's agent, except where the runtime
16
+ # prevents parity.
17
+ #
18
+ # Agents are reactive, not autonomous - there is no imperative message loop
19
+ # and no blocking receive. The state of an Agent should be itself immutable
20
+ # and the `#value` of an Agent is always immediately available for reading by
21
+ # any thread without any messages, i.e. observation does not require
22
+ # cooperation or coordination.
23
+ #
24
+ # Agent action dispatches are made using the various `#send` methods. These
25
+ # methods always return immediately. At some point later, in another thread,
26
+ # the following will happen:
27
+ #
28
+ # 1. The given `action` will be applied to the state of the Agent and the
29
+ # `args`, if any were supplied.
30
+ # 2. The return value of `action` will be passed to the validator lambda,
31
+ # if one has been set on the Agent.
32
+ # 3. If the validator succeeds or if no validator was given, the return value
33
+ # of the given `action` will become the new `#value` of the Agent. See
34
+ # `#initialize` for details.
35
+ # 4. If any observers were added to the Agent, they will be notified. See
36
+ # `#add_observer` for details.
37
+ # 5. If during the `action` execution any other dispatches are made (directly
38
+ # or indirectly), they will be held until after the `#value` of the Agent
39
+ # has been changed.
40
+ #
41
+ # If any exceptions are thrown by an action function, no nested dispatches
42
+ # will occur, and the exception will be cached in the Agent itself. When an
43
+ # Agent has errors cached, any subsequent interactions will immediately throw
44
+ # an exception, until the agent's errors are cleared. Agent errors can be
45
+ # examined with `#error` and the agent restarted with `#restart`.
46
+ #
47
+ # The actions of all Agents get interleaved amongst threads in a thread pool.
48
+ # At any point in time, at most one action for each Agent is being executed.
49
+ # Actions dispatched to an agent from another single agent or thread will
50
+ # occur in the order they were sent, potentially interleaved with actions
51
+ # dispatched to the same agent from other sources. The `#send` method should
52
+ # be used for actions that are CPU limited, while the `#send_off` method is
53
+ # appropriate for actions that may block on IO.
54
+ #
55
+ # Unlike in Clojure, `Agent` cannot participate in `Concurrent::TVar` transactions.
56
+ #
57
+ # ## Example
58
+ #
59
+ # ```
60
+ # def next_fibonacci(set = nil)
61
+ # return [0, 1] if set.nil?
62
+ # set + [set[-2..-1].reduce{|sum,x| sum + x }]
63
+ # end
64
+ #
65
+ # # create an agent with an initial value
66
+ # agent = Concurrent::Agent.new(next_fibonacci)
67
+ #
68
+ # # send a few update requests
69
+ # 5.times do
70
+ # agent.send{|set| next_fibonacci(set) }
71
+ # end
72
+ #
73
+ # # wait for them to complete
74
+ # agent.await
75
+ #
76
+ # # get the current value
77
+ # agent.value #=> [0, 1, 1, 2, 3, 5, 8]
78
+ # ```
79
+ #
80
+ # ## Observation
81
+ #
82
+ # Agents support observers through the {Concurrent::Observable} mixin module.
83
+ # Notification of observers occurs every time an action dispatch returns and
84
+ # the new value is successfully validated. Observation will *not* occur if the
85
+ # action raises an exception, if validation fails, or when a {#restart} occurs.
86
+ #
87
+ # When notified the observer will receive three arguments: `time`, `old_value`,
88
+ # and `new_value`. The `time` argument is the time at which the value change
89
+ # occurred. The `old_value` is the value of the Agent when the action began
90
+ # processing. The `new_value` is the value to which the Agent was set when the
91
+ # action completed. Note that `old_value` and `new_value` may be the same.
92
+ # This is not an error. It simply means that the action returned the same
93
+ # value.
94
+ #
95
+ # ## Nested Actions
96
+ #
97
+ # It is possible for an Agent action to post further actions back to itself.
98
+ # The nested actions will be enqueued normally then processed *after* the
99
+ # outer action completes, in the order they were sent, possibly interleaved
100
+ # with action dispatches from other threads. Nested actions never deadlock
101
+ # with one another and a failure in a nested action will never affect the
102
+ # outer action.
103
+ #
104
+ # Nested actions can be called using the Agent reference from the enclosing
105
+ # scope or by passing the reference in as a "send" argument. Nested actions
106
+ # cannot be post using `self` from within the action block/proc/lambda; `self`
107
+ # in this context will not reference the Agent. The preferred method for
108
+ # dispatching nested actions is to pass the Agent as an argument. This allows
109
+ # Ruby to more effectively manage the closing scope.
110
+ #
111
+ # Prefer this:
112
+ #
113
+ # ```
114
+ # agent = Concurrent::Agent.new(0)
115
+ # agent.send(agent) do |value, this|
116
+ # this.send {|v| v + 42 }
117
+ # 3.14
118
+ # end
119
+ # agent.value #=> 45.14
120
+ # ```
121
+ #
122
+ # Over this:
123
+ #
124
+ # ```
125
+ # agent = Concurrent::Agent.new(0)
126
+ # agent.send do |value|
127
+ # agent.send {|v| v + 42 }
128
+ # 3.14
129
+ # end
130
+ # ```
131
+ #
132
+ # @!macro agent_await_warning
133
+ #
134
+ # **NOTE** Never, *under any circumstances*, call any of the "await" methods
135
+ # ({#await}, {#await_for}, {#await_for!}, and {#wait}) from within an action
136
+ # block/proc/lambda. The call will block the Agent and will always fail.
137
+ # Calling either {#await} or {#wait} (with a timeout of `nil`) will
138
+ # hopelessly deadlock the Agent with no possibility of recovery.
139
+ #
140
+ # @!macro thread_safe_variable_comparison
141
+ #
142
+ # @see http://clojure.org/Agents Clojure Agents
143
+ # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State
144
+ class Agent < Synchronization::LockableObject
145
+ include Concern::Observable
146
+
147
+ ERROR_MODES = [:continue, :fail].freeze
148
+ private_constant :ERROR_MODES
149
+
150
+ AWAIT_FLAG = ::Object.new
151
+ private_constant :AWAIT_FLAG
152
+
153
+ AWAIT_ACTION = ->(value, latch) { latch.count_down; AWAIT_FLAG }
154
+ private_constant :AWAIT_ACTION
155
+
156
+ DEFAULT_ERROR_HANDLER = ->(agent, error) { nil }
157
+ private_constant :DEFAULT_ERROR_HANDLER
158
+
159
+ DEFAULT_VALIDATOR = ->(value) { true }
160
+ private_constant :DEFAULT_VALIDATOR
161
+
162
+ Job = Struct.new(:action, :args, :executor, :caller)
163
+ private_constant :Job
164
+
165
+ # Raised during action processing or any other time in an Agent's lifecycle.
166
+ class Error < StandardError
167
+ def initialize(message = nil)
168
+ message ||= 'agent must be restarted before jobs can post'
169
+ super(message)
170
+ end
171
+ end
172
+
173
+ # Raised when a new value obtained during action processing or at `#restart`
174
+ # fails validation.
175
+ class ValidationError < Error
176
+ def initialize(message = nil)
177
+ message ||= 'invalid value'
178
+ super(message)
179
+ end
180
+ end
181
+
182
+ # The error mode this Agent is operating in. See {#initialize} for details.
183
+ attr_reader :error_mode
184
+
185
+ # Create a new `Agent` with the given initial value and options.
186
+ #
187
+ # The `:validator` option must be `nil` or a side-effect free proc/lambda
188
+ # which takes one argument. On any intended value change the validator, if
189
+ # provided, will be called. If the new value is invalid the validator should
190
+ # return `false` or raise an error.
191
+ #
192
+ # The `:error_handler` option must be `nil` or a proc/lambda which takes two
193
+ # arguments. When an action raises an error or validation fails, either by
194
+ # returning false or raising an error, the error handler will be called. The
195
+ # arguments to the error handler will be a reference to the agent itself and
196
+ # the error object which was raised.
197
+ #
198
+ # The `:error_mode` may be either `:continue` (the default if an error
199
+ # handler is given) or `:fail` (the default if error handler nil or not
200
+ # given).
201
+ #
202
+ # If an action being run by the agent throws an error or doesn't pass
203
+ # validation the error handler, if present, will be called. After the
204
+ # handler executes if the error mode is `:continue` the Agent will continue
205
+ # as if neither the action that caused the error nor the error itself ever
206
+ # happened.
207
+ #
208
+ # If the mode is `:fail` the Agent will become {#failed?} and will stop
209
+ # accepting new action dispatches. Any previously queued actions will be
210
+ # held until {#restart} is called. The {#value} method will still work,
211
+ # returning the value of the Agent before the error.
212
+ #
213
+ # @param [Object] initial the initial value
214
+ # @param [Hash] opts the configuration options
215
+ #
216
+ # @option opts [Symbol] :error_mode either `:continue` or `:fail`
217
+ # @option opts [nil, Proc] :error_handler the (optional) error handler
218
+ # @option opts [nil, Proc] :validator the (optional) validation procedure
219
+ def initialize(initial, opts = {})
220
+ super()
221
+ synchronize { ns_initialize(initial, opts) }
222
+ end
223
+
224
+ # The current value (state) of the Agent, irrespective of any pending or
225
+ # in-progress actions. The value is always available and is non-blocking.
226
+ #
227
+ # @return [Object] the current value
228
+ def value
229
+ @current.value # TODO (pitr 12-Sep-2015): broken unsafe read?
230
+ end
231
+
232
+ alias_method :deref, :value
233
+
234
+ # When {#failed?} and {#error_mode} is `:fail`, returns the error object
235
+ # which caused the failure, else `nil`. When {#error_mode} is `:continue`
236
+ # will *always* return `nil`.
237
+ #
238
+ # @return [nil, Error] the error which caused the failure when {#failed?}
239
+ def error
240
+ @error.value
241
+ end
242
+
243
+ alias_method :reason, :error
244
+
245
+ # @!macro agent_send
246
+ #
247
+ # Dispatches an action to the Agent and returns immediately. Subsequently,
248
+ # in a thread from a thread pool, the {#value} will be set to the return
249
+ # value of the action. Action dispatches are only allowed when the Agent
250
+ # is not {#failed?}.
251
+ #
252
+ # The action must be a block/proc/lambda which takes 1 or more arguments.
253
+ # The first argument is the current {#value} of the Agent. Any arguments
254
+ # passed to the send method via the `args` parameter will be passed to the
255
+ # action as the remaining arguments. The action must return the new value
256
+ # of the Agent.
257
+ #
258
+ # * {#send} and {#send!} should be used for actions that are CPU limited
259
+ # * {#send_off}, {#send_off!}, and {#<<} are appropriate for actions that
260
+ # may block on IO
261
+ # * {#send_via} and {#send_via!} are used when a specific executor is to
262
+ # be used for the action
263
+ #
264
+ # @param [Array<Object>] args zero or more arguments to be passed to
265
+ # the action
266
+ # @param [Proc] action the action dispatch to be enqueued
267
+ #
268
+ # @yield [agent, value, *args] process the old value and return the new
269
+ # @yieldparam [Object] value the current {#value} of the Agent
270
+ # @yieldparam [Array<Object>] args zero or more arguments to pass to the
271
+ # action
272
+ # @yieldreturn [Object] the new value of the Agent
273
+ #
274
+ # @!macro send_return
275
+ # @return [Boolean] true if the action is successfully enqueued, false if
276
+ # the Agent is {#failed?}
277
+ def send(*args, &action)
278
+ enqueue_action_job(action, args, Concurrent.global_fast_executor)
279
+ end
280
+
281
+ # @!macro agent_send
282
+ #
283
+ # @!macro send_bang_return_and_raise
284
+ # @return [Boolean] true if the action is successfully enqueued
285
+ # @raise [Concurrent::Agent::Error] if the Agent is {#failed?}
286
+ def send!(*args, &action)
287
+ raise Error.new unless send(*args, &action)
288
+ true
289
+ end
290
+
291
+ # @!macro agent_send
292
+ # @!macro send_return
293
+ def send_off(*args, &action)
294
+ enqueue_action_job(action, args, Concurrent.global_io_executor)
295
+ end
296
+
297
+ alias_method :post, :send_off
298
+
299
+ # @!macro agent_send
300
+ # @!macro send_bang_return_and_raise
301
+ def send_off!(*args, &action)
302
+ raise Error.new unless send_off(*args, &action)
303
+ true
304
+ end
305
+
306
+ # @!macro agent_send
307
+ # @!macro send_return
308
+ # @param [Concurrent::ExecutorService] executor the executor on which the
309
+ # action is to be dispatched
310
+ def send_via(executor, *args, &action)
311
+ enqueue_action_job(action, args, executor)
312
+ end
313
+
314
+ # @!macro agent_send
315
+ # @!macro send_bang_return_and_raise
316
+ # @param [Concurrent::ExecutorService] executor the executor on which the
317
+ # action is to be dispatched
318
+ def send_via!(executor, *args, &action)
319
+ raise Error.new unless send_via(executor, *args, &action)
320
+ true
321
+ end
322
+
323
+ # Dispatches an action to the Agent and returns immediately. Subsequently,
324
+ # in a thread from a thread pool, the {#value} will be set to the return
325
+ # value of the action. Appropriate for actions that may block on IO.
326
+ #
327
+ # @param [Proc] action the action dispatch to be enqueued
328
+ # @return [Concurrent::Agent] self
329
+ # @see #send_off
330
+ def <<(action)
331
+ send_off(&action)
332
+ self
333
+ end
334
+
335
+ # Blocks the current thread (indefinitely!) until all actions dispatched
336
+ # thus far, from this thread or nested by the Agent, have occurred. Will
337
+ # block when {#failed?}. Will never return if a failed Agent is {#restart}
338
+ # with `:clear_actions` true.
339
+ #
340
+ # Returns a reference to `self` to support method chaining:
341
+ #
342
+ # ```
343
+ # current_value = agent.await.value
344
+ # ```
345
+ #
346
+ # @return [Boolean] self
347
+ #
348
+ # @!macro agent_await_warning
349
+ def await
350
+ wait(nil)
351
+ self
352
+ end
353
+
354
+ # Blocks the current thread until all actions dispatched thus far, from this
355
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
356
+ # has elapsed.
357
+ #
358
+ # @param [Float] timeout the maximum number of seconds to wait
359
+ # @return [Boolean] true if all actions complete before timeout else false
360
+ #
361
+ # @!macro agent_await_warning
362
+ def await_for(timeout)
363
+ wait(timeout.to_f)
364
+ end
365
+
366
+ # Blocks the current thread until all actions dispatched thus far, from this
367
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
368
+ # has elapsed.
369
+ #
370
+ # @param [Float] timeout the maximum number of seconds to wait
371
+ # @return [Boolean] true if all actions complete before timeout
372
+ #
373
+ # @raise [Concurrent::TimeoutError] when timout is reached
374
+ #
375
+ # @!macro agent_await_warning
376
+ def await_for!(timeout)
377
+ raise Concurrent::TimeoutError unless wait(timeout.to_f)
378
+ true
379
+ end
380
+
381
+ # Blocks the current thread until all actions dispatched thus far, from this
382
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
383
+ # has elapsed. Will block indefinitely when timeout is nil or not given.
384
+ #
385
+ # Provided mainly for consistency with other classes in this library. Prefer
386
+ # the various `await` methods instead.
387
+ #
388
+ # @param [Float] timeout the maximum number of seconds to wait
389
+ # @return [Boolean] true if all actions complete before timeout else false
390
+ #
391
+ # @!macro agent_await_warning
392
+ def wait(timeout = nil)
393
+ latch = Concurrent::CountDownLatch.new(1)
394
+ enqueue_await_job(latch)
395
+ latch.wait(timeout)
396
+ end
397
+
398
+ # Is the Agent in a failed state?
399
+ #
400
+ # @see #restart
401
+ def failed?
402
+ !@error.value.nil?
403
+ end
404
+
405
+ alias_method :stopped?, :failed?
406
+
407
+ # When an Agent is {#failed?}, changes the Agent {#value} to `new_value`
408
+ # then un-fails the Agent so that action dispatches are allowed again. If
409
+ # the `:clear_actions` option is give and true, any actions queued on the
410
+ # Agent that were being held while it was failed will be discarded,
411
+ # otherwise those held actions will proceed. The `new_value` must pass the
412
+ # validator if any, or `restart` will raise an exception and the Agent will
413
+ # remain failed with its old {#value} and {#error}. Observers, if any, will
414
+ # not be notified of the new state.
415
+ #
416
+ # @param [Object] new_value the new value for the Agent once restarted
417
+ # @param [Hash] opts the configuration options
418
+ # @option opts [Symbol] :clear_actions true if all enqueued but unprocessed
419
+ # actions should be discarded on restart, else false (default: false)
420
+ # @return [Boolean] true
421
+ #
422
+ # @raise [Concurrent:AgentError] when not failed
423
+ def restart(new_value, opts = {})
424
+ clear_actions = opts.fetch(:clear_actions, false)
425
+ synchronize do
426
+ raise Error.new('agent is not failed') unless failed?
427
+ raise ValidationError unless ns_validate(new_value)
428
+ @current.value = new_value
429
+ @error.value = nil
430
+ @queue.clear if clear_actions
431
+ ns_post_next_job unless @queue.empty?
432
+ end
433
+ true
434
+ end
435
+
436
+ class << self
437
+
438
+ # Blocks the current thread (indefinitely!) until all actions dispatched
439
+ # thus far to all the given Agents, from this thread or nested by the
440
+ # given Agents, have occurred. Will block when any of the agents are
441
+ # failed. Will never return if a failed Agent is restart with
442
+ # `:clear_actions` true.
443
+ #
444
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
445
+ # @return [Boolean] true
446
+ #
447
+ # @!macro agent_await_warning
448
+ def await(*agents)
449
+ agents.each { |agent| agent.await }
450
+ true
451
+ end
452
+
453
+ # Blocks the current thread until all actions dispatched thus far to all
454
+ # the given Agents, from this thread or nested by the given Agents, have
455
+ # occurred, or the timeout (in seconds) has elapsed.
456
+ #
457
+ # @param [Float] timeout the maximum number of seconds to wait
458
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
459
+ # @return [Boolean] true if all actions complete before timeout else false
460
+ #
461
+ # @!macro agent_await_warning
462
+ def await_for(timeout, *agents)
463
+ end_at = Concurrent.monotonic_time + timeout.to_f
464
+ ok = agents.length.times do |i|
465
+ break false if (delay = end_at - Concurrent.monotonic_time) < 0
466
+ break false unless agents[i].await_for(delay)
467
+ end
468
+ !!ok
469
+ end
470
+
471
+ # Blocks the current thread until all actions dispatched thus far to all
472
+ # the given Agents, from this thread or nested by the given Agents, have
473
+ # occurred, or the timeout (in seconds) has elapsed.
474
+ #
475
+ # @param [Float] timeout the maximum number of seconds to wait
476
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
477
+ # @return [Boolean] true if all actions complete before timeout
478
+ #
479
+ # @raise [Concurrent::TimeoutError] when timout is reached
480
+ # @!macro agent_await_warning
481
+ def await_for!(timeout, *agents)
482
+ raise Concurrent::TimeoutError unless await_for(timeout, *agents)
483
+ true
484
+ end
485
+ end
486
+
487
+ private
488
+
489
+ def ns_initialize(initial, opts)
490
+ @error_mode = opts[:error_mode]
491
+ @error_handler = opts[:error_handler]
492
+
493
+ if @error_mode && !ERROR_MODES.include?(@error_mode)
494
+ raise ArgumentError.new('unrecognized error mode')
495
+ elsif @error_mode.nil?
496
+ @error_mode = @error_handler ? :continue : :fail
497
+ end
498
+
499
+ @error_handler ||= DEFAULT_ERROR_HANDLER
500
+ @validator = opts.fetch(:validator, DEFAULT_VALIDATOR)
501
+ @current = Concurrent::AtomicReference.new(initial)
502
+ @error = Concurrent::AtomicReference.new(nil)
503
+ @caller = Concurrent::ThreadLocalVar.new(nil)
504
+ @queue = []
505
+
506
+ self.observers = Collection::CopyOnNotifyObserverSet.new
507
+ end
508
+
509
+ def enqueue_action_job(action, args, executor)
510
+ raise ArgumentError.new('no action given') unless action
511
+ job = Job.new(action, args, executor, @caller.value || Thread.current.object_id)
512
+ synchronize { ns_enqueue_job(job) }
513
+ end
514
+
515
+ def enqueue_await_job(latch)
516
+ synchronize do
517
+ if (index = ns_find_last_job_for_thread)
518
+ job = Job.new(AWAIT_ACTION, [latch], Concurrent.global_immediate_executor,
519
+ Thread.current.object_id)
520
+ ns_enqueue_job(job, index+1)
521
+ else
522
+ latch.count_down
523
+ true
524
+ end
525
+ end
526
+ end
527
+
528
+ def ns_enqueue_job(job, index = nil)
529
+ # a non-nil index means this is an await job
530
+ return false if index.nil? && failed?
531
+ index ||= @queue.length
532
+ @queue.insert(index, job)
533
+ # if this is the only job, post to executor
534
+ ns_post_next_job if @queue.length == 1
535
+ true
536
+ end
537
+
538
+ def ns_post_next_job
539
+ @queue.first.executor.post { execute_next_job }
540
+ end
541
+
542
+ def execute_next_job
543
+ job = synchronize { @queue.first }
544
+ old_value = @current.value
545
+
546
+ @caller.value = job.caller # for nested actions
547
+ new_value = job.action.call(old_value, *job.args)
548
+ @caller.value = nil
549
+
550
+ return if new_value == AWAIT_FLAG
551
+
552
+ if ns_validate(new_value)
553
+ @current.value = new_value
554
+ observers.notify_observers(Time.now, old_value, new_value)
555
+ else
556
+ handle_error(ValidationError.new)
557
+ end
558
+ rescue => error
559
+ handle_error(error)
560
+ ensure
561
+ synchronize do
562
+ @queue.shift
563
+ unless failed? || @queue.empty?
564
+ ns_post_next_job
565
+ end
566
+ end
567
+ end
568
+
569
+ def ns_validate(value)
570
+ @validator.call(value)
571
+ rescue
572
+ false
573
+ end
574
+
575
+ def handle_error(error)
576
+ # stop new jobs from posting
577
+ @error.value = error if @error_mode == :fail
578
+ @error_handler.call(self, error)
579
+ rescue
580
+ # do nothing
581
+ end
582
+
583
+ def ns_find_last_job_for_thread
584
+ @queue.rindex { |job| job.caller == Thread.current.object_id }
585
+ end
586
+ end
587
+ end
@@ -0,0 +1,66 @@
1
+ require 'concurrent/utility/engine'
2
+ require 'concurrent/thread_safe/util'
3
+
4
+ module Concurrent
5
+
6
+ # @!macro concurrent_array
7
+ #
8
+ # A thread-safe subclass of Array. This version locks against the object
9
+ # itself for every method call, ensuring only one thread can be reading
10
+ # or writing at a time. This includes iteration methods like `#each`.
11
+ #
12
+ # @note `a += b` is **not** a **thread-safe** operation on
13
+ # `Concurrent::Array`. It reads array `a`, then it creates new `Concurrent::Array`
14
+ # which is concatenation of `a` and `b`, then it writes the concatenation to `a`.
15
+ # The read and write are independent operations they do not form a single atomic
16
+ # operation therefore when two `+=` operations are executed concurrently updates
17
+ # may be lost. Use `#concat` instead.
18
+ #
19
+ # @see http://ruby-doc.org/core/Array.html Ruby standard library `Array`
20
+
21
+ # @!macro internal_implementation_note
22
+ ArrayImplementation = case
23
+ when Concurrent.on_cruby?
24
+ # Array is thread-safe in practice because CRuby runs
25
+ # threads one at a time and does not do context
26
+ # switching during the execution of C functions.
27
+ ::Array
28
+
29
+ when Concurrent.on_jruby?
30
+ require 'jruby/synchronized'
31
+
32
+ class JRubyArray < ::Array
33
+ include JRuby::Synchronized
34
+ end
35
+ JRubyArray
36
+
37
+ when Concurrent.on_rbx?
38
+ require 'monitor'
39
+ require 'concurrent/thread_safe/util/data_structures'
40
+
41
+ class RbxArray < ::Array
42
+ end
43
+
44
+ ThreadSafe::Util.make_synchronized_on_rbx RbxArray
45
+ RbxArray
46
+
47
+ when Concurrent.on_truffleruby?
48
+ require 'concurrent/thread_safe/util/data_structures'
49
+
50
+ class TruffleRubyArray < ::Array
51
+ end
52
+
53
+ ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyArray
54
+ TruffleRubyArray
55
+
56
+ else
57
+ warn 'Possibly unsupported Ruby implementation'
58
+ ::Array
59
+ end
60
+ private_constant :ArrayImplementation
61
+
62
+ # @!macro concurrent_array
63
+ class Array < ArrayImplementation
64
+ end
65
+
66
+ end