use std::{
fmt,
future::Future,
io,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use crate::driver::{Handle, helpers::scopes::ScopeId};
use crate::runtime::local::CURRENT_DRIVER;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct OperationCanceled;
impl fmt::Display for OperationCanceled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("operation canceled")
}
}
impl std::error::Error for OperationCanceled {}
pub fn operation_canceled() -> io::Error {
io::Error::other(OperationCanceled)
}
pub fn is_operation_canceled(err: &io::Error) -> bool {
err.get_ref().is_some_and(|inner| inner.is::<OperationCanceled>()) || is_raw_canceled(err)
}
pub(crate) fn is_raw_canceled(err: &io::Error) -> bool {
#[cfg(unix)]
{
err.raw_os_error() == Some(libc::ECANCELED)
}
#[cfg(windows)]
{
err.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_OPERATION_ABORTED as i32)
}
}
pub(crate) fn map_cancel_result<T>(result: io::Result<T>) -> io::Result<T> {
match result {
Ok(value) => Ok(value),
Err(error) if is_raw_canceled(&error) => Err(operation_canceled()),
Err(error) => Err(error),
}
}
pub struct CancellationSource {
id: ScopeId,
driver: Handle,
_local: PhantomData<*const ()>,
}
#[derive(Clone, Copy)]
pub struct CancellationToken {
id: ScopeId,
_local: PhantomData<*const ()>,
}
impl CancellationSource {
pub fn new() -> Self {
CURRENT_DRIVER.with(|driver| {
let driver = driver.clone();
Self {
id: driver.insert_scope(),
driver,
_local: PhantomData,
}
})
}
pub fn token(&self) -> CancellationToken {
CancellationToken {
id: self.id,
_local: PhantomData,
}
}
pub fn cancel(&self) {
self.driver.cancel_scope(self.id);
}
pub fn is_cancel_requested(&self) -> bool {
self.driver.scope_is_cancelled(self.id)
}
}
impl Default for CancellationSource {
fn default() -> Self {
Self::new()
}
}
impl Drop for CancellationSource {
fn drop(&mut self) {
self.driver.remove_scope(self.id);
}
}
impl CancellationToken {
pub fn is_cancel_requested(&self) -> bool {
assert!(CURRENT_DRIVER.is_set(), "Not in runtime context");
CURRENT_DRIVER.with(|driver| driver.scope_is_cancelled(self.id))
}
pub fn cancelled(&self) -> WaitFuture {
WaitFuture {
id: self.id,
driver: None,
registration: None,
_local: PhantomData,
}
}
}
#[must_use = "futures do nothing unless polled or awaited"]
pub struct WaitFuture {
id: ScopeId,
driver: Option<Handle>,
registration: Option<crate::driver::helpers::scopes::WaiterId>,
_local: PhantomData<*const ()>,
}
impl Future for WaitFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.driver.is_none() {
assert!(CURRENT_DRIVER.is_set(), "Not in runtime context");
self.driver = Some(CURRENT_DRIVER.with(Clone::clone));
}
let driver = self.driver.as_ref().expect("cancellation waiter driver missing");
match driver.subscribe_scope(self.id, self.registration, cx.waker().clone()) {
crate::driver::helpers::scopes::SubscribeResult::Ready => {
self.registration = None;
Poll::Ready(())
}
crate::driver::helpers::scopes::SubscribeResult::Pending(registration) => {
self.registration = Some(registration);
Poll::Pending
}
}
}
}
impl Drop for WaitFuture {
fn drop(&mut self) {
if let (Some(driver), Some(registration)) = (&self.driver, self.registration) {
driver.unsubscribe_scope(self.id, registration);
}
}
}
pub trait FutureExt: Future {
fn with_cancellation(self, token: CancellationToken) -> WithCancellation<Self>
where
Self: Sized,
{
WithCancellation::new(self, token)
}
}
impl<F: Future> FutureExt for F {}
pub struct WithCancellation<F> {
pub(crate) future: F,
pub(crate) token: CancellationToken,
}
impl<F> WithCancellation<F> {
pub(crate) fn new(future: F, token: CancellationToken) -> Self {
Self { future, token }
}
}
impl<F: Future> Future for WithCancellation<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let token = self.token;
let future = unsafe { self.map_unchecked_mut(|this| &mut this.future) };
crate::driver::helpers::scopes::with_scope(token.id, || future.poll(cx))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
mem::ManuallyDrop,
rc::Rc,
task::{RawWaker, RawWakerVTable, Waker},
};
struct LocalWake(Box<dyn Fn()>);
fn local_waker(callback: impl Fn() + 'static) -> Waker {
unsafe fn clone(data: *const ()) -> RawWaker {
let state = ManuallyDrop::new(unsafe { Rc::<LocalWake>::from_raw(data.cast()) });
let cloned = Rc::clone(&state);
RawWaker::new(Rc::into_raw(cloned).cast(), &VTABLE)
}
unsafe fn wake(data: *const ()) {
let state = unsafe { Rc::<LocalWake>::from_raw(data.cast()) };
(state.0)();
}
unsafe fn wake_by_ref(data: *const ()) {
let state = ManuallyDrop::new(unsafe { Rc::<LocalWake>::from_raw(data.cast()) });
(state.0)();
}
unsafe fn drop_waker(data: *const ()) {
drop(unsafe { Rc::<LocalWake>::from_raw(data.cast()) });
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
let raw = RawWaker::new(Rc::into_raw(Rc::new(LocalWake(Box::new(callback)))).cast(), &VTABLE);
unsafe { Waker::from_raw(raw) }
}
#[test]
fn operation_canceled_classifies() {
let err = operation_canceled();
assert!(is_operation_canceled(&err));
assert!(!is_operation_canceled(&io::Error::other("other")));
assert_ne!(err.kind(), io::ErrorKind::Interrupted);
}
#[test]
fn source_token_cancel_is_sticky() {
crate::Runtime::new().unwrap().block_on(async {
let source = CancellationSource::new();
let token = source.token();
let cloned = token;
assert!(!token.is_cancel_requested());
source.cancel();
source.cancel();
assert!(source.is_cancel_requested());
assert!(token.is_cancel_requested());
assert!(cloned.is_cancel_requested());
});
}
#[test]
fn dropping_source_cancels_tokens() {
crate::Runtime::new().unwrap().block_on(async {
let token = {
let source = CancellationSource::new();
source.token()
};
assert!(token.is_cancel_requested());
});
}
#[test]
fn dropping_source_between_runtime_entries_cancels_tokens() {
let mut runtime = crate::Runtime::new().unwrap();
let (source, token) = runtime.block_on(async {
let source = CancellationSource::new();
let token = source.token();
(source, token)
});
drop(source);
runtime.block_on(async {
assert!(token.is_cancel_requested());
token.cancelled().await;
});
}
#[test]
fn token_observation_requires_runtime_context() {
let mut runtime = crate::Runtime::new().unwrap();
let (source, token) = runtime.block_on(async {
let source = CancellationSource::new();
let token = source.token();
(source, token)
});
assert!(!source.is_cancel_requested());
assert!(std::panic::catch_unwind(|| token.is_cancel_requested()).is_err());
}
#[test]
fn token_from_another_runtime_is_cancelled() {
let mut first = crate::Runtime::new().unwrap();
let mut second = crate::Runtime::new().unwrap();
let (source, token) = first.block_on(async {
let source = CancellationSource::new();
let token = source.token();
(source, token)
});
second.block_on(async {
assert!(token.is_cancel_requested());
token.cancelled().await;
});
first.block_on(async {
assert!(!source.is_cancel_requested());
});
}
#[test]
fn cancelled_wait_completes_after_cancel() {
crate::Runtime::new().unwrap().block_on(async {
let source = CancellationSource::new();
let token = source.token();
crate::runtime::spawn_local(async move {
source.cancel();
});
token.cancelled().await;
});
}
#[test]
fn dropping_cancelled_wait_releases_its_waker() {
crate::Runtime::new().unwrap().block_on(async {
let source = CancellationSource::new();
let token = source.token();
let mut waiter = Box::pin(token.cancelled());
std::future::poll_fn(|cx| {
assert!(waiter.as_mut().poll(cx).is_pending());
Poll::Ready(())
})
.await;
assert_eq!(source.driver.scope_waiter_count(source.id), 1);
drop(waiter);
assert_eq!(source.driver.scope_waiter_count(source.id), 0);
});
}
#[test]
fn cancellation_wakes_after_releasing_scope_table_borrow() {
crate::Runtime::new().unwrap().block_on(async {
let source = CancellationSource::new();
let token = source.token();
let driver = source.driver.clone();
let id = source.id;
let waker = local_waker(move || assert!(driver.scope_is_cancelled(id)));
let mut cx = Context::from_waker(&waker);
let mut waiter = Box::pin(token.cancelled());
assert!(waiter.as_mut().poll(&mut cx).is_pending());
source.cancel();
assert!(waiter.as_mut().poll(&mut cx).is_ready());
});
}
#[test]
fn with_cancellation_installs_a_scope_frame() {
crate::Runtime::new().unwrap().block_on(async {
let source = CancellationSource::new();
let token = source.token();
std::future::poll_fn(|_cx| {
assert_eq!(crate::driver::helpers::scopes::current_scope_ids().len(), 1);
Poll::Ready(())
})
.with_cancellation(token)
.await;
});
}
#[test]
fn nested_with_cancellation_frames_all_apply() {
crate::Runtime::new().unwrap().block_on(async {
let outer = CancellationSource::new();
let inner = CancellationSource::new();
std::future::poll_fn(|_cx| {
assert_eq!(crate::driver::helpers::scopes::current_scope_ids().len(), 2);
Poll::Ready(())
})
.with_cancellation(outer.token())
.with_cancellation(inner.token())
.await;
});
}
}