use core::fmt;
use core::future::Future;
use core::marker::PhantomData;
use core::mem;
use core::pin::Pin;
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use core::task::{Context, Poll};
use crate::header::{Header, HeaderWithMetadata};
use crate::raw::{OpenSchedule, Panic};
use crate::state::*;
use crate::ScheduleInfo;
#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
pub struct Task<T, M = ()> {
pub(crate) ptr: NonNull<()>,
pub(crate) _marker: PhantomData<(T, M)>,
}
unsafe impl<T: Send, M: Send + Sync> Send for Task<T, M> {}
unsafe impl<T, M: Send + Sync> Sync for Task<T, M> {}
impl<T, M> Unpin for Task<T, M> {}
#[cfg(feature = "std")]
impl<T, M> std::panic::UnwindSafe for Task<T, M> {}
#[cfg(feature = "std")]
impl<T, M> std::panic::RefUnwindSafe for Task<T, M> {}
impl<T, M> Task<T, M> {
pub fn detach(self) {
let this = self;
let _out = set_detached::<T>(this.ptr.as_ptr());
mem::forget(this);
}
pub async fn cancel(self) -> Option<T> {
let this = self;
set_canceled(this.ptr.as_ptr());
this.fallible().await
}
pub fn fallible(self) -> FallibleTask<T, M> {
FallibleTask { task: self }
}
fn header_with_metadata(&self) -> &HeaderWithMetadata<M> {
let ptr = self.ptr.as_ptr();
let header = ptr as *const HeaderWithMetadata<M>;
unsafe { &*header }
}
pub fn is_finished(&self) -> bool {
let ptr = self.ptr.as_ptr();
let header = ptr as *const Header;
unsafe {
let state = (*header).state.load(Ordering::Acquire);
state & (CLOSED | COMPLETED) != 0
}
}
pub fn metadata(&self) -> &M {
let ptr = self.ptr.as_ptr();
let header = ptr as *const HeaderWithMetadata<M>;
&unsafe { &*header }.metadata
}
}
#[inline(never)]
fn set_detached<T>(ptr: *const ()) -> Option<Result<T, Panic>> {
let header = ptr as *const Header;
unsafe {
let mut output = None;
if let Err(mut state) = (*header).state.compare_exchange_weak(
SCHEDULED | TASK | REFERENCE,
SCHEDULED | REFERENCE,
Ordering::AcqRel,
Ordering::Acquire,
) {
loop {
if state & COMPLETED != 0 && state & CLOSED == 0 {
match (*header).state.compare_exchange_weak(
state,
state | CLOSED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
output = Some(
((*header).vtable.get_output(ptr) as *mut Result<T, Panic>).read(),
);
state |= CLOSED;
}
Err(s) => state = s,
}
} else {
let new = if state & (!(REFERENCE - 1) | CLOSED) == 0 {
SCHEDULED | CLOSED | REFERENCE
} else {
state & !TASK
};
let schedules = state & (!(REFERENCE - 1) | CLOSED) == 0;
if schedules {
((*header).vtable.begin_schedule)(ptr);
}
match (*header).state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if schedules {
let _held = OpenSchedule::after_begin(ptr);
((*header).vtable.before_enqueue)(ptr);
((*header).vtable.enqueue)(ptr, ScheduleInfo::new(false));
} else if state & !(REFERENCE - 1) == 0 {
((*header).vtable.destroy)(ptr);
}
break;
}
Err(s) => {
if schedules {
((*header).vtable.end_schedule)(ptr);
}
state = s;
}
}
}
}
}
output
}
}
#[inline(never)]
fn set_canceled(ptr: *const ()) {
let header = ptr as *const Header;
unsafe {
let mut state = (*header).state.load(Ordering::Acquire);
loop {
if state & (COMPLETED | CLOSED) != 0 {
break;
}
let schedules = state & (SCHEDULED | RUNNING) == 0;
let new = if schedules {
(state | SCHEDULED | CLOSED) + REFERENCE
} else {
state | CLOSED
};
if schedules {
((*header).vtable.begin_schedule)(ptr);
}
match (*header).state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if schedules {
let _held = OpenSchedule::after_begin(ptr);
((*header).vtable.before_enqueue)(ptr);
((*header).vtable.enqueue)(ptr, ScheduleInfo::new(false));
}
if state & AWAITER != 0 {
(*header).notify(None);
}
break;
}
Err(s) => {
if schedules {
((*header).vtable.end_schedule)(ptr);
}
state = s;
}
}
}
}
}
fn poll_task<T>(ptr: *const (), cx: &mut Context<'_>) -> Poll<Option<T>> {
let header = ptr as *const Header;
unsafe {
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()));
}
let output = (*header).vtable.get_output(ptr) as *mut Result<T, Panic>;
let output = output.read();
let output = match output {
Ok(output) => output,
Err(panic) => {
#[cfg(feature = "std")]
std::panic::resume_unwind(panic);
#[cfg(not(feature = "std"))]
match panic {}
}
};
return Poll::Ready(Some(output));
}
Err(s) => state = s,
}
}
}
}
impl<T, M> Drop for Task<T, M> {
fn drop(&mut self) {
let ptr = self.ptr.as_ptr();
set_canceled(ptr);
set_detached::<T>(ptr);
}
}
impl<T, M> Future for Task<T, M> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match poll_task::<T>(self.ptr.as_ptr(), cx) {
Poll::Ready(t) => Poll::Ready(t.expect("Task polled after completion")),
Poll::Pending => Poll::Pending,
}
}
}
impl<T, M: fmt::Debug> fmt::Debug for Task<T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Task")
.field("header", self.header_with_metadata())
.finish()
}
}
#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
pub struct FallibleTask<T, M = ()> {
task: Task<T, M>,
}
impl<T, M> FallibleTask<T, M> {
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()
}
}
impl<T, M> Future for FallibleTask<T, M> {
type Output = Option<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
poll_task::<T>(self.task.ptr.as_ptr(), cx)
}
}
impl<T, M: fmt::Debug> fmt::Debug for FallibleTask<T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FallibleTask")
.field("header", self.task.header_with_metadata())
.finish()
}
}