use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
type Callback = Box<dyn FnOnce() + Send>;
struct Inner {
canceled: AtomicBool,
callbacks: Mutex<Callbacks>,
}
#[derive(Default)]
struct Callbacks {
next_id: u64,
entries: Vec<(u64, Callback)>,
}
#[derive(Clone)]
pub struct CancellationTokenSource {
inner: Arc<Inner>,
}
impl Default for CancellationTokenSource {
fn default() -> Self {
Self::new()
}
}
impl CancellationTokenSource {
pub fn new() -> Self {
Self {
inner: Arc::new(Inner { canceled: AtomicBool::new(false), callbacks: Mutex::new(Callbacks::default()) }),
}
}
pub fn token(&self) -> CancellationToken {
CancellationToken { inner: Some(self.inner.clone()) }
}
pub fn is_cancellation_requested(&self) -> bool {
self.inner.canceled.load(Ordering::SeqCst)
}
pub fn cancel(&self) {
if self.inner.canceled.swap(true, Ordering::SeqCst) {
return;
}
let entries = {
let mut callbacks = self.inner.callbacks.lock().unwrap_or_else(PoisonError::into_inner);
std::mem::take(&mut callbacks.entries)
};
for (_, callback) in entries {
callback();
}
}
}
#[derive(Clone, Default)]
pub struct CancellationToken {
inner: Option<Arc<Inner>>,
}
impl CancellationToken {
pub const fn none() -> Self {
Self { inner: None }
}
pub fn can_be_canceled(&self) -> bool {
self.inner.is_some()
}
pub fn is_cancellation_requested(&self) -> bool {
self.inner.as_ref().is_some_and(|inner| inner.canceled.load(Ordering::SeqCst))
}
pub fn register(&self, callback: impl FnOnce() + Send + 'static) -> CancellationTokenRegistration {
let Some(inner) = &self.inner else {
return CancellationTokenRegistration { inner: None, id: 0 };
};
{
let mut callbacks = inner.callbacks.lock().unwrap_or_else(PoisonError::into_inner);
if !inner.canceled.load(Ordering::SeqCst) {
callbacks.next_id += 1;
let id = callbacks.next_id;
callbacks.entries.push((id, Box::new(callback)));
return CancellationTokenRegistration { inner: Some(inner.clone()), id };
}
}
callback();
CancellationTokenRegistration { inner: None, id: 0 }
}
}
impl CancellationToken {
pub fn register_local(&self, callback: impl FnOnce() + 'static) -> LocalCancellationTokenRegistration {
let local = super::tasks::local_executor::LocalCallback::register(Box::new(callback));
let handle = local.handle();
let registration = self.register(move || handle.run());
LocalCancellationTokenRegistration { registration, local }
}
}
pub struct LocalCancellationTokenRegistration {
registration: CancellationTokenRegistration,
local: super::tasks::local_executor::LocalCallback,
}
impl LocalCancellationTokenRegistration {
pub fn dispose(&self) {
self.registration.dispose();
self.local.remove();
}
}
pub struct CancellationTokenRegistration {
inner: Option<Arc<Inner>>,
id: u64,
}
impl CancellationTokenRegistration {
pub fn dispose(&self) {
if let Some(inner) = &self.inner {
let removed = {
let mut callbacks = inner.callbacks.lock().unwrap_or_else(PoisonError::into_inner);
callbacks.entries.iter().position(|(id, _)| *id == self.id).map(|i| callbacks.entries.remove(i))
};
drop(removed);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
#[test]
fn none_cannot_be_canceled() {
let token = CancellationToken::none();
assert!(!token.can_be_canceled());
assert!(!token.is_cancellation_requested());
}
#[test]
fn cancel_runs_callbacks_once() {
let count = Arc::new(AtomicUsize::new(0));
let source = CancellationTokenSource::new();
let token = source.token();
let c = count.clone();
token.register(move || {
c.fetch_add(1, Ordering::SeqCst);
});
let c = count.clone();
let removed = token.register(move || {
c.fetch_add(10, Ordering::SeqCst);
});
removed.dispose();
source.cancel();
source.cancel();
assert!(token.is_cancellation_requested());
assert_eq!(count.load(Ordering::SeqCst), 1);
let c = count.clone();
token.register(move || {
c.fetch_add(100, Ordering::SeqCst);
});
assert_eq!(count.load(Ordering::SeqCst), 101);
}
}