Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move AtomicMarkableReference out of Edge #737

Merged
merged 4 commits into from Jul 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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