use std::{
future::Future,
mem, panic,
ptr::NonNull,
task::{Context, Poll, Waker},
};
use crate::{
runtime::Schedule,
task::{
Task,
join::JoinError,
state::{Snapshot, TransitionToIdle, TransitionToRunning},
trailer::Trailer,
utils::UnsafeCellExt,
waker::waker_ref,
},
};
use super::{header::Header, internal::InternalTask, raw::RawTask};
pub(super) struct VTable {
pub(super) poll: fn(NonNull<Header>),
pub(super) try_read_output: fn(NonNull<Header>, *mut (), &Waker),
pub(super) drop_join_handle: fn(NonNull<Header>),
pub(super) dealloc: fn(NonNull<Header>),
pub(super) schedule: fn(NonNull<Header>),
}
impl VTable {
#[must_use]
pub(super) fn new<F: Future, S: Schedule>() -> &'static Self {
&VTable {
poll: poll::<F, S>,
try_read_output: try_read_output::<F, S>,
drop_join_handle: drop_join_handle::<F, S>,
dealloc: dealloc::<F, S>,
schedule: schedule::<F, S>,
}
}
}
fn poll<F: Future, S: Schedule>(ptr: NonNull<Header>) {
let internal_task_ptr: NonNull<InternalTask<F, S>> = ptr.cast();
let internal_task = unsafe { internal_task_ptr.as_ref() };
match internal_task.header.state.transition_to_running() {
TransitionToRunning::Success => {
let waker = waker_ref::<S>(&ptr);
let cx = Context::from_waker(&waker);
let res = poll_future(internal_task, cx);
if res == Poll::Ready(()) {
complete(internal_task_ptr);
RawTask::from_raw(ptr).drop_reference();
return;
}
match internal_task.header.state.transition_to_idle() {
TransitionToIdle::Ok => {}
TransitionToIdle::OkNotified => {
let task = unsafe { Task::from_raw(internal_task_ptr.cast()) };
internal_task.scheduler.yield_now(task);
RawTask::from_raw(ptr).drop_reference();
}
TransitionToIdle::OkDealloc => dealloc::<F, S>(ptr),
TransitionToIdle::Cancelled => {
cancel_task(internal_task_ptr);
RawTask::from_raw(ptr).drop_reference();
}
}
}
TransitionToRunning::Cancelled => {
cancel_task(internal_task_ptr);
RawTask::from_raw(ptr).drop_reference();
}
TransitionToRunning::Failed => {}
TransitionToRunning::Dealloc => dealloc::<F, S>(ptr),
}
}
fn try_read_output<F: Future, S: Schedule>(raw_task_ptr: NonNull<Header>, dst: *mut (), waker: &Waker) {
let out = unsafe { &mut *(dst as *mut Poll<crate::task::join::Result<F::Output>>) };
let internal_task_ptr: NonNull<InternalTask<F, S>> = raw_task_ptr.cast();
let internal_task = unsafe { internal_task_ptr.as_ref() };
if can_read_output(&internal_task.header, &internal_task.trailer, waker) {
*out = Poll::Ready(internal_task.take_output());
}
}
fn drop_join_handle<F: Future, S: Schedule>(ptr: NonNull<Header>) {
let internal_task_ptr: NonNull<InternalTask<F, S>> = ptr.cast();
let internal_task = unsafe { internal_task_ptr.as_ref() };
let next_state = internal_task.header.state.transition_to_join_handle_dropped();
if next_state.drop_output {
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
internal_task.drop_future_or_output();
}));
}
if next_state.drop_waker {
internal_task.trailer.set_waker(None);
}
RawTask::from_raw(ptr).drop_reference();
}
fn dealloc<F: Future, S: Schedule>(ptr: NonNull<Header>) {
let internal_task_ptr: NonNull<InternalTask<F, S>> = ptr.cast();
let internal_task = unsafe { internal_task_ptr.as_ref() };
internal_task.trailer.waker.with_mut(|_| ());
internal_task.future.with_mut(|_| ());
unsafe {
drop(Box::from_raw(internal_task_ptr.as_ptr()));
}
}
fn schedule<F: Future, S: Schedule>(raw_task_ptr: NonNull<Header>) {
unsafe {
raw_task_ptr
.cast::<InternalTask<F, S>>()
.as_ref()
.scheduler
.schedule(Task::from_raw(raw_task_ptr.cast()))
};
}
fn poll_future<F: Future, S: Schedule>(internal_task: &InternalTask<F, S>, cx: Context<'_>) -> Poll<()> {
let output = panic::catch_unwind(panic::AssertUnwindSafe(|| {
struct Guard<'a, F: Future, S: Schedule> {
core: &'a InternalTask<F, S>,
}
impl<'a, F: Future, S: Schedule> Drop for Guard<'a, F, S> {
fn drop(&mut self) {
self.core.drop_future_or_output();
}
}
let guard = Guard { core: internal_task };
let res = guard.core.poll(cx);
mem::forget(guard);
res
}));
let output = match output {
Ok(Poll::Pending) => return Poll::Pending,
Ok(Poll::Ready(output)) => Ok(output),
Err(payload) => {
internal_task.scheduler.unhandled_panic();
Err(JoinError::panic(payload))
}
};
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
internal_task.store_output(output);
}));
if res.is_err() {
internal_task.scheduler.unhandled_panic();
}
Poll::Ready(())
}
fn cancel_task<F: Future, S: Schedule>(internal_task_ptr: NonNull<InternalTask<F, S>>) {
let internal_task = unsafe { internal_task_ptr.as_ref() };
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
internal_task.drop_future_or_output();
internal_task.store_output(Err(JoinError::cancelled()));
}));
complete(internal_task_ptr);
}
fn complete<F: Future, S: Schedule>(internal_task_ptr: NonNull<InternalTask<F, S>>) {
let internal_task = unsafe { internal_task_ptr.as_ref() };
let snapshot = internal_task.header.state.transition_to_complete();
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
if !snapshot.is_join_interested() {
internal_task.drop_future_or_output();
} else if snapshot.is_join_waker_set() {
internal_task.trailer.wake_join();
if !internal_task
.header
.state
.unset_waker_after_complete()
.is_join_interested()
{
internal_task.trailer.set_waker(None);
}
}
}));
}
fn can_read_output(header: &Header, trailer: &Trailer, waker: &Waker) -> bool {
let state = header.state.get_snapshot();
debug_assert!(state.is_join_interested());
if !state.is_complete() {
let res = if state.is_join_waker_set() {
if trailer.will_wake(waker) {
return false;
}
header.state.unset_waker().and_then(|prev| {
debug_assert!(prev.is_join_waker_set());
let snapshot = header.state.get_snapshot();
set_join_waker(header, trailer, waker.clone(), snapshot)
})
} else {
set_join_waker(header, trailer, waker.clone(), state)
};
match res {
Ok(_) => return false,
Err(snapshot) => {
assert!(snapshot.is_complete());
}
}
}
true
}
fn set_join_waker(header: &Header, trailer: &Trailer, waker: Waker, state: Snapshot) -> Result<Snapshot, Snapshot> {
assert!(state.is_join_interested());
assert!(!state.is_join_waker_set());
trailer.set_waker(Some(waker));
let res = header.state.set_join_waker();
if res.is_err() {
trailer.set_waker(None);
}
res
}