concurrent-ruby 0.8.0.pre2-java → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +114 -3
- data/README.md +111 -55
- data/lib/concurrent.rb +90 -14
- data/lib/concurrent/async.rb +143 -51
- data/lib/concurrent/atom.rb +131 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
- data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
- data/lib/concurrent/atomic/atomic_reference.rb +49 -0
- data/lib/concurrent/atomic/condition.rb +23 -12
- data/lib/concurrent/atomic/count_down_latch.rb +23 -21
- data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
- data/lib/concurrent/atomic/event.rb +33 -42
- data/lib/concurrent/atomic/read_write_lock.rb +252 -0
- data/lib/concurrent/atomic/semaphore.rb +64 -89
- data/lib/concurrent/atomic/thread_local_var.rb +130 -58
- data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
- data/lib/concurrent/atomic_reference/jruby.rb +6 -3
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
- data/lib/concurrent/atomic_reference/rbx.rb +4 -1
- data/lib/concurrent/atomic_reference/ruby.rb +6 -3
- data/lib/concurrent/atomics.rb +74 -4
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
- data/lib/concurrent/collection/priority_queue.rb +300 -245
- data/lib/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent/concern/dereferenceable.rb +88 -0
- data/lib/concurrent/concern/logging.rb +27 -0
- data/lib/concurrent/concern/obligation.rb +228 -0
- data/lib/concurrent/concern/observable.rb +85 -0
- data/lib/concurrent/configuration.rb +234 -109
- data/lib/concurrent/dataflow.rb +2 -3
- data/lib/concurrent/delay.rb +141 -50
- data/lib/concurrent/edge.rb +30 -0
- data/lib/concurrent/errors.rb +19 -7
- data/lib/concurrent/exchanger.rb +25 -1
- data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
- data/lib/concurrent/executor/executor.rb +46 -299
- data/lib/concurrent/executor/executor_service.rb +521 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
- data/lib/concurrent/executor/immediate_executor.rb +9 -9
- data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
- data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
- data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
- data/lib/concurrent/executor/safe_task_executor.rb +5 -4
- data/lib/concurrent/executor/serialized_execution.rb +22 -18
- data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
- data/lib/concurrent/executor/single_thread_executor.rb +32 -21
- data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
- data/lib/concurrent/executor/timer_set.rb +96 -84
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/future.rb +71 -38
- data/lib/concurrent/immutable_struct.rb +89 -0
- data/lib/concurrent/ivar.rb +152 -60
- data/lib/concurrent/lazy_register.rb +40 -20
- data/lib/concurrent/maybe.rb +226 -0
- data/lib/concurrent/mutable_struct.rb +227 -0
- data/lib/concurrent/mvar.rb +44 -43
- data/lib/concurrent/promise.rb +229 -136
- data/lib/concurrent/scheduled_task.rb +341 -43
- data/lib/concurrent/settable_struct.rb +127 -0
- data/lib/concurrent/synchronization.rb +17 -0
- data/lib/concurrent/synchronization/abstract_object.rb +163 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
- data/lib/concurrent/synchronization/condition.rb +53 -0
- data/lib/concurrent/synchronization/java_object.rb +34 -0
- data/lib/concurrent/synchronization/lock.rb +32 -0
- data/lib/concurrent/synchronization/monitor_object.rb +26 -0
- data/lib/concurrent/synchronization/mutex_object.rb +43 -0
- data/lib/concurrent/synchronization/object.rb +78 -0
- data/lib/concurrent/synchronization/rbx_object.rb +75 -0
- data/lib/concurrent/timer_task.rb +92 -103
- data/lib/concurrent/tvar.rb +42 -38
- data/lib/concurrent/utilities.rb +3 -1
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +44 -0
- data/lib/concurrent/utility/monotonic_time.rb +59 -0
- data/lib/concurrent/utility/native_extension_loader.rb +56 -0
- data/lib/concurrent/utility/processor_counter.rb +156 -0
- data/lib/concurrent/utility/timeout.rb +18 -14
- data/lib/concurrent/utility/timer.rb +11 -6
- data/lib/concurrent/version.rb +2 -1
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.jar +0 -0
- metadata +46 -66
- data/lib/concurrent/actor.rb +0 -103
- data/lib/concurrent/actor/behaviour.rb +0 -70
- data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
- data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
- data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
- data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
- data/lib/concurrent/actor/behaviour/linking.rb +0 -45
- data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
- data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
- data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
- data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
- data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
- data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
- data/lib/concurrent/actor/behaviour/termination.rb +0 -54
- data/lib/concurrent/actor/context.rb +0 -154
- data/lib/concurrent/actor/core.rb +0 -217
- data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
- data/lib/concurrent/actor/envelope.rb +0 -41
- data/lib/concurrent/actor/errors.rb +0 -27
- data/lib/concurrent/actor/internal_delegations.rb +0 -49
- data/lib/concurrent/actor/public_delegations.rb +0 -40
- data/lib/concurrent/actor/reference.rb +0 -81
- data/lib/concurrent/actor/root.rb +0 -37
- data/lib/concurrent/actor/type_check.rb +0 -48
- data/lib/concurrent/actor/utils.rb +0 -10
- data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
- data/lib/concurrent/actor/utils/balancer.rb +0 -42
- data/lib/concurrent/actor/utils/broadcast.rb +0 -52
- data/lib/concurrent/actor/utils/pool.rb +0 -59
- data/lib/concurrent/actress.rb +0 -3
- data/lib/concurrent/agent.rb +0 -209
- data/lib/concurrent/atomic.rb +0 -92
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
- data/lib/concurrent/atomic/synchronization.rb +0 -51
- data/lib/concurrent/channel/buffered_channel.rb +0 -85
- data/lib/concurrent/channel/channel.rb +0 -41
- data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
- data/lib/concurrent/channel/waitable_list.rb +0 -40
- data/lib/concurrent/channels.rb +0 -5
- data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
- data/lib/concurrent/collection/ring_buffer.rb +0 -59
- data/lib/concurrent/collections.rb +0 -3
- data/lib/concurrent/dereferenceable.rb +0 -108
- data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
- data/lib/concurrent/logging.rb +0 -20
- data/lib/concurrent/obligation.rb +0 -171
- data/lib/concurrent/observable.rb +0 -73
- data/lib/concurrent/options_parser.rb +0 -48
- data/lib/concurrent/utility/processor_count.rb +0 -152
- data/lib/extension_helper.rb +0 -37
data/lib/concurrent/async.rb
CHANGED
@@ -5,16 +5,81 @@ require 'concurrent/errors'
|
|
5
5
|
require 'concurrent/ivar'
|
6
6
|
require 'concurrent/executor/immediate_executor'
|
7
7
|
require 'concurrent/executor/serialized_execution'
|
8
|
+
require 'concurrent/concern/deprecation'
|
8
9
|
|
9
10
|
module Concurrent
|
10
11
|
|
11
|
-
#
|
12
|
+
# A mixin module that provides simple asynchronous behavior to any standard
|
13
|
+
# class/object or object.
|
14
|
+
#
|
15
|
+
# ```cucumber
|
16
|
+
# Feature:
|
17
|
+
# As a stateful, plain old Ruby class/object
|
18
|
+
# I want safe, asynchronous behavior
|
19
|
+
# So my long-running methods don't block the main thread
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# Stateful, mutable objects must be managed carefully when used asynchronously.
|
23
|
+
# But Ruby is an object-oriented language so designing with objects and classes
|
24
|
+
# plays to Ruby's strengths and is often more natural to many Ruby programmers.
|
25
|
+
# The `Async` module is a way to mix simple yet powerful asynchronous capabilities
|
26
|
+
# into any plain old Ruby object or class. These capabilities provide a reasonable
|
27
|
+
# level of thread safe guarantees when used correctly.
|
28
|
+
#
|
29
|
+
# When this module is mixed into a class or object it provides to new methods:
|
30
|
+
# `async` and `await`. These methods are thread safe with respect to the enclosing
|
31
|
+
# object. The former method allows methods to be called asynchronously by posting
|
32
|
+
# to the global thread pool. The latter allows a method to be called synchronously
|
33
|
+
# on the current thread but does so safely with respect to any pending asynchronous
|
34
|
+
# method calls. Both methods return an `IVar` which can be inspected for
|
35
|
+
# the result of the method call. Calling a method with `async` will return a
|
36
|
+
# `:pending` `IVar` whereas `await` will return a `:complete` `IVar`.
|
37
|
+
#
|
38
|
+
# Very loosely based on the `async` and `await` keywords in C#.
|
39
|
+
#
|
40
|
+
# ### An Important Note About Thread Safe Guarantees
|
41
|
+
#
|
42
|
+
# > Thread safe guarantees can only be made when asynchronous method calls
|
43
|
+
# > are not mixed with synchronous method calls. Use only synchronous calls
|
44
|
+
# > when the object is used exclusively on a single thread. Use only
|
45
|
+
# > `async` and `await` when the object is shared between threads. Once you
|
46
|
+
# > call a method using `async`, you should no longer call any methods
|
47
|
+
# > directly on the object. Use `async` and `await` exclusively from then on.
|
48
|
+
# > With careful programming it is possible to switch back and forth but it's
|
49
|
+
# > also very easy to create race conditions and break your application.
|
50
|
+
# > Basically, it's "async all the way down."
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
#
|
54
|
+
# class Echo
|
55
|
+
# include Concurrent::Async
|
56
|
+
#
|
57
|
+
# def echo(msg)
|
58
|
+
# sleep(rand)
|
59
|
+
# print "#{msg}\n"
|
60
|
+
# nil
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# horn = Echo.new
|
65
|
+
# horn.echo('zero') # synchronous, not thread-safe
|
66
|
+
#
|
67
|
+
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
68
|
+
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
12
69
|
#
|
13
|
-
# @
|
14
|
-
#
|
15
|
-
# @see Concurrent::Obligation
|
70
|
+
# @see Concurrent::IVar
|
16
71
|
module Async
|
17
72
|
|
73
|
+
# @!method self.new(*args, &block)
|
74
|
+
#
|
75
|
+
# Instanciate a new object and ensure proper initialization of the
|
76
|
+
# synchronization mechanisms.
|
77
|
+
#
|
78
|
+
# @param [Array<Object>] args Zero or more arguments to be passed to the
|
79
|
+
# object's initializer.
|
80
|
+
# @param [Proc] bloc Optional block to pass to the object's initializer.
|
81
|
+
# @return [Object] A properly initialized object of the asynchronous class.
|
82
|
+
|
18
83
|
# Check for the presence of a method on an object and determine if a given
|
19
84
|
# set of arguments matches the required arity.
|
20
85
|
#
|
@@ -34,7 +99,9 @@ module Concurrent
|
|
34
99
|
# @see http://www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity
|
35
100
|
# @see http://ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to?
|
36
101
|
# @see http://www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing
|
37
|
-
|
102
|
+
#
|
103
|
+
# @!visibility private
|
104
|
+
def self.validate_argc(obj, method, *args)
|
38
105
|
argc = args.length
|
39
106
|
arity = obj.method(method).arity
|
40
107
|
|
@@ -44,12 +111,28 @@ module Concurrent
|
|
44
111
|
raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)")
|
45
112
|
end
|
46
113
|
end
|
47
|
-
|
114
|
+
|
115
|
+
# @!visibility private
|
116
|
+
def self.included(base)
|
117
|
+
base.singleton_class.send(:alias_method, :original_new, :new)
|
118
|
+
base.extend(ClassMethods)
|
119
|
+
super(base)
|
120
|
+
end
|
121
|
+
|
122
|
+
# @!visibility private
|
123
|
+
module ClassMethods
|
124
|
+
def new(*args, &block)
|
125
|
+
obj = original_new(*args, &block)
|
126
|
+
obj.send(:init_synchronization)
|
127
|
+
obj
|
128
|
+
end
|
129
|
+
end
|
130
|
+
private_constant :ClassMethods
|
48
131
|
|
49
132
|
# Delegates asynchronous, thread-safe method calls to the wrapped object.
|
50
133
|
#
|
51
134
|
# @!visibility private
|
52
|
-
class AsyncDelegator
|
135
|
+
class AsyncDelegator
|
53
136
|
|
54
137
|
# Create a new delegator object wrapping the given delegate,
|
55
138
|
# protecting it with the given serializer, and executing it on the
|
@@ -84,14 +167,11 @@ module Concurrent
|
|
84
167
|
self.define_singleton_method(method) do |*args2|
|
85
168
|
Async::validate_argc(@delegate, method, *args2)
|
86
169
|
ivar = Concurrent::IVar.new
|
87
|
-
value, reason = nil, nil
|
88
170
|
@serializer.post(@executor.value) do
|
89
171
|
begin
|
90
|
-
|
172
|
+
ivar.set(@delegate.send(method, *args2, &block))
|
91
173
|
rescue => reason
|
92
|
-
|
93
|
-
ensure
|
94
|
-
ivar.complete(reason.nil?, value, reason)
|
174
|
+
ivar.fail(reason)
|
95
175
|
end
|
96
176
|
end
|
97
177
|
ivar.value if @blocking
|
@@ -101,6 +181,7 @@ module Concurrent
|
|
101
181
|
self.send(method, *args)
|
102
182
|
end
|
103
183
|
end
|
184
|
+
private_constant :AsyncDelegator
|
104
185
|
|
105
186
|
# Causes the chained method call to be performed asynchronously on the
|
106
187
|
# global thread pool. The method called by this method will return a
|
@@ -115,26 +196,24 @@ module Concurrent
|
|
115
196
|
# library, some edge cases will be missed. For more information see
|
116
197
|
# the documentation for the `validate_argc` method.
|
117
198
|
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
199
|
+
# @!macro [attach] async_thread_safety_warning
|
200
|
+
# @note The method call is guaranteed to be thread safe with respect to
|
201
|
+
# all other method calls against the same object that are called with
|
202
|
+
# either `async` or `await`. The mutable nature of Ruby references
|
203
|
+
# (and object orientation in general) prevent any other thread safety
|
204
|
+
# guarantees. Do NOT mix non-protected method calls with protected
|
205
|
+
# method call. Use *only* protected method calls when sharing the object
|
206
|
+
# between threads.
|
125
207
|
#
|
126
208
|
# @return [Concurrent::IVar] the pending result of the asynchronous operation
|
127
209
|
#
|
128
|
-
# @raise [Concurrent::InitializationError] `#init_mutex` has not been called
|
129
210
|
# @raise [NameError] the object does not respond to `method` method
|
130
211
|
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
131
212
|
#
|
132
213
|
# @see Concurrent::IVar
|
133
214
|
def async
|
134
|
-
raise InitializationError.new('#init_mutex was never called') unless @__async_initialized__
|
135
215
|
@__async_delegator__.value
|
136
216
|
end
|
137
|
-
alias_method :future, :async
|
138
217
|
|
139
218
|
# Causes the chained method call to be performed synchronously on the
|
140
219
|
# current thread. The method called by this method will return an
|
@@ -149,58 +228,71 @@ module Concurrent
|
|
149
228
|
# library, some edge cases will be missed. For more information see
|
150
229
|
# the documentation for the `validate_argc` method.
|
151
230
|
#
|
152
|
-
#
|
153
|
-
# all other method calls against the same object that are called with
|
154
|
-
# either `async` or `await`. The mutable nature of Ruby references
|
155
|
-
# (and object orientation in general) prevent any other thread safety
|
156
|
-
# guarantees. Do NOT mix non-protected method calls with protected
|
157
|
-
# method call. Use *only* protected method calls when sharing the object
|
158
|
-
# between threads.
|
231
|
+
# @!macro async_thread_safety_warning
|
159
232
|
#
|
160
233
|
# @return [Concurrent::IVar] the completed result of the synchronous operation
|
161
234
|
#
|
162
|
-
# @raise [Concurrent::InitializationError] `#init_mutex` has not been called
|
163
235
|
# @raise [NameError] the object does not respond to `method` method
|
164
236
|
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
165
237
|
#
|
166
238
|
# @see Concurrent::IVar
|
167
239
|
def await
|
168
|
-
raise InitializationError.new('#init_mutex was never called') unless @__async_initialized__
|
169
240
|
@__await_delegator__.value
|
170
241
|
end
|
171
|
-
alias_method :delay, :await
|
172
242
|
|
173
|
-
# Set a new executor
|
243
|
+
# Set a new executor.
|
174
244
|
#
|
175
|
-
# @raise [
|
176
|
-
# @raise [ArgumentError] executor has already been set
|
245
|
+
# @raise [ArgumentError] executor has already been set.
|
177
246
|
def executor=(executor)
|
178
|
-
raise InitializationError.new('#init_mutex was never called') unless @__async_initialized__
|
179
247
|
@__async_executor__.reconfigure { executor } or
|
180
248
|
raise ArgumentError.new('executor has already been set')
|
181
249
|
end
|
182
250
|
|
183
|
-
# Initialize the internal serializer and other
|
184
|
-
# *must* be called from the constructor of the including class or explicitly
|
185
|
-
# by the caller prior to calling any other methods. If `init_mutex` is *not*
|
186
|
-
# called explicitly the async/await/executor methods will raize a
|
187
|
-
# `Concurrent::InitializationError`. This is the only way thread-safe
|
188
|
-
# initialization can be guaranteed.
|
251
|
+
# Initialize the internal serializer and other stnchronization mechanisms.
|
189
252
|
#
|
190
|
-
# @note This method *must* be called
|
191
|
-
#
|
192
|
-
# This is the only way thread-safe initialization can be guaranteed.
|
253
|
+
# @note This method *must* be called immediately upon object construction.
|
254
|
+
# This is the only way thread-safe initialization can be guaranteed.
|
193
255
|
#
|
194
256
|
# @raise [Concurrent::InitializationError] when called more than once
|
257
|
+
#
|
258
|
+
# @!visibility private
|
259
|
+
# @deprecated
|
195
260
|
def init_mutex
|
196
|
-
|
261
|
+
deprecated 'mutex synchronization now happens automatically'
|
262
|
+
init_synchronization
|
263
|
+
rescue InitializationError
|
264
|
+
# suppress
|
265
|
+
end
|
266
|
+
|
267
|
+
private
|
268
|
+
|
269
|
+
# Initialize the internal serializer and other stnchronization mechanisms.
|
270
|
+
#
|
271
|
+
# @note This method *must* be called immediately upon object construction.
|
272
|
+
# This is the only way thread-safe initialization can be guaranteed.
|
273
|
+
#
|
274
|
+
# @raise [Concurrent::InitializationError] when called more than once
|
275
|
+
#
|
276
|
+
# @!visibility private
|
277
|
+
def init_synchronization
|
278
|
+
return self if @__async_initialized__
|
279
|
+
|
197
280
|
@__async_initialized__ = true
|
198
281
|
serializer = Concurrent::SerializedExecution.new
|
199
|
-
|
200
|
-
@
|
201
|
-
|
202
|
-
|
203
|
-
|
282
|
+
|
283
|
+
@__async_executor__ = Delay.new {
|
284
|
+
Concurrent.global_io_executor
|
285
|
+
}
|
286
|
+
|
287
|
+
@__await_delegator__ = Delay.new {
|
288
|
+
AsyncDelegator.new(self, Delay.new{ Concurrent::ImmediateExecutor.new }, serializer, true)
|
289
|
+
}
|
290
|
+
|
291
|
+
@__async_delegator__ = Delay.new {
|
292
|
+
AsyncDelegator.new(self, @__async_executor__, serializer, false)
|
293
|
+
}
|
294
|
+
|
295
|
+
self
|
204
296
|
end
|
205
297
|
end
|
206
298
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'concurrent/concern/dereferenceable'
|
2
|
+
require 'concurrent/atomic/atomic_reference'
|
3
|
+
require 'concurrent/synchronization/object'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
# Atoms provide a way to manage shared, synchronous, independent state.
|
8
|
+
#
|
9
|
+
# An atom is initialized with an initial value and an optional validation
|
10
|
+
# proc. At any time the value of the atom can be synchronously and safely
|
11
|
+
# changed. If a validator is given at construction then any new value
|
12
|
+
# will be checked against the validator and will be rejected if the
|
13
|
+
# validator returns false or raises an exception.
|
14
|
+
#
|
15
|
+
# There are two ways to change the value of an atom: {#compare_and_set} and
|
16
|
+
# {#swap}. The former will set the new value if and only if it validates and
|
17
|
+
# the current value matches the new value. The latter will atomically set the
|
18
|
+
# new value to the result of running the given block if and only if that
|
19
|
+
# value validates.
|
20
|
+
#
|
21
|
+
# @!macro copy_options
|
22
|
+
#
|
23
|
+
# @see http://clojure.org/atoms Clojure Atoms
|
24
|
+
class Atom < Synchronization::Object
|
25
|
+
include Concern::Dereferenceable
|
26
|
+
|
27
|
+
# Create a new atom with the given initial value.
|
28
|
+
#
|
29
|
+
# @param [Object] value The initial value
|
30
|
+
# @param [Hash] opts The options used to configure the atom
|
31
|
+
# @option opts [Proc] :validator (nil) Optional proc used to validate new
|
32
|
+
# values. It must accept one and only one argument which will be the
|
33
|
+
# intended new value. The validator will return true if the new value
|
34
|
+
# is acceptable else return false (preferrably) or raise an exception.
|
35
|
+
#
|
36
|
+
# @!macro deref_options
|
37
|
+
#
|
38
|
+
# @raise [ArgumentError] if the validator is not a `Proc` (when given)
|
39
|
+
def initialize(value, opts = {})
|
40
|
+
super()
|
41
|
+
|
42
|
+
@validator = opts.fetch(:validator, ->(v){ true })
|
43
|
+
raise ArgumentError.new('validator must be a proc') unless @validator.is_a? Proc
|
44
|
+
|
45
|
+
@value = Concurrent::AtomicReference.new(value)
|
46
|
+
ns_set_deref_options(opts)
|
47
|
+
ensure_ivar_visibility!
|
48
|
+
end
|
49
|
+
|
50
|
+
# The current value of the atom.
|
51
|
+
#
|
52
|
+
# @return [Object] The current value.
|
53
|
+
def value
|
54
|
+
apply_deref_options(@value.value)
|
55
|
+
end
|
56
|
+
alias_method :deref, :value
|
57
|
+
|
58
|
+
# Atomically swaps the value of atom using the given block. The current
|
59
|
+
# value will be passed to the block, as will any arguments passed as
|
60
|
+
# arguments to the function. The new value will be validated against the
|
61
|
+
# (optional) validator proc given at construction. If validation fails the
|
62
|
+
# value will not be changed.
|
63
|
+
#
|
64
|
+
# Internally, {#swap} reads the current value, applies the block to it, and
|
65
|
+
# attempts to compare-and-set it in. Since another thread may have changed
|
66
|
+
# the value in the intervening time, it may have to retry, and does so in a
|
67
|
+
# spin loop. The net effect is that the value will always be the result of
|
68
|
+
# the application of the supplied block to a current value, atomically.
|
69
|
+
# However, because the block might be called multiple times, it must be free
|
70
|
+
# of side effects.
|
71
|
+
#
|
72
|
+
# @note The given block may be called multiple times, and thus should be free
|
73
|
+
# of side effects.
|
74
|
+
#
|
75
|
+
# @param [Object] args Zero or more arguments passed to the block.
|
76
|
+
#
|
77
|
+
# @yield [value, args] Calculates a new value for the atom based on the
|
78
|
+
# current value and any supplied agruments.
|
79
|
+
# @yieldparam value [Object] The current value of the atom.
|
80
|
+
# @yieldparam args [Object] All arguments passed to the function, in order.
|
81
|
+
# @yieldreturn [Object] The intended new value of the atom.
|
82
|
+
#
|
83
|
+
# @return [Object] The final value of the atom after all operations and
|
84
|
+
# validations are complete.
|
85
|
+
#
|
86
|
+
# @raise [ArgumentError] When no block is given.
|
87
|
+
def swap(*args)
|
88
|
+
raise ArgumentError.new('no block given') unless block_given?
|
89
|
+
|
90
|
+
begin
|
91
|
+
loop do
|
92
|
+
old_value = @value.value
|
93
|
+
new_value = yield(old_value, *args)
|
94
|
+
return old_value unless @validator.call(new_value)
|
95
|
+
return new_value if compare_and_set!(old_value, new_value)
|
96
|
+
end
|
97
|
+
rescue
|
98
|
+
return @value.value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @!macro [attach] atom_compare_and_set
|
103
|
+
# Atomically sets the value of atom to the new value if and only if the
|
104
|
+
# current value of the atom is identical to the old value and the new
|
105
|
+
# value successfully validates against the (optional) validator given
|
106
|
+
# at construction.
|
107
|
+
#
|
108
|
+
# @param [Object] old_value The expected current value.
|
109
|
+
# @param [Object] new_value The intended new value.
|
110
|
+
#
|
111
|
+
# @return [Boolean] True if the value is changed else false.
|
112
|
+
def compare_and_set(old_value, new_value)
|
113
|
+
compare_and_set!(old_value, new_value)
|
114
|
+
rescue
|
115
|
+
false
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# @!macro atom_compare_and_set
|
121
|
+
# @raise [Exception] if the validator proc raises an exception
|
122
|
+
# @!visibility private
|
123
|
+
def compare_and_set!(old_value, new_value)
|
124
|
+
if @validator.call(new_value) # may raise exception
|
125
|
+
@value.compare_and_set(old_value, new_value)
|
126
|
+
else
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require 'concurrent/utility/native_extension_loader'
|
2
|
+
require 'concurrent/synchronization'
|
2
3
|
|
3
4
|
module Concurrent
|
4
5
|
|
@@ -21,7 +22,11 @@ module Concurrent
|
|
21
22
|
# 3.340000 0.010000 3.350000 ( 0.855000)
|
22
23
|
#
|
23
24
|
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html java.util.concurrent.atomic.AtomicBoolean
|
24
|
-
|
25
|
+
#
|
26
|
+
# @!visibility private
|
27
|
+
#
|
28
|
+
# @!macro internal_implementation_note
|
29
|
+
class MutexAtomicBoolean < Synchronization::Object
|
25
30
|
|
26
31
|
# @!macro [attach] atomic_boolean_method_initialize
|
27
32
|
#
|
@@ -29,8 +34,8 @@ module Concurrent
|
|
29
34
|
#
|
30
35
|
# @param [Boolean] initial the initial value
|
31
36
|
def initialize(initial = false)
|
32
|
-
|
33
|
-
|
37
|
+
super()
|
38
|
+
synchronize { ns_initialize(initial) }
|
34
39
|
end
|
35
40
|
|
36
41
|
# @!macro [attach] atomic_boolean_method_value_get
|
@@ -39,10 +44,7 @@ module Concurrent
|
|
39
44
|
#
|
40
45
|
# @return [Boolean] the current value
|
41
46
|
def value
|
42
|
-
@
|
43
|
-
@value
|
44
|
-
ensure
|
45
|
-
@mutex.unlock
|
47
|
+
synchronize { @value }
|
46
48
|
end
|
47
49
|
|
48
50
|
# @!macro [attach] atomic_boolean_method_value_set
|
@@ -53,11 +55,7 @@ module Concurrent
|
|
53
55
|
#
|
54
56
|
# @return [Boolean] the current value
|
55
57
|
def value=(value)
|
56
|
-
@
|
57
|
-
@value = !!value
|
58
|
-
@value
|
59
|
-
ensure
|
60
|
-
@mutex.unlock
|
58
|
+
synchronize { @value = !!value }
|
61
59
|
end
|
62
60
|
|
63
61
|
# @!macro [attach] atomic_boolean_method_true_question
|
@@ -66,22 +64,16 @@ module Concurrent
|
|
66
64
|
#
|
67
65
|
# @return [Boolean] true if the current value is `true`, else false
|
68
66
|
def true?
|
69
|
-
@
|
70
|
-
@value
|
71
|
-
ensure
|
72
|
-
@mutex.unlock
|
67
|
+
synchronize { @value }
|
73
68
|
end
|
74
69
|
|
75
|
-
# @!macro atomic_boolean_method_false_question
|
70
|
+
# @!macro [attach] atomic_boolean_method_false_question
|
76
71
|
#
|
77
72
|
# Is the current value `false`
|
78
73
|
#
|
79
74
|
# @return [Boolean] true if the current value is `false`, else false
|
80
75
|
def false?
|
81
|
-
|
82
|
-
!@value
|
83
|
-
ensure
|
84
|
-
@mutex.unlock
|
76
|
+
synchronize { !@value }
|
85
77
|
end
|
86
78
|
|
87
79
|
# @!macro [attach] atomic_boolean_method_make_true
|
@@ -90,12 +82,7 @@ module Concurrent
|
|
90
82
|
#
|
91
83
|
# @return [Boolean] true is value has changed, otherwise false
|
92
84
|
def make_true
|
93
|
-
|
94
|
-
old = @value
|
95
|
-
@value = true
|
96
|
-
!old
|
97
|
-
ensure
|
98
|
-
@mutex.unlock
|
85
|
+
synchronize { ns_make_value(true) }
|
99
86
|
end
|
100
87
|
|
101
88
|
# @!macro [attach] atomic_boolean_method_make_false
|
@@ -104,98 +91,61 @@ module Concurrent
|
|
104
91
|
#
|
105
92
|
# @return [Boolean] true is value has changed, otherwise false
|
106
93
|
def make_false
|
107
|
-
|
108
|
-
old = @value
|
109
|
-
@value = false
|
110
|
-
old
|
111
|
-
ensure
|
112
|
-
@mutex.unlock
|
94
|
+
synchronize { ns_make_value(false) }
|
113
95
|
end
|
114
|
-
end
|
115
96
|
|
116
|
-
|
117
|
-
|
118
|
-
# @!macro atomic_boolean
|
119
|
-
class JavaAtomicBoolean
|
120
|
-
|
121
|
-
# @!macro atomic_boolean_method_initialize
|
122
|
-
#
|
123
|
-
def initialize(initial = false)
|
124
|
-
@atomic = java.util.concurrent.atomic.AtomicBoolean.new(!!initial)
|
125
|
-
end
|
126
|
-
|
127
|
-
# @!macro atomic_boolean_method_value_get
|
128
|
-
#
|
129
|
-
def value
|
130
|
-
@atomic.get
|
131
|
-
end
|
132
|
-
|
133
|
-
# @!macro atomic_boolean_method_value_set
|
134
|
-
#
|
135
|
-
def value=(value)
|
136
|
-
@atomic.set(!!value)
|
137
|
-
end
|
138
|
-
|
139
|
-
# @!macro atomic_boolean_method_true_question
|
140
|
-
def true?
|
141
|
-
@atomic.get
|
142
|
-
end
|
143
|
-
|
144
|
-
# @!macro atomic_boolean_method_false_question
|
145
|
-
def false?
|
146
|
-
!@atomic.get
|
147
|
-
end
|
148
|
-
|
149
|
-
# @!macro atomic_boolean_method_make_true
|
150
|
-
def make_true
|
151
|
-
@atomic.compareAndSet(false, true)
|
152
|
-
end
|
153
|
-
|
154
|
-
# @!macro atomic_boolean_method_make_false
|
155
|
-
def make_false
|
156
|
-
@atomic.compareAndSet(true, false)
|
157
|
-
end
|
158
|
-
end
|
97
|
+
protected
|
159
98
|
|
160
|
-
# @!
|
161
|
-
|
99
|
+
# @!visibility private
|
100
|
+
def ns_initialize(initial)
|
101
|
+
@value = !!initial
|
162
102
|
end
|
163
103
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
104
|
+
# @!visibility private
|
105
|
+
def ns_make_value(value)
|
106
|
+
old = @value
|
107
|
+
@value = value
|
108
|
+
old != @value
|
109
|
+
end
|
110
|
+
end
|
171
111
|
|
172
|
-
|
173
|
-
|
112
|
+
# @!visibility private
|
113
|
+
# @!macro internal_implementation_note
|
114
|
+
AtomicBooleanImplementation = case
|
115
|
+
when Concurrent.on_jruby?
|
116
|
+
JavaAtomicBoolean
|
117
|
+
when defined?(CAtomicBoolean)
|
118
|
+
CAtomicBoolean
|
119
|
+
else
|
120
|
+
MutexAtomicBoolean
|
121
|
+
end
|
122
|
+
private_constant :AtomicBooleanImplementation
|
123
|
+
|
124
|
+
# @!macro atomic_boolean
|
125
|
+
#
|
126
|
+
# @see Concurrent::MutexAtomicBoolean
|
127
|
+
class AtomicBoolean < AtomicBooleanImplementation
|
174
128
|
|
175
|
-
|
176
|
-
|
129
|
+
# @!method initialize(initial = false)
|
130
|
+
# @!macro atomic_boolean_method_initialize
|
177
131
|
|
178
|
-
|
179
|
-
|
132
|
+
# @!method value
|
133
|
+
# @!macro atomic_boolean_method_value_get
|
180
134
|
|
181
|
-
|
182
|
-
|
135
|
+
# @!method value=(value)
|
136
|
+
# @!macro atomic_boolean_method_value_set
|
183
137
|
|
184
|
-
|
185
|
-
|
138
|
+
# @!method true?
|
139
|
+
# @!macro atomic_boolean_method_true_question
|
186
140
|
|
187
|
-
|
188
|
-
|
189
|
-
end
|
141
|
+
# @!method false?
|
142
|
+
# @!macro atomic_boolean_method_false_question
|
190
143
|
|
191
|
-
# @!
|
192
|
-
|
193
|
-
end
|
144
|
+
# @!method make_true
|
145
|
+
# @!macro atomic_boolean_method_make_true
|
194
146
|
|
195
|
-
|
147
|
+
# @!method make_false
|
148
|
+
# @!macro atomic_boolean_method_make_false
|
196
149
|
|
197
|
-
# @!macro atomic_boolean
|
198
|
-
class AtomicBoolean < MutexAtomicBoolean
|
199
|
-
end
|
200
150
|
end
|
201
151
|
end
|