concurrent-ruby 0.8.0.pre2-java → 0.9.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +114 -3
  3. data/README.md +111 -55
  4. data/lib/concurrent.rb +90 -14
  5. data/lib/concurrent/async.rb +143 -51
  6. data/lib/concurrent/atom.rb +131 -0
  7. data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
  8. data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
  9. data/lib/concurrent/atomic/atomic_reference.rb +49 -0
  10. data/lib/concurrent/atomic/condition.rb +23 -12
  11. data/lib/concurrent/atomic/count_down_latch.rb +23 -21
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
  13. data/lib/concurrent/atomic/event.rb +33 -42
  14. data/lib/concurrent/atomic/read_write_lock.rb +252 -0
  15. data/lib/concurrent/atomic/semaphore.rb +64 -89
  16. data/lib/concurrent/atomic/thread_local_var.rb +130 -58
  17. data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
  18. data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
  21. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
  22. data/lib/concurrent/atomic_reference/rbx.rb +4 -1
  23. data/lib/concurrent/atomic_reference/ruby.rb +6 -3
  24. data/lib/concurrent/atomics.rb +74 -4
  25. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
  26. data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
  27. data/lib/concurrent/collection/priority_queue.rb +300 -245
  28. data/lib/concurrent/concern/deprecation.rb +34 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +27 -0
  31. data/lib/concurrent/concern/obligation.rb +228 -0
  32. data/lib/concurrent/concern/observable.rb +85 -0
  33. data/lib/concurrent/configuration.rb +234 -109
  34. data/lib/concurrent/dataflow.rb +2 -3
  35. data/lib/concurrent/delay.rb +141 -50
  36. data/lib/concurrent/edge.rb +30 -0
  37. data/lib/concurrent/errors.rb +19 -7
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
  40. data/lib/concurrent/executor/executor.rb +46 -299
  41. data/lib/concurrent/executor/executor_service.rb +521 -0
  42. data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
  43. data/lib/concurrent/executor/immediate_executor.rb +9 -9
  44. data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
  45. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  46. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  47. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  48. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  49. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  50. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  51. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  52. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  53. data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
  54. data/lib/concurrent/executor/timer_set.rb +96 -84
  55. data/lib/concurrent/executors.rb +1 -1
  56. data/lib/concurrent/future.rb +71 -38
  57. data/lib/concurrent/immutable_struct.rb +89 -0
  58. data/lib/concurrent/ivar.rb +152 -60
  59. data/lib/concurrent/lazy_register.rb +40 -20
  60. data/lib/concurrent/maybe.rb +226 -0
  61. data/lib/concurrent/mutable_struct.rb +227 -0
  62. data/lib/concurrent/mvar.rb +44 -43
  63. data/lib/concurrent/promise.rb +229 -136
  64. data/lib/concurrent/scheduled_task.rb +341 -43
  65. data/lib/concurrent/settable_struct.rb +127 -0
  66. data/lib/concurrent/synchronization.rb +17 -0
  67. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  68. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  69. data/lib/concurrent/synchronization/condition.rb +53 -0
  70. data/lib/concurrent/synchronization/java_object.rb +34 -0
  71. data/lib/concurrent/synchronization/lock.rb +32 -0
  72. data/lib/concurrent/synchronization/monitor_object.rb +26 -0
  73. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  74. data/lib/concurrent/synchronization/object.rb +78 -0
  75. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  76. data/lib/concurrent/timer_task.rb +92 -103
  77. data/lib/concurrent/tvar.rb +42 -38
  78. data/lib/concurrent/utilities.rb +3 -1
  79. data/lib/concurrent/utility/at_exit.rb +97 -0
  80. data/lib/concurrent/utility/engine.rb +44 -0
  81. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  82. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  83. data/lib/concurrent/utility/processor_counter.rb +156 -0
  84. data/lib/concurrent/utility/timeout.rb +18 -14
  85. data/lib/concurrent/utility/timer.rb +11 -6
  86. data/lib/concurrent/version.rb +2 -1
  87. data/lib/concurrent_ruby.rb +1 -0
  88. data/lib/concurrent_ruby_ext.jar +0 -0
  89. metadata +46 -66
  90. data/lib/concurrent/actor.rb +0 -103
  91. data/lib/concurrent/actor/behaviour.rb +0 -70
  92. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  93. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  94. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  95. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  96. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  97. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  98. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  99. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  100. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  101. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  102. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  103. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  104. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  105. data/lib/concurrent/actor/context.rb +0 -154
  106. data/lib/concurrent/actor/core.rb +0 -217
  107. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  108. data/lib/concurrent/actor/envelope.rb +0 -41
  109. data/lib/concurrent/actor/errors.rb +0 -27
  110. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  111. data/lib/concurrent/actor/public_delegations.rb +0 -40
  112. data/lib/concurrent/actor/reference.rb +0 -81
  113. data/lib/concurrent/actor/root.rb +0 -37
  114. data/lib/concurrent/actor/type_check.rb +0 -48
  115. data/lib/concurrent/actor/utils.rb +0 -10
  116. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  117. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  118. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  119. data/lib/concurrent/actor/utils/pool.rb +0 -59
  120. data/lib/concurrent/actress.rb +0 -3
  121. data/lib/concurrent/agent.rb +0 -209
  122. data/lib/concurrent/atomic.rb +0 -92
  123. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  124. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  125. data/lib/concurrent/atomic/synchronization.rb +0 -51
  126. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  127. data/lib/concurrent/channel/channel.rb +0 -41
  128. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  129. data/lib/concurrent/channel/waitable_list.rb +0 -40
  130. data/lib/concurrent/channels.rb +0 -5
  131. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  132. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  133. data/lib/concurrent/collections.rb +0 -3
  134. data/lib/concurrent/dereferenceable.rb +0 -108
  135. data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
  136. data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
  137. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
  138. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
  139. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  140. data/lib/concurrent/logging.rb +0 -20
  141. data/lib/concurrent/obligation.rb +0 -171
  142. data/lib/concurrent/observable.rb +0 -73
  143. data/lib/concurrent/options_parser.rb +0 -48
  144. data/lib/concurrent/utility/processor_count.rb +0 -152
  145. data/lib/extension_helper.rb +0 -37
