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

Add Atomic::compare_exchange(_weak) and deprecate Atomic::compare_and_set(_weak) #628

Merged
merged 2 commits into from
Jan 3, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 181 additions & 25 deletions crossbeam-epoch/src/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use core::sync::atomic::Ordering;

use crate::alloc::alloc;
use crate::alloc::boxed::Box;
use crate::primitive::sync::atomic::AtomicUsize;
use crate::guard::Guard;
use crate::primitive::sync::atomic::AtomicUsize;
use crossbeam_utils::atomic::AtomicConsume;

/// Given ordering for the success case in a compare-exchange operation, returns the strongest
Expand All @@ -26,17 +26,22 @@ fn strongest_failure_ordering(ord: Ordering) -> Ordering {
}

/// The error returned on failed compare-and-set operation.
pub struct CompareAndSetError<'g, T: ?Sized + Pointable, P: Pointer<T>> {
// TODO: remove in the next major version.
#[deprecated(note = "Use `CompareExchangeError` instead")]
pub type CompareAndSetError<'g, T, P> = CompareExchangeError<'g, T, P>;

/// The error returned on failed compare-and-swap operation.
pub struct CompareExchangeError<'g, T: ?Sized + Pointable, P: Pointer<T>> {
/// The value in the atomic pointer at the time of the failed operation.
pub current: Shared<'g, T>,

/// The new value, which the operation failed to store.
pub new: P,
}

