use futures::Stream;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::watch;
pub use futures::StreamExt;
pub type Async<T> = Pin<Box<dyn Future<Output = T> + Send>>;
pub type AsyncStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>;
#[derive(Clone, Debug)]
pub struct CancelToken {
tx: Arc<watch::Sender<bool>>,
}
impl Default for CancelToken {
fn default() -> Self {
let (tx, _rx) = watch::channel(false);
Self { tx: Arc::new(tx) }
}
}
impl CancelToken {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.tx.send_replace(true);
}
pub fn is_cancelled(&self) -> bool {
*self.tx.borrow()
}
pub async fn cancelled(&self) {
let mut rx = self.tx.subscribe();
loop {
if *rx.borrow_and_update() {
return;
}
if rx.changed().await.is_err() {
return;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn cancel_is_sticky_and_wakes_late_waiter() {
let token = CancelToken::new();
token.cancel();
tokio::time::timeout(Duration::from_millis(100), token.cancelled())
.await
.expect("late waiter hung on sticky cancel");
assert!(token.is_cancelled());
}
#[tokio::test]
async fn cancel_wakes_concurrent_waiters() {
let token = CancelToken::new();
let t1 = token.clone();
let t2 = token.clone();
let w1 = tokio::spawn(async move { t1.cancelled().await });
let w2 = tokio::spawn(async move { t2.cancelled().await });
tokio::task::yield_now().await;
token.cancel();
tokio::time::timeout(Duration::from_secs(1), async {
w1.await.unwrap();
w2.await.unwrap();
})
.await
.expect("waiters did not wake");
}
#[tokio::test]
async fn cancel_before_wait_is_not_lost() {
for _ in 0..100 {
let token = CancelToken::new();
let waiter = token.clone();
let handle = tokio::spawn(async move {
tokio::task::yield_now().await;
waiter.cancelled().await;
});
token.cancel();
tokio::time::timeout(Duration::from_millis(200), handle)
.await
.expect("cancel-before-wait hung")
.unwrap();
}
}
}