From 5e340b48a30d26b490b642851e850f61e1cdc19b Mon Sep 17 00:00:00 2001 From: Petr Chalupa Date: Fri, 6 Jul 2018 16:42:58 +0200 Subject: [PATCH] Move out of Atomic namespace --- .../atomic/atomic_markable_reference.rb | 276 +++++++++--------- .../edge/lock_free_linked_set/node.rb | 1 - .../atomic/atomic_markable_reference_spec.rb | 2 +- 3 files changed, 139 insertions(+), 140 deletions(-) diff --git a/lib/concurrent/atomic/atomic_markable_reference.rb b/lib/concurrent/atomic/atomic_markable_reference.rb index 776ab43fc..ed616609f 100644 --- a/lib/concurrent/atomic/atomic_markable_reference.rb +++ b/lib/concurrent/atomic/atomic_markable_reference.rb @@ -1,163 +1,163 @@ module Concurrent - module Atomic - # An atomic reference which maintains an object reference along with a mark bit - # that can be updated atomically. - # - # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html - # java.util.concurrent.atomic.AtomicMarkableReference - class AtomicMarkableReference < ::Concurrent::Synchronization::Object - - private(*attr_atomic(:reference)) + # An atomic reference which maintains an object reference along with a mark bit + # that can be updated atomically. + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html + # java.util.concurrent.atomic.AtomicMarkableReference + class AtomicMarkableReference < ::Concurrent::Synchronization::Object + + private(*attr_atomic(:reference)) + + def initialize(value = nil, mark = false) + super() + self.reference = immutable_array(value, mark) + end - def initialize(value = nil, mark = false) - super() - self.reference = immutable_array(value, mark) + # Atomically sets the value and mark to the given updated value and + # mark given both: + # - the current value == the expected value && + # - the current mark == the expected mark + # + # @param [Object] expected_val the expected value + # @param [Object] new_val the new value + # @param [Boolean] expected_mark the expected mark + # @param [Boolean] new_mark the new mark + # + # @return [Boolean] `true` if successful. A `false` return indicates + # that the actual value was not equal to the expected value or the + # actual mark was not equal to the expected mark + def compare_and_set(expected_val, new_val, expected_mark, new_mark) + # Memoize a valid reference to the current AtomicReference for + # later comparison. + current = reference + curr_val, curr_mark = current + + # Ensure that that the expected marks match. + return false unless expected_mark == curr_mark + + if expected_val.is_a? Numeric + # If the object is a numeric, we need to ensure we are comparing + # the numerical values + return false unless expected_val == curr_val + else + # Otherwise, we need to ensure we are comparing the object identity. + # Theoretically, this could be incorrect if a user monkey-patched + # `Object#equal?`, but they should know that they are playing with + # fire at that point. + return false unless expected_val.equal? curr_val end - # Atomically sets the value and mark to the given updated value and - # mark given both: - # - the current value == the expected value && - # - the current mark == the expected mark - # - # @param [Object] expected_val the expected value - # @param [Object] new_val the new value - # @param [Boolean] expected_mark the expected mark - # @param [Boolean] new_mark the new mark - # - # @return [Boolean] `true` if successful. A `false` return indicates - # that the actual value was not equal to the expected value or the - # actual mark was not equal to the expected mark - def compare_and_set(expected_val, new_val, expected_mark, new_mark) - # Memoize a valid reference to the current AtomicReference for - # later comparison. - current = reference - curr_val, curr_mark = current - - # Ensure that that the expected marks match. - return false unless expected_mark == curr_mark - - if expected_val.is_a? Numeric - # If the object is a numeric, we need to ensure we are comparing - # the numerical values - return false unless expected_val == curr_val - else - # Otherwise, we need to ensure we are comparing the object identity. - # Theoretically, this could be incorrect if a user monkey-patched - # `Object#equal?`, but they should know that they are playing with - # fire at that point. - return false unless expected_val.equal? curr_val - end + prospect = immutable_array(new_val, new_mark) - prospect = immutable_array(new_val, new_mark) + compare_and_set_reference current, prospect + end - compare_and_set_reference current, prospect - end - alias_method :compare_and_swap, :compare_and_set + alias_method :compare_and_swap, :compare_and_set - # Gets the current reference and marked values. - # - # @return [Array] the current reference and marked values - def get - reference - end + # Gets the current reference and marked values. + # + # @return [Array] the current reference and marked values + def get + reference + end - # Gets the current value of the reference - # - # @return [Object] the current value of the reference - def value - reference[0] - end + # Gets the current value of the reference + # + # @return [Object] the current value of the reference + def value + reference[0] + end - # Gets the current marked value - # - # @return [Boolean] the current marked value - def mark - reference[1] - end - alias_method :marked?, :mark - - # _Unconditionally_ sets to the given value of both the reference and - # the mark. - # - # @param [Object] new_val the new value - # @param [Boolean] new_mark the new mark - # - # @return [Array] both the new value and the new mark - def set(new_val, new_mark) - self.reference = immutable_array(new_val, new_mark) - end + # Gets the current marked value + # + # @return [Boolean] the current marked value + def mark + reference[1] + end - # Pass the current value and marked state to the given block, replacing it - # with the block's results. May retry if the value changes during the - # block's execution. - # - # @yield [Object] Calculate a new value and marked state for the atomic - # reference using given (old) value and (old) marked - # @yieldparam [Object] old_val the starting value of the atomic reference - # @yieldparam [Boolean] old_mark the starting state of marked - # - # @return [Array] the new value and new mark - def update - loop do - old_val, old_mark = reference - new_val, new_mark = yield old_val, old_mark - - if compare_and_set old_val, new_val, old_mark, new_mark - return immutable_array(new_val, new_mark) - end - end - end + alias_method :marked?, :mark - # Pass the current value to the given block, replacing it - # with the block's result. Raise an exception if the update - # fails. - # - # @yield [Object] Calculate a new value and marked state for the atomic - # reference using given (old) value and (old) marked - # @yieldparam [Object] old_val the starting value of the atomic reference - # @yieldparam [Boolean] old_mark the starting state of marked - # - # @return [Array] the new value and marked state - # - # @raise [Concurrent::ConcurrentUpdateError] if the update fails - def try_update! + # _Unconditionally_ sets to the given value of both the reference and + # the mark. + # + # @param [Object] new_val the new value + # @param [Boolean] new_mark the new mark + # + # @return [Array] both the new value and the new mark + def set(new_val, new_mark) + self.reference = immutable_array(new_val, new_mark) + end + + # Pass the current value and marked state to the given block, replacing it + # with the block's results. May retry if the value changes during the + # block's execution. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and new mark + def update + loop do old_val, old_mark = reference new_val, new_mark = yield old_val, old_mark - unless compare_and_set old_val, new_val, old_mark, new_mark - fail ::Concurrent::ConcurrentUpdateError, - 'AtomicMarkableReference: Update failed due to race condition.', - 'Note: If you would like to guarantee an update, please use ' \ - 'the `AtomicMarkableReference#update` method.' + if compare_and_set old_val, new_val, old_mark, new_mark + return immutable_array(new_val, new_mark) end + end + end - immutable_array(new_val, new_mark) + # Pass the current value to the given block, replacing it + # with the block's result. Raise an exception if the update + # fails. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and marked state + # + # @raise [Concurrent::ConcurrentUpdateError] if the update fails + def try_update! + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + unless compare_and_set old_val, new_val, old_mark, new_mark + fail ::Concurrent::ConcurrentUpdateError, + 'AtomicMarkableReference: Update failed due to race condition.', + 'Note: If you would like to guarantee an update, please use ' + + 'the `AtomicMarkableReference#update` method.' end - # Pass the current value to the given block, replacing it with the - # block's result. Simply return nil if update fails. - # - # @yield [Object] Calculate a new value and marked state for the atomic - # reference using given (old) value and (old) marked - # @yieldparam [Object] old_val the starting value of the atomic reference - # @yieldparam [Boolean] old_mark the starting state of marked - # - # @return [Array] the new value and marked state, or nil if - # the update failed - def try_update - old_val, old_mark = reference - new_val, new_mark = yield old_val, old_mark + immutable_array(new_val, new_mark) + end + + # Pass the current value to the given block, replacing it with the + # block's result. Simply return nil if update fails. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and marked state, or nil if + # the update failed + def try_update + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark - return unless compare_and_set old_val, new_val, old_mark, new_mark + return unless compare_and_set old_val, new_val, old_mark, new_mark - immutable_array(new_val, new_mark) - end + immutable_array(new_val, new_mark) + end - private + private - def immutable_array(*args) - args.freeze - end + def immutable_array(*args) + args.freeze end end end diff --git a/lib/concurrent/edge/lock_free_linked_set/node.rb b/lib/concurrent/edge/lock_free_linked_set/node.rb index 20506e1b2..d7fafa370 100644 --- a/lib/concurrent/edge/lock_free_linked_set/node.rb +++ b/lib/concurrent/edge/lock_free_linked_set/node.rb @@ -5,7 +5,6 @@ module Edge class LockFreeLinkedSet class Node < Synchronization::Object include Comparable - include Concurrent::Atomic safe_initialization! diff --git a/spec/concurrent/atomic/atomic_markable_reference_spec.rb b/spec/concurrent/atomic/atomic_markable_reference_spec.rb index 1bfdd6897..6ffab9d13 100644 --- a/spec/concurrent/atomic/atomic_markable_reference_spec.rb +++ b/spec/concurrent/atomic/atomic_markable_reference_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe Concurrent::Atomic::AtomicMarkableReference do +RSpec.describe Concurrent::AtomicMarkableReference do subject { described_class.new 1000, true } describe '.initialize' do