use core::future::Future;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::pin::Pin;
use core::ptr::NonNull;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::panic::{AssertUnwindSafe, catch_unwind};
use super::header::Header;
use super::header::wake_caught;
use super::state::{
AWAITER, CLOSED, COMPLETED, FLAGS, NOTIFYING, REFERENCE, REGISTERING, RUNNING, SCHEDULED, TASK,
};
use super::{Id, Runnable, Schedule, ScheduleInfo};
pub(crate) use crate::rt::caught::Panic;
use crate::rt::caught::{self, Thin};
use crate::sync::internal::{Ordering, UnsafeCell};
pub(super) struct TaskVTable {
pub(super) schedule: unsafe fn(*const (), ScheduleInfo),
pub(super) drop_runnable: unsafe fn(*const ()),
pub(super) read_output: unsafe fn(*const (), *mut ()),
pub(super) destroy: unsafe fn(*const ()),
pub(super) run: unsafe fn(*const ()) -> bool,
pub(super) waker: RawWakerVTable,
}
#[repr(C)]
struct Cell<F: Future, S> {
header: Header,
schedule: S,
core: UnsafeCell<Core<F>>,
}
union Core<F: Future> {
future: ManuallyDrop<F>,
output: ManuallyDrop<Result<F::Output, Panic>>,
}
pub(super) struct RawTask<F, S>(PhantomData<(F, S)>);
impl<F, S> RawTask<F, S>
where
F: Future,
S: Schedule,
{
const VTABLE: TaskVTable = TaskVTable {
schedule: Self::schedule,
drop_runnable: Self::drop_runnable,
read_output: Self::read_output,
destroy: Self::destroy,
run: Self::run,
waker: RawWakerVTable::new(
Self::clone_waker,
Self::wake,
Self::wake_by_ref,
Self::drop_waker,
),
};
#[inline(always)]
pub(super) fn allocate(future: F, schedule: S) -> NonNull<()> {
let (ptr, owner) = crate::mem::alloc(core::alloc::Layout::new::<Cell<F, S>>());
let ptr = ptr.cast::<Cell<F, S>>();
unsafe {
ptr.as_ptr().write(Cell {
header: Header::new(&Self::VTABLE, owner),
schedule,
core: UnsafeCell::new(Core {
future: ManuallyDrop::new(future),
}),
});
}
ptr.cast()
}
unsafe fn cell<'a>(ptr: *const ()) -> &'a Cell<F, S> {
unsafe { &*ptr.cast::<Cell<F, S>>() }
}
unsafe fn clone_waker(ptr: *const ()) -> RawWaker {
let header = unsafe { &Self::cell(ptr).header };
let state = header.state.fetch_add(REFERENCE, Ordering::Relaxed);
if state > isize::MAX as usize {
std::process::abort();
}
RawWaker::new(ptr, &header.vtable.waker)
}
unsafe fn wake(ptr: *const ()) {
unsafe {
let header = &Self::cell(ptr).header;
let mut state = header.state.load(Ordering::Acquire);
loop {
if state & (COMPLETED | CLOSED) != 0 {
Self::drop_waker(ptr);
return;
}
let new = state | SCHEDULED;
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) if state & (SCHEDULED | RUNNING) == 0 => {
Self::schedule(ptr, ScheduleInfo::new(false));
return;
}
Ok(_) => {
Self::drop_waker(ptr);
return;
}
Err(actual) => state = actual,
}
}
}
}
unsafe fn wake_by_ref(ptr: *const ()) {
unsafe {
let header = &Self::cell(ptr).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) + REFERENCE
} else {
state | SCHEDULED
};
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if idle {
if state > isize::MAX as usize {
std::process::abort();
}
Self::schedule(ptr, ScheduleInfo::new(false));
}
return;
}
Err(actual) => state = actual,
}
}
}
}
unsafe fn drop_waker(ptr: *const ()) {
unsafe {
let header = &Self::cell(ptr).header;
let new = header.state.fetch_sub(REFERENCE, Ordering::AcqRel) - REFERENCE;
if new & !FLAGS == 0 && new & TASK == 0 {
if new & (COMPLETED | CLOSED) == 0 {
header
.state
.store(SCHEDULED | CLOSED | REFERENCE, Ordering::Release);
Self::schedule(ptr, ScheduleInfo::new(false));
} else {
Self::destroy(ptr);
}
}
}
}
unsafe fn drop_ref(ptr: *const ()) {
unsafe {
let header = &Self::cell(ptr).header;
let new = header.state.fetch_sub(REFERENCE, Ordering::AcqRel) - REFERENCE;
if new & !FLAGS == 0 && new & TASK == 0 {
if new & (COMPLETED | CLOSED) == 0 {
Self::drop_unawaited(ptr);
}
Self::destroy(ptr);
}
}
}
unsafe fn schedule(ptr: *const (), info: ScheduleInfo) {
unsafe {
let runnable = Runnable::from_raw(NonNull::new_unchecked(ptr.cast_mut()));
Self::cell(ptr).schedule.schedule(runnable, info);
}
}
unsafe fn drop_future(ptr: *const ()) -> Option<Panic> {
unsafe { Self::drop_future_here(ptr) }
}
unsafe fn drop_unawaited(ptr: *const ()) {
unsafe {
if let Some(panic) = Self::drop_future(ptr) {
lost_ending(Self::cell(ptr).header.id, panic);
}
}
}
#[inline(always)]
unsafe fn drop_future_here(ptr: *const ()) -> Option<Panic> {
unsafe {
let cell = Self::cell(ptr);
let mut dropped = None;
cell.schedule.drop_future(
&cell.header.links,
#[inline(always)]
|| {
dropped = caught::catch(|| {
cell.core
.with_mut(|core| ManuallyDrop::drop(&mut (*core).future))
});
},
);
if dropped.is_none() && !cell.schedule.has_hooks() {
return None;
}
ended_with(&cell.schedule, cell.header.id, dropped)
}
}
#[inline(always)]
unsafe fn settle(ptr: *const (), clear: usize, panic: Option<Panic>) -> usize {
unsafe {
match panic {
None => Self::cell(ptr)
.header
.state
.fetch_and(!clear, Ordering::AcqRel),
Some(panic) => Self::settle_panicked(ptr, clear, panic),
}
}
}
#[cold]
#[inline(never)]
unsafe fn settle_panicked(ptr: *const (), clear: usize, panic: Panic) -> usize {
unsafe {
let cell = Self::cell(ptr);
let header = &cell.header;
cell.core
.with_mut(|core| (*core).output = ManuallyDrop::new(Err(panic)));
let mut state = header.state.load(Ordering::Acquire);
loop {
let new = if state & TASK != 0 {
(state & !clear & !CLOSED) | COMPLETED
} else {
state & !clear
};
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(actual) => state = actual,
}
}
if state & TASK == 0
&& let Err(panic) = cell
.core
.with_mut(|core| ManuallyDrop::take(&mut (*core).output))
{
lost_ending(header.id, panic);
}
state
}
}
unsafe fn drop_runnable(ptr: *const ()) {
unsafe {
let header = &Self::cell(ptr).header;
let mut state = header.state.load(Ordering::Acquire);
loop {
if state & (COMPLETED | CLOSED) != 0 {
break;
}
match header.state.compare_exchange_weak(
state,
state | CLOSED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(actual) => state = actual,
}
}
let panic = Self::drop_future(ptr);
let state = Self::settle(ptr, SCHEDULED, panic);
if state & AWAITER != 0 {
header.notify(None);
}
Self::drop_ref(ptr);
}
}
unsafe fn read_output(ptr: *const (), out: *mut ()) {
unsafe {
let cell = Self::cell(ptr);
cell.core.with_mut(|core| {
out.cast::<Result<F::Output, Panic>>()
.write(ManuallyDrop::take(&mut (*core).output));
});
}
}
unsafe fn destroy(ptr: *const ()) {
unsafe {
let header = &Self::cell(ptr).header;
super::names::forget(header.id);
let owner = header.owner;
let cell = ptr.cast_mut().cast::<Cell<F, S>>();
if let Some(panic) = caught::catch(|| core::ptr::drop_in_place(cell)) {
lost_freeing(panic);
}
crate::mem::free(
NonNull::new_unchecked(cell.cast()),
core::alloc::Layout::new::<Cell<F, S>>(),
owner,
);
}
}
#[inline(always)]
unsafe fn finish(ptr: *const (), state: usize) {
unsafe {
let header = &Self::cell(ptr).header;
let awaiter = if state & AWAITER != 0 {
header.take(None)
} else {
None
};
Self::drop_ref(ptr);
if let Some(waker) = awaiter {
wake_caught(waker);
}
}
}
unsafe fn run(ptr: *const ()) -> bool {
unsafe {
let cell = Self::cell(ptr);
let header = &cell.header;
let waker =
ManuallyDrop::new(Waker::from_raw(RawWaker::new(ptr, &header.vtable.waker)));
let cx = &mut Context::from_waker(&waker);
let state = header
.state
.fetch_add(RUNNING - SCHEDULED, Ordering::AcqRel);
debug_assert_eq!(state & (SCHEDULED | RUNNING), SCHEDULED);
if state & CLOSED != 0 {
return Self::end_queued(ptr);
}
let entered = crate::coop::enter_task(header.id);
let poll = catch_unwind(AssertUnwindSafe(|| {
cell.schedule.before_poll(cell.header.id);
cell.core
.with_mut(|core| F::poll(Pin::new_unchecked(&mut *(*core).future), cx))
}))
.map_err(Box::new);
if cell.schedule.has_hooks() {
return Self::run_hooked(ptr, &entered, poll);
}
Self::settle_run(ptr, &entered, poll)
}
}
#[cold]
#[inline(never)]
unsafe fn run_hooked(
ptr: *const (),
entered: &crate::coop::Entered,
poll: Result<Poll<F::Output>, Thin>,
) -> bool {
unsafe {
let poll =
after_poll_hooked(&Self::cell(ptr).schedule, Self::cell(ptr).header.id, poll);
Self::settle_run(ptr, entered, poll)
}
}
#[inline(always)]
unsafe fn settle_run(
ptr: *const (),
entered: &crate::coop::Entered,
poll: Result<Poll<F::Output>, Thin>,
) -> bool {
unsafe {
let cell = Self::cell(ptr);
let header = &cell.header;
crate::coop::leave_task(*entered);
let mut state = header.state.load(Ordering::Acquire);
let output = match poll {
Ok(Poll::Pending) => None,
Ok(Poll::Ready(value)) => Some(Ok(value)),
Err(payload) => Some(Err(*payload)),
};
match output {
Some(mut output) => {
if let Some(panic) = Self::drop_future_here(ptr) {
output = outcome_with(header.id, output, panic);
}
let panicked = output.is_err();
cell.core
.with_mut(|core| (*core).output = ManuallyDrop::new(output));
if panicked {
cell.schedule.panicked(header.id);
return Self::complete_panicked(ptr, state);
}
Self::complete::<false>(ptr, state)
}
None => {
cell.schedule.pending(&header.links);
loop {
if state & CLOSED != 0 {
return Self::end_closed(ptr);
}
let new = if state & SCHEDULED != 0 {
state & !RUNNING
} else {
(state & !RUNNING) - REFERENCE
};
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) if state & SCHEDULED != 0 => {
Self::schedule(ptr, ScheduleInfo::new(true));
return true;
}
Ok(_) => {
if new & !FLAGS == 0 && new & TASK == 0 {
Self::drop_unawaited(ptr);
Self::destroy(ptr);
}
return false;
}
Err(actual) => state = actual,
}
}
}
}
}
}
#[inline(always)]
unsafe fn complete<const PANICKED: bool>(ptr: *const (), mut state: usize) -> bool {
unsafe {
let cell = Self::cell(ptr);
let header = &cell.header;
let mut awaiter = None;
let mut claimed = false;
let mut unclaimed = None;
let last = loop {
if !claimed && state & AWAITER != 0 && state & (NOTIFYING | REGISTERING) == 0 {
let (waker, ours, now) = header.claim();
if ours {
awaiter = waker;
claimed = true;
}
state = now;
}
if unclaimed.is_none() {
if state & TASK == 0 {
unclaimed = Some(Some(
cell.core
.with_mut(|core| ManuallyDrop::take(&mut (*core).output)),
));
} else if !PANICKED && state & CLOSED != 0 {
Self::drop_cancelled_output(ptr);
unclaimed = Some(None);
}
}
let mut new = (state & !RUNNING & !SCHEDULED) | COMPLETED;
if state & TASK == 0 {
new |= CLOSED;
} else if PANICKED {
new &= !CLOSED;
}
if claimed {
new &= !(NOTIFYING | AWAITER);
}
new -= REFERENCE;
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break new & !FLAGS == 0 && new & TASK == 0,
Err(actual) => state = actual,
}
};
if last {
Self::destroy(ptr);
}
if let Some(Some(output)) = unclaimed {
drop_unclaimed(output);
}
if let Some(waker) = awaiter {
wake_caught(waker);
}
false
}
}
#[cold]
#[inline(never)]
unsafe fn drop_cancelled_output(ptr: *const ()) {
unsafe {
let output = Self::cell(ptr)
.core
.with_mut(|core| ManuallyDrop::take(&mut (*core).output));
drop_unclaimed(output);
}
}
#[cold]
#[inline(never)]
unsafe fn complete_panicked(ptr: *const (), state: usize) -> bool {
unsafe { Self::complete::<true>(ptr, state) }
}
#[cold]
#[inline(never)]
unsafe fn end_queued(ptr: *const ()) -> bool {
unsafe {
let panic = Self::drop_future(ptr);
let state = Self::settle(ptr, RUNNING, panic);
Self::finish(ptr, state);
false
}
}
#[cold]
#[inline(never)]
unsafe fn end_closed(ptr: *const ()) -> bool {
unsafe {
let panic = Self::drop_future(ptr);
let state = Self::settle(ptr, RUNNING | SCHEDULED, panic);
Self::finish(ptr, state);
false
}
}
}
#[cold]
#[inline(never)]
fn ended_with<S>(schedule: &S, id: Id, dropped: Option<Panic>) -> Option<Panic>
where
S: Schedule,
{
match (dropped, schedule.terminated(id)) {
(None, hook) => hook,
(dropped, None) => dropped,
(Some(dropped), Some(hook)) => {
lost_again(id, hook);
Some(dropped)
}
}
}
#[cold]
#[inline(never)]
fn outcome_with<T>(id: Id, output: Result<T, Panic>, panic: Panic) -> Result<T, Panic> {
match output {
Ok(value) => {
if let Some(again) = caught::catch(|| drop(value)) {
lost_again(id, again);
}
Err(panic)
}
Err(first) => {
lost_again(id, panic);
Err(first)
}
}
}
#[cold]
#[inline(never)]
fn after_poll_hooked<T, S>(
schedule: &S,
id: Id,
poll: Result<Poll<T>, Thin>,
) -> Result<Poll<T>, Thin>
where
S: Schedule,
{
let Some(panic) = schedule.after_poll(id) else {
return poll;
};
match poll {
Ok(Poll::Pending) => Err(Box::new(panic)),
Ok(Poll::Ready(value)) => outcome_with(id, Ok(value), panic)
.map(Poll::Ready)
.map_err(Box::new),
Err(first) => outcome_with::<T>(id, Err(*first), panic)
.map(Poll::Ready)
.map_err(Box::new),
}
}
#[inline(always)]
pub(super) fn drop_unclaimed<T>(output: Result<T, Panic>) {
match output {
Ok(value) => {
if let Some(panic) = caught::catch(|| drop(value)) {
lost_output(panic);
}
}
Err(panic) => lost_unawaited(panic),
}
}
#[cold]
#[inline(never)]
pub(super) fn lost_unawaited(panic: Panic) {
caught::lost(format_args!("a task panicked and nobody awaited it"), panic);
}
#[cold]
#[inline(never)]
fn lost_output(panic: Panic) {
caught::lost(
format_args!("a task's output panicked as it was dropped"),
panic,
);
}
#[cold]
#[inline(never)]
fn lost_ending(id: Id, panic: Panic) {
caught::lost(format_args!("task {id} panicked as it ended"), panic);
}
#[cold]
#[inline(never)]
fn lost_again(id: Id, panic: Panic) {
caught::lost(format_args!("task {id} panicked again as it ended"), panic);
}
#[cold]
#[inline(never)]
fn lost_freeing(panic: Panic) {
caught::lost(
format_args!("a task panicked as its memory was freed"),
panic,
);
}