use alloc::alloc::Layout as StdLayout;
use core::future::Future;
use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::pin::Pin;
use core::ptr::NonNull;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use core::sync::atomic::Ordering;
use crate::header::{DropWakerAction, Header, HeaderWithMetadata};
use crate::runnable::{Schedule, ScheduleInfo};
use crate::state::*;
use crate::utils::{abort, abort_on_panic, max, Layout};
use crate::Runnable;
#[cfg(feature = "std")]
pub(crate) type Panic = alloc::boxed::Box<dyn core::any::Any + Send + 'static>;
#[cfg(not(feature = "std"))]
pub(crate) type Panic = core::convert::Infallible;
pub(crate) struct TaskVTable {
pub(crate) raw_waker_vtable: &'static RawWakerVTable,
pub(crate) schedule: unsafe fn(*const (), ScheduleInfo),
pub(crate) enqueue: unsafe fn(*const (), ScheduleInfo),
pub(crate) begin_schedule: unsafe fn(*const ()),
pub(crate) before_enqueue: unsafe fn(*const ()),
pub(crate) end_schedule: unsafe fn(*const ()),
pub(crate) drop_future: unsafe fn(*const (), &TaskLayout),
pub(crate) destroy: unsafe fn(*const ()),
pub(crate) run: unsafe fn(*const ()) -> bool,
pub(crate) layout_info: &'static TaskLayout,
}
impl TaskVTable {
pub(crate) unsafe fn get_output(&self, ptr: *const ()) -> *const () {
ptr.add_byte(self.layout_info.offset_r)
}
}
#[derive(Clone, Copy)]
pub(crate) struct TaskLayout {
pub(crate) layout: StdLayout,
pub(crate) offset_s: usize,
pub(crate) offset_f: usize,
pub(crate) offset_r: usize,
}
pub(crate) struct RawTask<F, T, S, M> {
pub(crate) header: *const HeaderWithMetadata<M>,
pub(crate) schedule: *const S,
pub(crate) future: *mut F,
pub(crate) output: *mut Result<T, Panic>,
}
impl<F, T, S, M> Copy for RawTask<F, T, S, M> {}
impl<F, T, S, M> Clone for RawTask<F, T, S, M> {
fn clone(&self) -> Self {
*self
}
}
impl<F, T, S, M> RawTask<F, T, S, M> {
pub(crate) const TASK_LAYOUT: TaskLayout = Self::eval_task_layout();
#[inline]
const fn eval_task_layout() -> TaskLayout {
let layout_header = Layout::new::<HeaderWithMetadata<M>>();
let layout_s = Layout::new::<S>();
let layout_f = Layout::new::<F>();
let layout_r = Layout::new::<Result<T, Panic>>();
let size_union = max(layout_f.size(), layout_r.size());
let align_union = max(layout_f.align(), layout_r.align());
let layout_union = Layout::from_size_align(size_union, align_union);
let layout = layout_header;
let (layout, offset_s) = leap_unwrap!(layout.extend(layout_s));
let (layout, offset_union) = leap_unwrap!(layout.extend(layout_union));
let offset_f = offset_union;
let offset_r = offset_union;
TaskLayout {
layout: unsafe { layout.into_std() },
offset_s,
offset_f,
offset_r,
}
}
}
macro_rules! allocate_task {
($f:tt, $s:tt, $m:tt, $builder:ident, $schedule:ident, $raw:ident => $future:block) => {{
let allocation =
alloc::alloc::alloc(RawTask::<$f, <$f as Future>::Output, $s, $m>::TASK_LAYOUT.layout);
let ptr = NonNull::new(allocation as *mut ()).unwrap_or_else(|| crate::utils::abort());
let $raw = RawTask::<$f, <$f as Future>::Output, $s, $m>::from_ptr(ptr.as_ptr());
let crate::Builder {
metadata,
#[cfg(feature = "std")]
propagate_panic,
} = $builder;
($raw.header as *mut HeaderWithMetadata<$m>).write(HeaderWithMetadata {
header: Header {
#[cfg(not(feature = "portable-atomic"))]
state: core::sync::atomic::AtomicUsize::new(SCHEDULED | TASK | REFERENCE),
#[cfg(feature = "portable-atomic")]
state: portable_atomic::AtomicUsize::new(SCHEDULED | TASK | REFERENCE),
awaiter: core::cell::UnsafeCell::new(None),
vtable: &RawTask::<$f, <$f as Future>::Output, $s, $m>::TASK_VTABLE,
#[cfg(feature = "std")]
propagate_panic,
},
metadata,
});
($raw.schedule as *mut S).write($schedule);
let bomb = crate::utils::Bomb;
$raw.future.write($future);
mem::forget(bomb);
ptr
}};
}
pub(crate) use allocate_task;
impl<F, T, S, M> RawTask<F, T, S, M>
where
F: Future<Output = T>,
S: Schedule<M>,
{
pub(crate) const RAW_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
Header::clone_waker,
wake::<S, M>,
wake_by_ref::<S, M>,
drop_waker,
);
pub(crate) const TASK_VTABLE: TaskVTable = TaskVTable {
raw_waker_vtable: &Self::RAW_WAKER_VTABLE,
schedule: schedule::<S, M>,
enqueue: enqueue::<S, M>,
begin_schedule: begin_schedule_hook::<S, M>,
before_enqueue: before_enqueue_hook::<S, M>,
end_schedule: end_schedule_hook::<S, M>,
drop_future: drop_future::<F>,
destroy: destroy::<S, M>,
run: Self::run,
layout_info: &Self::TASK_LAYOUT,
};
#[inline]
pub(crate) fn from_ptr(ptr: *const ()) -> Self {
unsafe {
Self {
header: ptr as *const HeaderWithMetadata<M>,
schedule: ptr.add_byte(Self::TASK_LAYOUT.offset_s) as *const S,
future: ptr.add_byte(Self::TASK_LAYOUT.offset_f) as *mut F,
output: ptr.add_byte(Self::TASK_LAYOUT.offset_r) as *mut Result<T, Panic>,
}
}
}
unsafe fn run(ptr: *const ()) -> bool {
let raw = Self::from_ptr(ptr);
let header = ptr as *const Header;
let waker = ManuallyDrop::new(Waker::from_raw(RawWaker::new(
ptr,
(*header).vtable.raw_waker_vtable,
)));
let cx = &mut Context::from_waker(&waker);
let mut state = (*header).state.load(Ordering::Acquire);
loop {
if state & CLOSED != 0 {
drop_future::<F>(ptr, &Self::TASK_LAYOUT);
let state = (*header).state.fetch_and(!SCHEDULED, Ordering::AcqRel);
let mut awaiter = None;
if state & AWAITER != 0 {
awaiter = (*header).take(None);
}
drop_ref(ptr);
if let Some(w) = awaiter {
abort_on_panic(|| w.wake());
}
return false;
}
match (*header).state.compare_exchange_weak(
state,
(state & !SCHEDULED) | RUNNING,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(s) => state = s,
}
}
let guard = Guard::<F, T>(ptr, &Self::TASK_LAYOUT, PhantomData);
#[cfg(not(feature = "std"))]
let poll = <F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx).map(Ok);
#[cfg(feature = "std")]
let poll = {
if (*header).propagate_panic {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
<F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx)
})) {
Ok(Poll::Ready(v)) => Poll::Ready(Ok(v)),
Ok(Poll::Pending) => Poll::Pending,
Err(e) => Poll::Ready(Err(e)),
}
} else {
<F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx).map(Ok)
}
};
mem::forget(guard);
state = (*header).state.load(Ordering::Acquire);
match poll {
Poll::Ready(out) => {
drop_future::<F>(ptr, &Self::TASK_LAYOUT);
raw.output.write(out);
loop {
let new = if state & TASK == 0 {
(state & !RUNNING & !SCHEDULED) | COMPLETED | CLOSED
} else {
(state & !RUNNING & !SCHEDULED) | COMPLETED
};
match (*header).state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if state & TASK == 0 || state & CLOSED != 0 {
abort_on_panic(|| raw.output.drop_in_place());
}
let mut awaiter = None;
if state & AWAITER != 0 {
awaiter = (*header).take(None);
}
drop_ref(ptr);
if let Some(w) = awaiter {
abort_on_panic(|| w.wake());
}
break;
}
Err(s) => state = s,
}
}
}
Poll::Pending => {
let mut future_dropped = false;
loop {
let new = if state & CLOSED != 0 {
state & !RUNNING & !SCHEDULED
} else {
state & !RUNNING
};
if state & CLOSED != 0 && !future_dropped {
drop_future::<F>(ptr, &Self::TASK_LAYOUT);
future_dropped = true;
}
match (*header).state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(state) => {
if state & CLOSED != 0 {
let mut awaiter = None;
if state & AWAITER != 0 {
awaiter = (*header).take(None);
}
drop_ref(ptr);
if let Some(w) = awaiter {
abort_on_panic(|| w.wake());
}
} else if state & SCHEDULED != 0 {
schedule::<S, M>(ptr, ScheduleInfo::new(true));
return true;
} else {
drop_ref(ptr);
}
break;
}
Err(s) => state = s,
}
}
}
}
false
}
}
struct Guard<F, T>(*const (), &'static TaskLayout, PhantomData<fn() -> F>)
where
F: Future<Output = T>;
impl<F, T> Drop for Guard<F, T>
where
F: Future<Output = T>,
{
fn drop(&mut self) {
let ptr = self.0;
let task_layout = self.1;
let header = ptr as *const Header;
unsafe {
let header = &*header;
let mut state = header.state.load(Ordering::Acquire);
loop {
if state & CLOSED != 0 {
drop_future::<F>(ptr, task_layout);
header
.state
.fetch_and(!RUNNING & !SCHEDULED, Ordering::AcqRel);
let mut awaiter = None;
if state & AWAITER != 0 {
awaiter = header.take(None);
}
drop_ref(ptr);
if let Some(w) = awaiter {
abort_on_panic(|| w.wake());
}
break;
}
match header.state.compare_exchange_weak(
state,
(state & !RUNNING & !SCHEDULED) | CLOSED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(state) => {
drop_future::<F>(ptr, task_layout);
let mut awaiter = None;
if state & AWAITER != 0 {
awaiter = header.take(None);
}
drop_ref(ptr);
if let Some(w) = awaiter {
abort_on_panic(|| w.wake());
}
break;
}
Err(s) => state = s,
}
}
}
}
}
struct EndSchedule<F: Fn()>(F);
impl<F: Fn()> Drop for EndSchedule<F> {
fn drop(&mut self) {
(self.0)();
}
}
pub(crate) struct OpenSchedule {
ptr: *const (),
waker: ManuallyDrop<Waker>,
}
impl OpenSchedule {
pub(crate) unsafe fn after_begin(ptr: *const ()) -> Self {
Self {
ptr,
waker: ManuallyDrop::new(Waker::from_raw(Header::clone_waker(ptr))),
}
}
}
impl Drop for OpenSchedule {
fn drop(&mut self) {
unsafe {
let header = self.ptr as *const Header;
((*header).vtable.end_schedule)(self.ptr);
ManuallyDrop::drop(&mut self.waker);
}
}
}
unsafe fn schedule_ref<S>(ptr: *const ()) -> *const S {
let header = ptr as *const Header;
let task_layout = (*header).vtable.layout_info;
ptr.add_byte(task_layout.offset_s) as *const S
}
unsafe fn begin_schedule_hook<S: Schedule<M>, M>(ptr: *const ()) {
(*schedule_ref::<S>(ptr)).begin_schedule();
}
unsafe fn before_enqueue_hook<S: Schedule<M>, M>(ptr: *const ()) {
(*schedule_ref::<S>(ptr)).before_enqueue();
}
unsafe fn end_schedule_hook<S: Schedule<M>, M>(ptr: *const ()) {
(*schedule_ref::<S>(ptr)).end_schedule();
}
unsafe fn enqueue<S: Schedule<M>, M>(ptr: *const (), info: ScheduleInfo) {
let hooks = &*schedule_ref::<S>(ptr);
let runnable = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
hooks.schedule(runnable, info);
}
unsafe fn schedule<S: Schedule<M>, M>(ptr: *const (), info: ScheduleInfo) {
let _waker = Waker::from_raw(Header::clone_waker(ptr));
let hooks = &*schedule_ref::<S>(ptr);
hooks.begin_schedule();
let _end = EndSchedule(|| hooks.end_schedule());
hooks.before_enqueue();
enqueue::<S, M>(ptr, info);
}
#[inline]
unsafe fn drop_waker(ptr: *const ()) {
let header = ptr as *const Header;
match Header::drop_waker(ptr) {
DropWakerAction::Schedule => {
let _held = OpenSchedule::after_begin(ptr);
((*header).vtable.before_enqueue)(ptr);
((*header).vtable.enqueue)(ptr, ScheduleInfo::new(false));
}
DropWakerAction::Destroy => ((*header).vtable.destroy)(ptr),
DropWakerAction::None => {}
}
}
#[inline]
unsafe fn drop_future<F>(ptr: *const (), task_layout: &TaskLayout) {
let future_ptr = ptr.add_byte(task_layout.offset_f) as *mut F;
abort_on_panic(|| {
future_ptr.drop_in_place();
})
}
unsafe fn wake<S: Schedule<M>, M>(ptr: *const ()) {
if mem::size_of::<S>() > 0 {
wake_by_ref::<S, M>(ptr);
drop_waker(ptr);
return;
}
let hooks = &*schedule_ref::<S>(ptr);
hooks.begin_schedule();
let _end = EndSchedule(|| hooks.end_schedule());
let header = ptr as *const Header;
let mut state = (*header).state.load(Ordering::Acquire);
loop {
if state & (COMPLETED | CLOSED) != 0 {
drop_waker(ptr);
break;
}
if state & SCHEDULED != 0 {
match (*header).state.compare_exchange_weak(
state,
state,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
drop_waker(ptr);
break;
}
Err(s) => state = s,
}
} else {
match (*header).state.compare_exchange_weak(
state,
state | SCHEDULED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if state & RUNNING == 0 {
schedule::<S, M>(ptr, ScheduleInfo::new(false));
} else {
drop_waker(ptr);
}
break;
}
Err(s) => state = s,
}
}
}
}
unsafe fn wake_by_ref<S: Schedule<M>, M>(ptr: *const ()) {
let hooks = &*schedule_ref::<S>(ptr);
hooks.begin_schedule();
let _end = EndSchedule(|| hooks.end_schedule());
let header = ptr as *const Header;
let header = &*header;
let mut state = header.state.load(Ordering::Acquire);
loop {
if state & (COMPLETED | CLOSED) != 0 {
break;
}
if state & SCHEDULED != 0 {
match header.state.compare_exchange_weak(
state,
state,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(s) => state = s,
}
} else {
let new = if state & RUNNING == 0 {
(state | SCHEDULED) + REFERENCE
} else {
state | SCHEDULED
};
match header.state.compare_exchange_weak(
state,
new,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if state & RUNNING == 0 {
if state > isize::MAX as usize {
abort();
}
hooks.before_enqueue();
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
hooks.schedule(task, ScheduleInfo::new(false));
}
break;
}
Err(s) => state = s,
}
}
}
}
#[inline]
unsafe fn destroy<S, M>(ptr: *const ()) {
let header = ptr as *const Header;
let task_layout = (*header).vtable.layout_info;
let schedule = ptr.add_byte(task_layout.offset_s);
abort_on_panic(|| {
(ptr as *mut HeaderWithMetadata<M>).drop_in_place();
(schedule as *mut S).drop_in_place();
});
alloc::alloc::dealloc(ptr as *mut u8, task_layout.layout);
}
#[inline]
pub(crate) unsafe fn drop_ref(ptr: *const ()) {
let header = ptr as *const Header;
let header = &*header;
let new = header.state.fetch_sub(REFERENCE, Ordering::AcqRel) - REFERENCE;
if new & !(REFERENCE - 1) == 0 && new & TASK == 0 {
(header.vtable.destroy)(ptr);
}
}
trait PointerPolyfill {
unsafe fn add_byte(self, size: usize) -> Self;
}
impl<T> PointerPolyfill for *const T {
#[inline]
unsafe fn add_byte(self, size: usize) -> Self {
(self.cast::<u8>().add(size)).cast::<T>()
}
}