use core::fmt;
use core::future::Future;
use core::marker::PhantomData;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::pin::Pin;
use core::ptr::NonNull;
use core::task::{Context, Poll};
use std::panic::{RefUnwindSafe, UnwindSafe};
use super::header::Header;
use super::raw::Panic;
use super::state::{AWAITER, CLOSED, COMPLETED, FLAGS, REFERENCE, RUNNING, SCHEDULED, TASK};
use crate::sync::internal::Ordering;
#[must_use = "dropping a task cancels it; call `detach` to let it run"]
pub struct Task<T> {
ptr: NonNull<()>,
_output: PhantomData<T>,
}
unsafe impl<T: Send> Send for Task<T> {}
unsafe impl<T> Sync for Task<T> {}
impl<T> UnwindSafe for Task<T> {}
impl<T> RefUnwindSafe for Task<T> {}
impl<T> Unpin for Task<T> {}
impl<T> Task<T> {
pub(super) unsafe fn from_raw(ptr: NonNull<()>) -> Self {
Self {
ptr,
_output: PhantomData,
}
}
fn header(&self) -> &Header {
unsafe { self.ptr.cast::<Header>().as_ref() }
}
#[inline(always)]
pub fn detach(self) {
let mut this = ManuallyDrop::new(self);
if this
.header()
.state
.compare_exchange_weak(
SCHEDULED | TASK | REFERENCE,
SCHEDULED | REFERENCE,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
return;
}
this.detach_late();
}
#[inline(never)]
fn detach_late(&mut self) {
drop(self.set_detached());
}
pub async fn cancel(self) -> Option<T> {
let mut this = self;
this.set_canceled();
core::future::poll_fn(|cx| this.poll_task(cx)).await
}
pub fn fallible(self) -> FallibleTask<T> {
FallibleTask { task: self }
}
pub fn is_finished(&self) -> bool {
self.header().state.load(Ordering::Acquire) & (COMPLETED | CLOSED) != 0
}
fn set_canceled(&mut self) {
unsafe { cancel(self.ptr.as_ptr().cast_const()) }
}
pub fn id(&self) -> super::Id {
self.header().id
}
pub fn abort(&self) {
unsafe { cancel(self.ptr.as_ptr().cast_const()) }
}
pub fn abort_handle(&self) -> AbortHandle {
let ptr = self.ptr.as_ptr().cast_const();
let header = self.header();
let waker = core::mem::ManuallyDrop::new(unsafe {
core::task::Waker::from_raw(core::task::RawWaker::new(ptr, &header.vtable.waker))
});
AbortHandle {
waker: (*waker).clone(),
id: header.id,
}
}
pub fn join(self) -> Join<T> {
Join { task: self }
}
}
unsafe fn cancel(ptr: *const ()) {
let header = unsafe { &*ptr.cast::<Header>() };
{
let mut state = header.state.load(Ordering::Acquire);
loop {
if state & (COMPLETED | CLOSED) != 0 {
return;
}
let idle = state & (SCHEDULED | RUNNING) == 0;
let new = if idle {
(state | SCHEDULED | CLOSED) + REFERENCE
} else {
state | CLOSED
};
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if idle {
unsafe { (header.vtable.schedule)(ptr, super::ScheduleInfo::new(false)) };
}
if state & AWAITER != 0 {
header.notify(None);
}
return;
}
Err(actual) => state = actual,
}
}
}
}
impl<T> Task<T> {
fn set_detached(&mut self) -> Option<Result<T, Panic>> {
let ptr = self.ptr.as_ptr().cast_const();
let header = self.header();
let mut output = None;
let Err(mut state) = header.state.compare_exchange_weak(
SCHEDULED | TASK | REFERENCE,
SCHEDULED | REFERENCE,
Ordering::AcqRel,
Ordering::Acquire,
) else {
return None;
};
loop {
if state & COMPLETED != 0 && state & CLOSED == 0 {
match header.state.compare_exchange_weak(
state,
state | CLOSED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
output = Some(self.read_output());
state |= CLOSED;
}
Err(actual) => state = actual,
}
continue;
}
let last = state & !FLAGS == 0;
let new = if last && state & CLOSED == 0 {
SCHEDULED | CLOSED | REFERENCE
} else {
state & !TASK
};
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if last {
unsafe {
if state & CLOSED == 0 {
(header.vtable.schedule)(ptr, super::ScheduleInfo::new(false));
} else {
(header.vtable.destroy)(ptr);
}
}
}
return output;
}
Err(actual) => state = actual,
}
}
}
fn read_output(&self) -> Result<T, Panic> {
let ptr = self.ptr.as_ptr().cast_const();
let mut out = MaybeUninit::<Result<T, Panic>>::uninit();
unsafe {
(self.header().vtable.read_output)(ptr, out.as_mut_ptr().cast());
out.assume_init()
}
}
#[inline(always)]
fn poll_task(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
match self.poll_raw(cx) {
Poll::Ready(Some(Ok(output))) => Poll::Ready(Some(output)),
Poll::Ready(Some(Err(panic))) => std::panic::resume_unwind(panic),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
#[inline(always)]
fn poll_raw(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<T, Panic>>> {
let header = self.header();
let mut state = header.state.load(Ordering::Acquire);
loop {
if state & CLOSED != 0 {
if state & (SCHEDULED | RUNNING) != 0 {
header.register(cx.waker());
state = header.state.load(Ordering::Acquire);
if state & (SCHEDULED | RUNNING) != 0 {
return Poll::Pending;
}
}
header.notify(Some(cx.waker()));
return Poll::Ready(None);
}
if state & COMPLETED == 0 {
header.register(cx.waker());
state = header.state.load(Ordering::Acquire);
if state & CLOSED != 0 {
continue;
}
if state & COMPLETED == 0 {
return Poll::Pending;
}
}
match header.state.compare_exchange(
state,
state | CLOSED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if state & AWAITER != 0 {
header.notify(Some(cx.waker()));
}
return Poll::Ready(Some(self.read_output()));
}
Err(actual) => state = actual,
}
}
}
}
impl<T> Drop for Task<T> {
#[inline]
fn drop(&mut self) {
if self.header().state.load(Ordering::Acquire) == COMPLETED | CLOSED | TASK {
unsafe { (self.header().vtable.destroy)(self.ptr.as_ptr().cast_const()) };
return;
}
self.cancel_and_let_go();
}
}
impl<T> Task<T> {
#[inline(never)]
fn cancel_and_let_go(&mut self) {
self.set_canceled();
drop(self.set_detached());
}
}
impl<T> Future for Task<T> {
type Output = T;
#[expect(
clippy::panic,
reason = "documented under `# Panics` of `Task`; `Task::fallible` answers `None` instead"
)]
#[inline(always)]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
match self.poll_task(cx) {
Poll::Ready(Some(output)) => Poll::Ready(output),
Poll::Ready(None) => panic!(
"the task has no output: it was cancelled or its runtime shut down before it \
completed, or it was awaited again after completing"
),
Poll::Pending => Poll::Pending,
}
}
}
impl<T> fmt::Debug for Task<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.header().state.load(Ordering::Acquire);
f.debug_struct("Task")
.field("scheduled", &(state & SCHEDULED != 0))
.field("running", &(state & RUNNING != 0))
.field("completed", &(state & COMPLETED != 0))
.field("closed", &(state & CLOSED != 0))
.finish()
}
}
#[must_use = "dropping a task cancels it; call `detach` to let it run"]
pub struct FallibleTask<T> {
task: Task<T>,
}
impl<T> FallibleTask<T> {
pub fn detach(self) {
self.task.detach()
}
pub async fn cancel(self) -> Option<T> {
self.task.cancel().await
}
pub fn is_finished(&self) -> bool {
self.task.is_finished()
}
pub fn id(&self) -> super::Id {
self.task.id()
}
}
impl<T> Future for FallibleTask<T> {
type Output = Option<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
self.task.poll_task(cx)
}
}
impl<T> fmt::Debug for FallibleTask<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("FallibleTask").field(&self.task).finish()
}
}
#[must_use = "dropping it cancels the task, as dropping the `Task` does"]
pub struct Join<T> {
task: Task<T>,
}
impl<T> Join<T> {
pub fn detach(self) {
self.task.detach()
}
pub fn id(&self) -> super::Id {
self.task.id()
}
}
impl<T> Future for Join<T> {
type Output = Result<T, JoinError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let id = self.task.id();
self.task.poll_raw(cx).map(|output| match output {
Some(Ok(output)) => Ok(output),
Some(Err(panic)) => Err(JoinError {
id,
panic: Some(panic),
}),
None => Err(JoinError { id, panic: None }),
})
}
}
impl<T> fmt::Debug for Join<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Join").field(&self.task).finish()
}
}
pub struct JoinError {
id: super::Id,
panic: Option<Panic>,
}
impl JoinError {
pub fn is_cancelled(&self) -> bool {
self.panic.is_none()
}
pub fn is_panic(&self) -> bool {
self.panic.is_some()
}
#[expect(clippy::panic, reason = "documented under `# Panics`")]
pub fn into_panic(self) -> Box<dyn core::any::Any + Send + 'static> {
match self.try_into_panic() {
Ok(panic) => panic,
Err(error) => panic!("`JoinError::into_panic` on a cancelled task ({error})"),
}
}
pub fn try_into_panic(self) -> Result<Box<dyn core::any::Any + Send + 'static>, JoinError> {
match self.panic {
Some(panic) => Ok(panic),
None => Err(self),
}
}
pub fn id(&self) -> super::Id {
self.id
}
}
impl fmt::Display for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.panic {
None => write!(f, "task {} was cancelled", self.id),
Some(panic) => match panic
.downcast_ref::<&str>()
.copied()
.or_else(|| panic.downcast_ref::<String>().map(String::as_str))
{
Some(message) => write!(f, "task {} panicked: {message}", self.id),
None => write!(f, "task {} panicked", self.id),
},
}
}
}
impl fmt::Debug for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoinError")
.field("id", &self.id)
.field("cancelled", &self.is_cancelled())
.finish()
}
}
impl std::error::Error for JoinError {}
impl From<JoinError> for std::io::Error {
fn from(error: JoinError) -> Self {
std::io::Error::other(error.to_string())
}
}
#[derive(Clone)]
pub struct AbortHandle {
waker: core::task::Waker,
id: super::Id,
}
impl AbortHandle {
pub fn abort(&self) {
unsafe { cancel(self.waker.data()) }
}
pub fn is_finished(&self) -> bool {
let header = unsafe { &*self.waker.data().cast::<Header>() };
header.state.load(Ordering::Acquire) & (COMPLETED | CLOSED) != 0
}
pub fn id(&self) -> super::Id {
self.id
}
}
impl fmt::Debug for AbortHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AbortHandle").field("id", &self.id).finish()
}
}