use futures_util::future::{AbortHandle, Abortable, FutureExt, LocalBoxFuture};
#[cfg(feature = "browser")]
use std::cell::OnceCell;
use std::{
cell::{Cell, RefCell},
collections::BTreeMap,
future::Future,
rc::{Rc, Weak},
};
type Callback = Box<dyn FnOnce()>;
#[derive(Default)]
struct Cancellation {
cancelled: Cell<bool>,
next: Cell<u64>,
callbacks: RefCell<BTreeMap<u64, Callback>>,
#[cfg(feature = "browser")]
controller: OnceCell<web_sys::AbortController>,
}
#[derive(Clone, Default)]
pub struct CancellationToken(Rc<Cancellation>);
pub struct CancellationSource(Option<CancellationToken>);
impl Default for CancellationSource {
fn default() -> Self {
Self(Some(CancellationToken::default()))
}
}
impl CancellationSource {
pub fn token(&self) -> CancellationToken {
self.0.as_ref().expect("live cancellation source").clone()
}
pub fn cancel(&self) {
if let Some(token) = &self.0 {
token.cancel();
}
}
pub fn complete(mut self) {
self.release();
}
fn release(&mut self) {
self.0.take();
}
}
impl Drop for CancellationSource {
fn drop(&mut self) {
self.cancel();
}
}
pub(crate) struct InFlight {
abort: AbortHandle,
source: CancellationSource,
}
impl InFlight {
pub(crate) fn start<F: Future<Output = ()> + 'static>(
work: impl FnOnce(CancellationToken) -> F,
) -> (Self, LocalBoxFuture<'static, ()>) {
let (abort, registration) = AbortHandle::new_pair();
let source = CancellationSource::default();
let work = Abortable::new(work(source.token()), registration)
.map(drop)
.boxed_local();
(Self { abort, source }, work)
}
pub(crate) fn complete(mut self) {
self.source.release();
}
}
impl Drop for InFlight {
fn drop(&mut self) {
self.abort.abort();
}
}
#[must_use = "retain the cancellation registration while the operation is pending"]
pub struct CancelRegistration {
token: Weak<Cancellation>,
id: u64,
}
impl Drop for CancelRegistration {
fn drop(&mut self) {
if let Some(token) = self.token.upgrade() {
let callback = token.callbacks.borrow_mut().remove(&self.id);
drop(callback);
}
}
}
impl CancellationToken {
pub fn is_cancelled(&self) -> bool {
self.0.cancelled.get()
}
pub fn on_cancel(&self, callback: impl FnOnce() + 'static) -> CancelRegistration {
let id = crate::increment(&self.0.next, "cancellation registration");
if self.is_cancelled() {
callback();
} else {
self.0.callbacks.borrow_mut().insert(id, Box::new(callback));
}
CancelRegistration {
token: Rc::downgrade(&self.0),
id,
}
}
#[cfg(feature = "browser")]
pub fn abort_signal(&self) -> Result<web_sys::AbortSignal, wasm_bindgen::JsValue> {
if let Some(controller) = self.0.controller.get() {
return Ok(controller.signal());
}
let controller = web_sys::AbortController::new()?;
if self.is_cancelled() {
controller.abort();
}
let signal = controller.signal();
let _ = self.0.controller.set(controller);
Ok(signal)
}
pub(crate) fn cancel(&self) {
if self.0.cancelled.replace(true) {
return;
}
#[cfg(feature = "browser")]
if let Some(controller) = self.0.controller.get() {
controller.abort();
}
let callbacks = self.0.callbacks.take();
for callback in callbacks.into_values() {
callback();
}
}
}