use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
#[derive(Default)]
struct Shared {
cancelled: AtomicBool,
wakers: Mutex<Vec<Option<Waker>>>,
}
impl Shared {
fn register(&self, slot: &mut Option<usize>, waker: &Waker) {
let mut wakers = self
.wakers
.lock()
.unwrap_or_else(|error| error.into_inner());
match slot {
Some(index) => wakers[*index] = Some(waker.clone()),
None => {
let index = match wakers.iter().position(Option::is_none) {
Some(free) => free,
None => {
wakers.push(None);
wakers.len() - 1
}
};
wakers[index] = Some(waker.clone());
*slot = Some(index);
}
}
}
fn unregister(&self, slot: &mut Option<usize>) {
if let Some(index) = slot.take() {
let mut wakers = self
.wakers
.lock()
.unwrap_or_else(|error| error.into_inner());
wakers[index] = None;
}
}
}
#[derive(Clone, Default)]
pub struct CancellationToken(Arc<Shared>);
impl CancellationToken {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.0.cancelled.store(true, Ordering::SeqCst);
let parked: Vec<Waker> = {
let mut wakers = self
.0
.wakers
.lock()
.unwrap_or_else(|error| error.into_inner());
wakers.iter_mut().filter_map(Option::take).collect()
};
for waker in parked {
waker.wake();
}
}
pub fn is_cancelled(&self) -> bool {
self.0.cancelled.load(Ordering::SeqCst)
}
pub fn cancelled(&self) -> Cancelled<'_> {
Cancelled {
token: self,
slot: None,
}
}
pub fn run_until_cancelled<F: Future>(&self, future: F) -> RunUntilCancelled<'_, F> {
RunUntilCancelled {
token: self,
slot: None,
future: Box::pin(future),
}
}
}
impl std::fmt::Debug for CancellationToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CancellationToken")
.field("cancelled", &self.is_cancelled())
.finish()
}
}
#[must_use = "a future does nothing unless awaited"]
pub struct Cancelled<'a> {
token: &'a CancellationToken,
slot: Option<usize>,
}
impl Future for Cancelled<'_> {
type Output = ();
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.token.is_cancelled() {
this.token.0.unregister(&mut this.slot);
return Poll::Ready(());
}
this.token.0.register(&mut this.slot, context.waker());
if this.token.is_cancelled() {
this.token.0.unregister(&mut this.slot);
return Poll::Ready(());
}
Poll::Pending
}
}
impl Drop for Cancelled<'_> {
fn drop(&mut self) {
self.token.0.unregister(&mut self.slot);
}
}
#[must_use = "a future does nothing unless awaited"]
pub struct RunUntilCancelled<'a, F> {
token: &'a CancellationToken,
slot: Option<usize>,
future: Pin<Box<F>>,
}
impl<F: Future> Future for RunUntilCancelled<'_, F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.token.is_cancelled() {
this.token.0.unregister(&mut this.slot);
return Poll::Ready(None);
}
this.token.0.register(&mut this.slot, context.waker());
if let Poll::Ready(output) = this.future.as_mut().poll(context) {
this.token.0.unregister(&mut this.slot);
return Poll::Ready(Some(output));
}
if this.token.is_cancelled() {
this.token.0.unregister(&mut this.slot);
return Poll::Ready(None);
}
Poll::Pending
}
}
impl<F> Drop for RunUntilCancelled<'_, F> {
fn drop(&mut self) {
self.token.0.unregister(&mut self.slot);
}
}
#[cfg(test)]
mod tests {
use std::pin::pin;
use std::sync::atomic::AtomicUsize;
use std::task::Wake;
use super::*;
#[test]
fn starts_uncancelled_and_is_shared_across_clones() {
let token = CancellationToken::new();
let clone = token.clone();
assert!(!token.is_cancelled());
assert!(!clone.is_cancelled());
clone.cancel();
assert!(token.is_cancelled());
assert!(clone.is_cancelled());
}
#[test]
fn independent_tokens_do_not_share_state() {
let a = CancellationToken::new();
let b = CancellationToken::new();
a.cancel();
assert!(a.is_cancelled());
assert!(!b.is_cancelled());
}
#[derive(Default)]
struct CountingWaker(AtomicUsize);
impl Wake for CountingWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn poll_once<F: Future>(future: Pin<&mut F>, waker: &Waker) -> Poll<F::Output> {
future.poll(&mut Context::from_waker(waker))
}
#[test]
fn a_finished_future_wins_when_nothing_cancels() {
let token = CancellationToken::new();
let mut future = pin!(token.run_until_cancelled(async { 42 }));
assert_eq!(
poll_once(future.as_mut(), Waker::noop()),
Poll::Ready(Some(42))
);
}
#[test]
fn an_already_cancelled_token_never_starts_the_work() {
let token = CancellationToken::new();
token.cancel();
let started = Arc::new(AtomicBool::new(false));
let flag = started.clone();
let mut future = pin!(token.run_until_cancelled(async move {
flag.store(true, Ordering::SeqCst);
}));
assert_eq!(poll_once(future.as_mut(), Waker::noop()), Poll::Ready(None));
assert!(!started.load(Ordering::SeqCst), "work must not have run");
}
#[test]
fn cancelling_a_pending_future_wakes_it_and_resolves_to_none() {
let token = CancellationToken::new();
let counter = Arc::new(CountingWaker::default());
let waker = Waker::from(counter.clone());
let mut future = pin!(token.run_until_cancelled(std::future::pending::<u8>()));
assert_eq!(poll_once(future.as_mut(), &waker), Poll::Pending);
assert_eq!(counter.0.load(Ordering::SeqCst), 0);
token.cancel();
assert_eq!(counter.0.load(Ordering::SeqCst), 1, "cancel must wake it");
assert_eq!(poll_once(future.as_mut(), &waker), Poll::Ready(None));
}
#[test]
fn the_cancelled_future_resolves_on_cancel() {
let token = CancellationToken::new();
let counter = Arc::new(CountingWaker::default());
let waker = Waker::from(counter.clone());
let mut future = pin!(token.cancelled());
assert_eq!(poll_once(future.as_mut(), &waker), Poll::Pending);
token.cancel();
assert_eq!(counter.0.load(Ordering::SeqCst), 1);
assert_eq!(poll_once(future.as_mut(), &waker), Poll::Ready(()));
}
#[test]
fn waker_slots_are_reused_rather_than_accumulated() {
let token = CancellationToken::new();
for _ in 0..100 {
let mut future = pin!(token.run_until_cancelled(std::future::pending::<u8>()));
assert_eq!(poll_once(future.as_mut(), Waker::noop()), Poll::Pending);
}
let parked = token.0.wakers.lock().unwrap().len();
assert_eq!(parked, 1, "each dropped future must free its slot");
}
}