#![allow(unsafe_code)]
use core::fmt;
use core::task::Waker;
use super::internal::{AtomicUsize, Ordering, UnsafeCell, during_unwind};
const WAITING: usize = 0;
const REGISTERING: usize = 0b01;
const WAKING: usize = 0b10;
pub struct AtomicWaker {
state: AtomicUsize,
waker: UnsafeCell<Option<Waker>>,
}
unsafe impl Send for AtomicWaker {}
unsafe impl Sync for AtomicWaker {}
impl AtomicWaker {
loom_const_fn! {
pub fn new() -> Self {
Self { state: AtomicUsize::new(WAITING), waker: UnsafeCell::new(None) }
}
}
pub fn register(&self, waker: &Waker) {
if let Registered::InUse = self.register_returning(waker) {
waker.wake_by_ref();
}
}
pub(crate) fn register_displacing(&self, waker: &Waker) -> bool {
match self.register_returning(waker) {
Registered::Kept(displaced) => {
if let Some(displaced) = displaced {
displaced.wake();
}
true
}
Registered::InUse => {
waker.wake_by_ref();
false
}
Registered::WokenNow => false,
}
}
pub(crate) fn try_register_displacing(&self, waker: &Waker) -> Registered {
let registered = self.register_returning(waker);
if let Registered::Kept(Some(displaced)) = registered {
displaced.wake();
return Registered::Kept(None);
}
registered
}
#[inline]
fn register_returning(&self, waker: &Waker) -> Registered {
if self
.state
.compare_exchange(WAITING, REGISTERING, Ordering::Acquire, Ordering::Acquire)
.is_err()
{
return Registered::InUse;
}
let displaced = self.waker.with_mut(|slot| unsafe {
match &*slot {
Some(old) if old.will_wake(waker) => None,
_ => {
let abandon = Abandon(self);
let fresh = waker.clone();
core::mem::forget(abandon);
(*slot).replace(fresh)
}
}
});
if self
.state
.compare_exchange(REGISTERING, WAITING, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
self.woken_while_registering(displaced);
return Registered::WokenNow;
}
Registered::Kept(displaced)
}
#[cold]
#[inline(never)]
fn woken_while_registering(&self, displaced: Option<Waker>) {
let taken = self.waker.with_mut(|slot| unsafe { (*slot).take() });
self.state.swap(WAITING, Ordering::AcqRel);
let mut displaced = WakeOnUnwind(displaced);
if let Some(taken) = taken {
taken.wake();
}
if let Some(displaced) = displaced.0.take() {
displaced.wake();
}
}
pub fn take(&self) -> Option<Waker> {
if self.state.fetch_or(WAKING, Ordering::AcqRel) != WAITING {
return None;
}
let taken = self.waker.with_mut(|slot| unsafe { (*slot).take() });
self.state.fetch_and(!WAKING, Ordering::Release);
taken
}
pub fn wake(&self) {
if let Some(waker) = self.take() {
waker.wake();
}
}
}
struct Abandon<'a>(&'a AtomicWaker);
impl Drop for Abandon<'_> {
#[cold]
fn drop(&mut self) {
let cell = self.0;
if cell
.state
.compare_exchange(REGISTERING, WAITING, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
let taken = cell.waker.with_mut(|slot| unsafe { (*slot).take() });
cell.state.swap(WAITING, Ordering::AcqRel);
if let Some(taken) = taken {
during_unwind(|| taken.wake());
}
}
}
}
struct WakeOnUnwind(Option<Waker>);
impl Drop for WakeOnUnwind {
#[cold]
fn drop(&mut self) {
if let Some(waker) = self.0.take() {
during_unwind(|| waker.wake());
}
}
}
pub(crate) enum Registered {
Kept(Option<Waker>),
InUse,
WokenNow,
}
impl Default for AtomicWaker {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for AtomicWaker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AtomicWaker").finish_non_exhaustive()
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
use std::task::Wake;
use std::time::{Duration, Instant};
struct Counter(StdAtomicUsize);
impl Wake for Counter {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, StdOrdering::SeqCst);
}
}
fn counting() -> (Arc<Counter>, Waker) {
let counter = Arc::new(Counter(StdAtomicUsize::new(0)));
let waker = Waker::from(Arc::clone(&counter));
(counter, waker)
}
fn wakes(counter: &Counter) -> usize {
counter.0.load(StdOrdering::SeqCst)
}
#[test]
fn a_registered_waker_is_woken_once_and_the_cell_empties() {
let cell = AtomicWaker::new();
let (counter, waker) = counting();
cell.register(&waker);
cell.wake();
assert_eq!(wakes(&counter), 1);
cell.wake();
assert_eq!(wakes(&counter), 1, "the cell was emptied by the first wake");
}
#[test]
fn a_wake_before_any_registration_takes_nothing() {
let cell = AtomicWaker::new();
cell.wake();
assert!(cell.take().is_none());
let (counter, waker) = counting();
cell.register(&waker);
assert_eq!(wakes(&counter), 0, "an earlier wake is not replayed");
cell.wake();
assert_eq!(wakes(&counter), 1);
}
#[test]
fn a_new_registration_replaces_the_old_one() {
let cell = AtomicWaker::new();
let (first, first_waker) = counting();
let (second, second_waker) = counting();
cell.register(&first_waker);
cell.register(&second_waker);
cell.wake();
assert_eq!((wakes(&first), wakes(&second)), (0, 1));
}
#[test]
fn registering_the_same_waker_again_keeps_one_wake() {
let cell = AtomicWaker::new();
let (counter, waker) = counting();
cell.register(&waker);
cell.register(&waker.clone());
cell.wake();
assert_eq!(wakes(&counter), 1);
}
#[test]
fn take_hands_the_waker_to_the_caller() {
let cell = AtomicWaker::new();
let (counter, waker) = counting();
cell.register(&waker);
let taken = cell.take().expect("a waker was registered");
assert_eq!(wakes(&counter), 0, "take does not call the waker");
taken.wake();
assert_eq!(wakes(&counter), 1);
assert!(cell.take().is_none());
}
#[test]
fn a_registration_that_meets_a_wake_in_progress_wakes_itself() {
let cell = AtomicWaker::new();
cell.state.store(WAKING, StdOrdering::SeqCst);
let (counter, waker) = counting();
cell.register(&waker);
assert_eq!(wakes(&counter), 1, "the contended register woke its caller");
cell.state.store(WAITING, StdOrdering::SeqCst);
cell.wake();
assert_eq!(wakes(&counter), 1, "nothing was registered");
}
struct Interrupting {
cell: *const AtomicWaker,
clones: StdAtomicUsize,
wakes: StdAtomicUsize,
}
unsafe impl Send for Interrupting {}
unsafe impl Sync for Interrupting {}
static INTERRUPTING: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
interrupting_clone,
interrupting_wake,
interrupting_wake_by_ref,
interrupting_drop,
);
unsafe fn interrupting_clone(data: *const ()) -> std::task::RawWaker {
let hook = unsafe { &*(data as *const Interrupting) };
if hook.clones.fetch_add(1, StdOrdering::SeqCst) == 0 {
let landed = unsafe { &*hook.cell }.take();
assert!(landed.is_none(), "a wake over a registration takes nothing");
}
unsafe { Arc::increment_strong_count(data as *const Interrupting) };
std::task::RawWaker::new(data, &INTERRUPTING)
}
unsafe fn interrupting_wake(data: *const ()) {
let hook = unsafe { Arc::from_raw(data as *const Interrupting) };
hook.wakes.fetch_add(1, StdOrdering::SeqCst);
}
unsafe fn interrupting_wake_by_ref(data: *const ()) {
unsafe { &*(data as *const Interrupting) }
.wakes
.fetch_add(1, StdOrdering::SeqCst);
}
unsafe fn interrupting_drop(data: *const ()) {
drop(unsafe { Arc::from_raw(data as *const Interrupting) });
}
#[test]
fn a_wake_that_lands_during_registration_is_delivered_by_the_registrant() {
let cell = AtomicWaker::new();
let hook = Arc::new(Interrupting {
cell: &cell,
clones: StdAtomicUsize::new(0),
wakes: StdAtomicUsize::new(0),
});
let waker = unsafe {
Waker::from_raw(std::task::RawWaker::new(
Arc::into_raw(Arc::clone(&hook)) as *const (),
&INTERRUPTING,
))
};
cell.register(&waker);
assert_eq!(hook.clones.load(StdOrdering::SeqCst), 1);
assert_eq!(
hook.wakes.load(StdOrdering::SeqCst),
1,
"the registrant woke itself for the wake it could not take"
);
assert_eq!(cell.state.load(StdOrdering::SeqCst), WAITING);
assert!(cell.take().is_none(), "the slot was emptied");
}
const ROUND: Duration = Duration::from_secs(30);
#[test]
fn no_wakeup_is_lost_between_a_register_and_a_wake() {
let rounds = if cfg!(miri) { 20 } else { 20_000 };
for _ in 0..rounds {
let deadline = Instant::now() + ROUND;
let cell = Arc::new(AtomicWaker::new());
let ready = Arc::new(AtomicBool::new(false));
let (counter, waker) = counting();
let waking = {
let (cell, ready) = (Arc::clone(&cell), Arc::clone(&ready));
std::thread::spawn(move || {
ready.store(true, StdOrdering::SeqCst);
cell.wake();
})
};
cell.register(&waker);
while !ready.load(StdOrdering::SeqCst) && wakes(&counter) == 0 {
assert!(Instant::now() < deadline, "a wakeup was lost");
std::hint::spin_loop();
}
waking.join().expect("waking thread");
}
}
#[test]
fn repeated_registration_from_one_thread_against_a_waking_thread_never_loses_the_last_wake() {
let rounds = if cfg!(miri) { 20 } else { 5_000 };
for _ in 0..rounds {
let deadline = Instant::now() + ROUND;
let cell = Arc::new(AtomicWaker::new());
let ready = Arc::new(AtomicBool::new(false));
let (counter, waker) = counting();
let (other_counter, other) = counting();
let waking = {
let (cell, ready) = (Arc::clone(&cell), Arc::clone(&ready));
std::thread::spawn(move || {
ready.store(true, StdOrdering::SeqCst);
cell.wake();
})
};
cell.register(&other);
cell.register(&waker);
while !ready.load(StdOrdering::SeqCst) && wakes(&counter) + wakes(&other_counter) == 0 {
assert!(Instant::now() < deadline, "a wakeup was lost");
std::hint::spin_loop();
}
waking.join().expect("waking thread");
}
}
fn boom() -> Box<dyn FnOnce()> {
Box::new(|| panic!("a waker panicked"))
}
#[test]
fn a_register_whose_clone_panics_leaves_the_cell_usable() {
let cell = AtomicWaker::new();
let bad = crate::sync::test_support::hooked(Some(boom()), None);
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cell.register(&bad)));
assert!(caught.is_err(), "the panic reaches the caller");
let (counter, waker) = counting();
cell.register(&waker);
assert_eq!(wakes(&counter), 0, "the cell stayed in use");
cell.wake();
assert_eq!(wakes(&counter), 1, "the registration was kept");
}
#[test]
fn a_wake_during_a_clone_that_panics_reaches_the_waker_the_cell_held() {
let cell = Arc::new(AtomicWaker::new());
let (held, held_waker) = counting();
cell.register(&held_waker);
let waking = Arc::clone(&cell);
let bad = crate::sync::test_support::hooked(
Some(Box::new(move || {
waking.wake();
panic!("a clone panicked");
})),
None,
);
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cell.register(&bad)));
assert!(caught.is_err(), "the panic reaches the caller");
assert_eq!(wakes(&held), 1, "the wake that met the registration");
let (counter, waker) = counting();
cell.register(&waker);
assert_eq!(wakes(&counter), 0);
cell.wake();
assert_eq!(wakes(&counter), 1, "the cell works after it");
}
#[test]
fn a_panicking_wake_in_the_failure_path_still_wakes_the_displaced_waker() {
let cell = Arc::new(AtomicWaker::new());
let (displaced, displaced_waker) = counting();
cell.register(&displaced_waker);
let waking = Arc::clone(&cell);
let new =
crate::sync::test_support::hooked(Some(Box::new(move || waking.wake())), Some(boom()));
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cell.register_displacing(&new)
}));
assert!(caught.is_err(), "the panic reaches the caller");
assert_eq!(wakes(&displaced), 1, "the displaced task was woken");
let (counter, waker) = counting();
cell.register(&waker);
cell.wake();
assert_eq!(wakes(&counter), 1);
}
}