use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::Notify;
#[derive(Clone, Default)]
pub struct Cancel {
inner: Arc<Inner>,
}
#[derive(Default)]
struct Inner {
cancelled: AtomicBool,
woken: Notify,
}
impl Cancel {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.inner.cancelled.store(true, Ordering::SeqCst);
self.inner.woken.notify_waiters();
}
pub fn is_cancelled(&self) -> bool {
self.inner.cancelled.load(Ordering::SeqCst)
}
pub async fn cancelled(&self) {
let mut woken = pin!(self.inner.woken.notified());
woken.as_mut().enable();
if self.is_cancelled() {
return;
}
woken.await;
}
pub async fn guard<F: Future>(&self, fut: F) -> Option<F::Output> {
tokio::select! {
biased;
() = self.cancelled() => None,
out = fut => Some(out),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_guarded_future_is_dropped_when_the_token_is_cancelled() {
let cancel = Cancel::new();
let cancelling = cancel.clone();
tokio::spawn(async move { cancelling.cancel() });
let forever = std::future::pending::<()>();
assert!(cancel.guard(forever).await.is_none());
}
#[tokio::test]
async fn a_future_that_finishes_first_still_answers() {
let cancel = Cancel::new();
assert_eq!(cancel.guard(async { 7 }).await, Some(7));
assert!(!cancel.is_cancelled());
}
#[tokio::test]
async fn waiting_on_a_token_cancelled_earlier_returns_at_once() {
let cancel = Cancel::new();
cancel.cancel();
cancel.cancelled().await;
assert!(cancel.guard(async { 7 }).await.is_none());
}
}