use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};
use std::task::Waker;
#[derive(Debug, Default)]
struct State {
cancelled: AtomicBool,
waiters: Mutex<Vec<Waker>>,
}
#[derive(Clone, Debug, Default)]
pub struct Cancellation {
state: Arc<State>,
}
impl Cancellation {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
if !self.state.cancelled.swap(true, Ordering::AcqRel) {
let waiters =
std::mem::take(&mut *self.state.waiters.lock().expect("cancel lock poisoned"));
for waiter in waiters {
waiter.wake();
}
}
}
pub fn is_cancelled(&self) -> bool {
self.state.cancelled.load(Ordering::Acquire)
}
pub(crate) fn register(&self, waker: &Waker) {
let mut waiters = self.state.waiters.lock().expect("cancel lock poisoned");
if self.is_cancelled() {
waker.wake_by_ref();
} else if !waiters.iter().any(|waiter| waiter.will_wake(waker)) {
waiters.push(waker.clone());
}
}
}
#[cfg(test)]
mod tests {
use super::Cancellation;
#[test]
fn cancellation_is_shared_within_a_run_but_isolated_between_runs() {
let first_run = Cancellation::new();
let first_run_child = first_run.clone();
let second_run = Cancellation::new();
first_run.cancel();
assert!(first_run_child.is_cancelled());
assert!(!second_run.is_cancelled());
}
}