Skip to content

Commit

Permalink
Merge pull request #737 from ruby-concurrency/pitr-ch/atomic-markable…
Browse files Browse the repository at this point in the history
…-reference

Move AtomicMarkableReference out of Edge
  • Loading branch information
pitr-ch committed Jul 6, 2018
2 parents 476f593 + 5e340b4 commit 343923d
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 189 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -108,6 +108,7 @@ Thread-safe variables:
* [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html) A lock that supports multiple readers but only one writer.
* [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html) A read/write lock with reentrant and upgrade features.
* [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html) A counting-based locking mechanism that uses permits.
* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atomic/AtomicMarkableReference.html)

### Edge Features

Expand All @@ -129,7 +130,6 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional
inspiration from Clojure [core.async](https://clojure.github.io/core.async/).
* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
* [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
* [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LockFreeStack.html)

Expand Down
1 change: 0 additions & 1 deletion lib/concurrent-edge.rb
Expand Up @@ -6,7 +6,6 @@
require 'concurrent/exchanger'
require 'concurrent/lazy_register'

require 'concurrent/edge/atomic_markable_reference'
require 'concurrent/edge/lock_free_linked_set'
require 'concurrent/edge/lock_free_queue'
require 'concurrent/edge/lock_free_stack'
Expand Down
1 change: 1 addition & 0 deletions lib/concurrent.rb
Expand Up @@ -7,6 +7,7 @@
require 'concurrent/executors'
require 'concurrent/synchronization'

require 'concurrent/atomic/atomic_markable_reference'
require 'concurrent/atomic/atomic_reference'
require 'concurrent/agent'
require 'concurrent/atom'
Expand Down
163 changes: 163 additions & 0 deletions lib/concurrent/atomic/atomic_markable_reference.rb
@@ -0,0 +1,163 @@
module Concurrent
# 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

# 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)

compare_and_set_reference current, prospect
end

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 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

# 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

# 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

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

immutable_array(new_val, new_mark)
end

private

def immutable_array(*args)
args.freeze
end
end
end
184 changes: 0 additions & 184 deletions lib/concurrent/edge/atomic_markable_reference.rb

This file was deleted.

4 changes: 2 additions & 2 deletions lib/concurrent/edge/lock_free_linked_set/node.rb
@@ -1,4 +1,4 @@
require 'concurrent/edge/atomic_markable_reference'
require 'concurrent/atomic/atomic_markable_reference'

module Concurrent
module Edge
Expand All @@ -10,7 +10,7 @@ class Node < Synchronization::Object

def initialize(data = nil, successor = nil)
super()
@SuccessorReference = AtomicMarkableReference.new(successor || Tail.new)
@SuccessorReference = AtomicMarkableReference.new(successor || Tail.new)
@Data = data
@Key = key_for data
end
Expand Down

0 comments on commit 343923d

Please sign in to comment.