impl<'g, T: 'g, P: Pointer<T> + fmt::Debug> fmt::Debug for CompareAndSetError<'g, T, P> {
impl<T, P: Pointer<T> + fmt::Debug> fmt::Debug for CompareExchangeError<'_, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CompareAndSetError")
f.debug_struct("CompareExchangeError")
.field("current", &self.current)
.field("new", &self.new)
.finish()
Expand All @@ -54,6 +59,11 @@ impl<'g, T: 'g, P: Pointer<T> + fmt::Debug> fmt::Debug for CompareAndSetError<'g
/// ordering is chosen.
/// 2. A pair of `Ordering`s. The first one is for the success case, while the second one is
/// for the failure case.
// TODO: remove in the next major version.
#[deprecated(
note = "`compare_and_set` and `compare_and_set_weak` that use this trait are deprecated, \
use `compare_exchange` or `compare_exchange_weak instead`"
)]
pub trait CompareAndSetOrdering {
/// The ordering of the operation when it succeeds.
fn success(&self) -> Ordering;
Expand All @@ -65,6 +75,7 @@ pub trait CompareAndSetOrdering {
fn failure(&self) -> Ordering;
}

#[allow(deprecated)]
impl CompareAndSetOrdering for Ordering {
#[inline]
fn success(&self) -> Ordering {
Expand All @@ -77,6 +88,7 @@ impl CompareAndSetOrdering for Ordering {
}
}

#[allow(deprecated)]
impl CompareAndSetOrdering for (Ordering, Ordering) {
#[inline]
fn success(&self) -> Ordering {
Expand Down Expand Up @@ -426,8 +438,14 @@ impl<T: ?Sized + Pointable> Atomic<T> {
/// pointer that was written is returned. On failure the actual current value and `new` are
/// returned.
///
/// This method takes a [`CompareAndSetOrdering`] argument which describes the memory
/// ordering of this operation.
/// This method takes two `Ordering` arguments to describe the memory
/// ordering of this operation. `success` describes the required ordering for the
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
/// `failure` describes the required ordering for the load operation that takes place when
/// the comparison fails. Using `Acquire` as success ordering makes the store part
/// of this operation `Relaxed`, and using `Release` makes the successful load
/// `Relaxed`. The failure ordering can only be `SeqCst`, `Acquire` or `Relaxed`
/// and must be equivalent to or weaker than the success ordering.
///
/// # Examples
///
Expand All @@ -439,32 +457,162 @@ impl<T: ?Sized + Pointable> Atomic<T> {
///
/// let guard = &epoch::pin();
/// let curr = a.load(SeqCst, guard);
/// let res1 = a.compare_and_set(curr, Shared::null(), SeqCst, guard);
/// let res2 = a.compare_and_set(curr, Owned::new(5678), SeqCst, guard);
/// let res1 = a.compare_exchange(curr, Shared::null(), SeqCst, SeqCst, guard);
/// let res2 = a.compare_exchange(curr, Owned::new(5678), SeqCst, SeqCst, guard);
/// ```
pub fn compare_and_set<'g, O, P>(
pub fn compare_exchange<'g, P>(
&self,
current: Shared<'_, T>,
new: P,
ord: O,
success: Ordering,
failure: Ordering,
_: &'g Guard,
) -> Result<Shared<'g, T>, CompareAndSetError<'g, T, P>>
) -> Result<Shared<'g, T>, CompareExchangeError<'g, T, P>>
where
O: CompareAndSetOrdering,
P: Pointer<T>,
{
let new = new.into_usize();
self.data
.compare_exchange(current.into_usize(), new, ord.success(), ord.failure())
.compare_exchange(current.into_usize(), new, success, failure)
.map(|_| unsafe { Shared::from_usize(new) })
.map_err(|current| unsafe {
CompareAndSetError {
CompareExchangeError {
current: Shared::from_usize(current),
new: P::from_usize(new),
}
})
}

/// Stores the pointer `new` (either `Shared` or `Owned`) into the atomic pointer if the current
/// value is the same as `current`. The tag is also taken into account, so two pointers to the
/// same object, but with different tags, will not be considered equal.
///
/// Unlike [`compare_exchange`], this method is allowed to spuriously fail even when comparison
/// succeeds, which can result in more efficient code on some platforms. The return value is a
/// result indicating whether the new pointer was written. On success the pointer that was
/// written is returned. On failure the actual current value and `new` are returned.
///
/// This method takes two `Ordering` arguments to describe the memory
/// ordering of this operation. `success` describes the required ordering for the
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
/// `failure` describes the required ordering for the load operation that takes place when
/// the comparison fails. Using `Acquire` as success ordering makes the store part
/// of this operation `Relaxed`, and using `Release` makes the successful load
/// `Relaxed`. The failure ordering can only be `SeqCst`, `Acquire` or `Relaxed`
/// and must be equivalent to or weaker than the success ordering.
///
/// [`compare_exchange`]: Atomic::compare_exchange
///
/// # Examples
///
/// ```
/// use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
/// use std::sync::atomic::Ordering::SeqCst;
///
/// let a = Atomic::new(1234);
/// let guard = &epoch::pin();
///
/// let mut new = Owned::new(5678);
/// let mut ptr = a.load(SeqCst, guard);
/// loop {
/// match a.compare_exchange_weak(ptr, new, SeqCst, SeqCst, guard) {
/// Ok(p) => {
/// ptr = p;
/// break;
/// }
/// Err(err) => {
/// ptr = err.current;
/// new = err.new;
/// }
/// }
/// }
///
/// let mut curr = a.load(SeqCst, guard);
/// loop {
/// match a.compare_exchange_weak(curr, Shared::null(), SeqCst, SeqCst, guard) {
/// Ok(_) => break,
/// Err(err) => curr = err.current,
/// }
/// }
/// ```
pub fn compare_exchange_weak<'g, P>(
&self,
current: Shared<'_, T>,
new: P,
success: Ordering,
failure: Ordering,
_: &'g Guard,
) -> Result<Shared<'g, T>, CompareExchangeError<'g, T, P>>
where
P: Pointer<T>,
{
let new = new.into_usize();
self.data
.compare_exchange_weak(current.into_usize(), new, success, failure)
.map(|_| unsafe { Shared::from_usize(new) })
.map_err(|current| unsafe {
CompareExchangeError {
current: Shared::from_usize(current),
new: P::from_usize(new),
}
})
}

/// Stores the pointer `new` (either `Shared` or `Owned`) into the atomic pointer if the current
/// value is the same as `current`. The tag is also taken into account, so two pointers to the
/// same object, but with different tags, will not be considered equal.
///
/// The return value is a result indicating whether the new pointer was written. On success the
/// pointer that was written is returned. On failure the actual current value and `new` are
/// returned.
///
/// This method takes a [`CompareAndSetOrdering`] argument which describes the memory
/// ordering of this operation.
///
/// # Migrating to `compare_exchange`
///
/// `compare_and_set` is equivalent to `compare_exchange` with the following mapping for
/// memory orderings:
///
/// Original | Success | Failure
/// -------- | ------- | -------
/// Relaxed | Relaxed | Relaxed
/// Acquire | Acquire | Acquire
/// Release | Release | Relaxed
/// AcqRel | AcqRel | Acquire
/// SeqCst | SeqCst | SeqCst
///
/// # Examples
///
/// ```
/// # #![allow(deprecated)]
/// use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
/// use std::sync::atomic::Ordering::SeqCst;
///
/// let a = Atomic::new(1234);
///
/// let guard = &epoch::pin();
/// let curr = a.load(SeqCst, guard);
/// let res1 = a.compare_and_set(curr, Shared::null(), SeqCst, guard);
/// let res2 = a.compare_and_set(curr, Owned::new(5678), SeqCst, guard);
/// ```
// TODO: remove in the next major version.
#[allow(deprecated)]
#[deprecated(note = "Use `compare_exchange` instead")]
pub fn compare_and_set<'g, O, P>(
&self,
current: Shared<'_, T>,
new: P,
ord: O,
guard: &'g Guard,
) -> Result<Shared<'g, T>, CompareAndSetError<'g, T, P>>
where
O: CompareAndSetOrdering,
P: Pointer<T>,
{
self.compare_exchange(current, new, ord.success(), ord.failure(), guard)
}

/// Stores the pointer `new` (either `Shared` or `Owned`) into the atomic pointer if the current
/// value is the same as `current`. The tag is also taken into account, so two pointers to the
/// same object, but with different tags, will not be considered equal.
Expand All @@ -479,9 +627,23 @@ impl<T: ?Sized + Pointable> Atomic<T> {
///
/// [`compare_and_set`]: Atomic::compare_and_set
///
/// # Migrating to `compare_exchange_weak`
///
/// `compare_and_set_weak` is equivalent to `compare_exchange_weak` with the following mapping for
/// memory orderings:
///
/// Original | Success | Failure
/// -------- | ------- | -------
/// Relaxed | Relaxed | Relaxed
/// Acquire | Acquire | Acquire
/// Release | Release | Relaxed
/// AcqRel | AcqRel | Acquire
/// SeqCst | SeqCst | SeqCst
///
/// # Examples
///
/// ```
/// # #![allow(deprecated)]
/// use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
/// use std::sync::atomic::Ordering::SeqCst;
///
Expand Down Expand Up @@ -511,27 +673,21 @@ impl<T: ?Sized + Pointable> Atomic<T> {
/// }
/// }
/// ```
// TODO: remove in the next major version.
#[allow(deprecated)]
#[deprecated(note = "Use `compare_exchange_weak` instead")]
pub fn compare_and_set_weak<'g, O, P>(
&self,
current: Shared<'_, T>,
new: P,
ord: O,
_: &'g Guard,
guard: &'g Guard,
) -> Result<Shared<'g, T>, CompareAndSetError<'g, T, P>>
where
O: CompareAndSetOrdering,
P: Pointer<T>,
{
let new = new.into_usize();
self.data
.compare_exchange_weak(current.into_usize(), new, ord.success(), ord.failure())
.map(|_| unsafe { Shared::from_usize(new) })
.map_err(|current| unsafe {
CompareAndSetError {
current: Shared::from_usize(current),
new: P::from_usize(new),
}
})
self.compare_exchange_weak(current, new, ord.success(), ord.failure(), guard)
}

/// Bitwise "and" with the current tag.
Expand Down
2 changes: 1 addition & 1 deletion crossbeam-epoch/src/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl AtomicEpoch {
/// The return value is a result indicating whether the new value was written and containing
/// the previous value. On success this value is guaranteed to be equal to `current`.
///
/// `compare_exchange` takes two `Ordering` arguments to describe the memory
/// This method takes two `Ordering` arguments to describe the memory
/// ordering of this operation. `success` describes the required ordering for the
/// read-modify-write operation that takes place if the comparison with `current` succeeds.
/// `failure` describes the required ordering for the load operation that takes place when
Expand Down
8 changes: 7 additions & 1 deletion crossbeam-epoch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,15 @@ cfg_if! {
mod internal;
mod sync;

pub use self::atomic::{Pointable, Atomic, CompareAndSetError, CompareAndSetOrdering, Owned, Pointer, Shared};
pub use self::atomic::{
Pointable, Atomic, CompareExchangeError,
Owned, Pointer, Shared,
};
pub use self::collector::{Collector, LocalHandle};
pub use self::guard::{unprotected, Guard};

#[allow(deprecated)]
pub use self::atomic::{CompareAndSetError, CompareAndSetOrdering};
}
}

Expand Down
4 changes: 2 additions & 2 deletions crossbeam-epoch/src/sync/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl<T, C: IsElement<T>> List<T, C> {
// Set the Entry of the to-be-inserted element to point to the previous successor of
// `to`.
entry.next.store(next, Relaxed);
match to.compare_and_set_weak(next, entry_ptr, Release, guard) {
match to.compare_exchange_weak(next, entry_ptr, Release, Relaxed, guard) {
Ok(_) => break,
// We lost the race or weak CAS failed spuriously. Update the successor and try
// again.
Expand Down Expand Up @@ -250,7 +250,7 @@ impl<'g, T: 'g, C: IsElement<T>> Iterator for Iter<'g, T, C> {
// Try to unlink `curr` from the list, and get the new value of `self.pred`.
let succ = match self
.pred
.compare_and_set(self.curr, succ, Acquire, self.guard)
.compare_exchange(self.curr, succ, Acquire, Acquire, self.guard)
{
Ok(_) => {
// We succeeded in unlinking `curr`, so we have to schedule
Expand Down
Loading