@@ -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
- # {include:file:doc/async.md}
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
- # @since 0.6.0
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
- def validate_argc(obj, method, *args)
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
- module_function :validate_argc
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 # :nodoc:
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
- value = @delegate.send(method, *args2, &block)
172
+ ivar.set(@delegate.send(method, *args2, &block))
91
173
  rescue => reason
92
- # caught
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
- # @note The method call is guaranteed to be thread safe with respect to
119
- # all other method calls against the same object that are called with
120
- # either `async` or `await`. The mutable nature of Ruby references
121
- # (and object orientation in general) prevent any other thread safety
122
- # guarantees. Do NOT mix non-protected method calls with protected
123
- # method call. Use *only* protected method calls when sharing the object
124
- # between threads.
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
- # @note The method call is guaranteed to be thread safe with respect to
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 [Concurrent::InitializationError] `#init_mutex` has not been called
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 synchronization objects. This method
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 from the constructor of the including
191
- # class or explicitly by the caller prior to calling any other methods.
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
- raise InitializationError.new('#init_mutex was already called') if @__async_initialized__
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
- @__async_executor__ = Delay.new{ Concurrent.configuration.global_operation_pool }
200
- @__await_delegator__ = Delay.new{ AsyncDelegator.new(
201
- self, Delay.new{ Concurrent::ImmediateExecutor.new }, serializer, true) }
202
- @__async_delegator__ = Delay.new{ AsyncDelegator.new(
203
- self, @__async_executor__, serializer, false) }
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
- require_relative '../../extension_helper'
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
- class MutexAtomicBoolean
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
- @value = !!initial
33
- @mutex = Mutex.new
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
- @mutex.lock
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
- @mutex.lock
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
- @mutex.lock
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
- @mutex.lock
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
- @mutex.lock
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
- @mutex.lock
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
- if RUBY_PLATFORM == 'java'
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
- # @!macro atomic_boolean
161
- class AtomicBoolean < JavaAtomicBoolean
99
+ # @!visibility private
100
+ def ns_initialize(initial)
101
+ @value = !!initial
162
102
  end
163
103
 
164
- elsif defined?(CAtomicBoolean)
165
-
166
- # @!macro atomic_boolean
167
- class CAtomicBoolean
168
-
169
- # @!method initialize
170
- # @!macro atomic_boolean_method_initialize
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
- # @!method value
173
- # @!macro atomic_boolean_method_value_get
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
- # @!method value=
176
- # @!macro atomic_boolean_method_value_set
129
+ # @!method initialize(initial = false)
130
+ # @!macro atomic_boolean_method_initialize
177
131
 
178
- # @!method true?
179
- # @!macro atomic_boolean_method_true_question
132
+ # @!method value
133
+ # @!macro atomic_boolean_method_value_get
180
134
 
181
- # @!method false?
182
- # @!macro atomic_boolean_method_false_question
135
+ # @!method value=(value)
136
+ # @!macro atomic_boolean_method_value_set
183
137
 
184
- # @!method make_true
185
- # @!macro atomic_boolean_method_make_true
138
+ # @!method true?
139
+ # @!macro atomic_boolean_method_true_question
186
140
 
187
- # @!method make_false
188
- # @!macro atomic_boolean_method_make_false
189
- end
141
+ # @!method false?
142
+ # @!macro atomic_boolean_method_false_question
190
143
 
191
- # @!macro atomic_boolean
192
- class AtomicBoolean < CAtomicBoolean
193
- end
144
+ # @!method make_true
145
+ # @!macro atomic_boolean_method_make_true
194
146
 
195
- else
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