Skip to content

Commit

Permalink
Merge #628
Browse files Browse the repository at this point in the history
628: Add Atomic::compare_exchange(_weak) and deprecate Atomic::compare_and_set(_weak) r=jeehoonkang a=taiki-e

Instead of changing the argument of `Atomic::compare_and_set(_weak)`, add new methods `Atomic::compare_exchange(_weak)` that always use two orderings.

This PR adds:

- `Atomic::compare_exchange`
  Unlike `compare_and_set`, this always receive two orderings.
- `Atomic::compare_exchange_weak`
  Unlike `compare_and_set_weak`, this always receive two orderings.
- `CompareExchangeError` (error returned by `Atomic::compare_exchange(_weak)`)
  The definition is the same as `CompareAndSetError`, just renamed `CompareExchangeError`.

This PR deprecates:

- [`Atomic::compare_and_set`](https://docs.rs/crossbeam-epoch/0.9/crossbeam_epoch/struct.Atomic.html#method.compare_and_set)
- [`Atomic::compare_and_set_weak`](https://docs.rs/crossbeam-epoch/0.9/crossbeam_epoch/struct.Atomic.html#method.compare_and_set_weak)
- [`CompareAndSetError`](https://docs.rs/crossbeam-epoch/0.9/crossbeam_epoch/struct.CompareAndSetError.html) (error returned by `Atomic::compare_and_set(_weak)`)
  This is now a type alias of `CompareExchangeError`.
- [`CompareAndSetOrdering`](https://docs.rs/crossbeam-epoch/0.9/crossbeam_epoch/trait.CompareAndSetOrdering.html)

Closes #621

Co-authored-by: Taiki Endo <te316e89@gmail.com>
  • Loading branch information
bors[bot] and taiki-e authored Jan 3, 2021
2 parents f81df02 + a172017 commit 06229f5
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 43 deletions.
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

0 comments on commit 06229f5

Please sign in to comment.