#![allow(unsafe_code)]
use core::fmt;
use core::task::Waker;
use super::internal::{AtomicUsize, Ordering, UnsafeCell};
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
}
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,
_ => (*slot).replace(waker.clone()),
}
});
if self
.state
.compare_exchange(REGISTERING, WAITING, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
let taken = self.waker.with_mut(|slot| unsafe { (*slot).take() });
self.state.swap(WAITING, Ordering::AcqRel);
if let Some(taken) = taken {
taken.wake();
}
drop(displaced);
return Registered::WokenNow;
}
Registered::Kept(displaced)
}
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();
}
}
}
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");
}
#[test]
fn no_wakeup_is_lost_between_a_register_and_a_wake() {
let rounds = if cfg!(miri) { 20 } else { 20_000 };
let deadline = Instant::now() + Duration::from_secs(60);
for _ in 0..rounds {
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 };
let deadline = Instant::now() + Duration::from_secs(60);
for _ in 0..rounds {
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");
}
}
}