use std::cell::Cell;
use std::marker::{PhantomData, PhantomPinned};
use std::sync::{Arc, atomic::Ordering};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::{fmt, future::Future, mem, mem::ManuallyDrop, pin::Pin};
use std::{process::abort, ptr, ptr::NonNull};
use crate::handle::SpawnLocation;
use crate::rt::{REPORTED, RunnableQueue, TaskStats, try_current_thread_id};
#[path = "wake.rs"]
pub(super) mod wake;
pub(crate) use wake::{WakeEntry, WakeQueue};
#[cfg(ntex_loom)]
use loom::{cell::UnsafeCell, sync::atomic::AtomicBool};
#[cfg(not(ntex_loom))]
use std::{cell::UnsafeCell, sync::atomic::AtomicBool};
struct SharedCell<T>(UnsafeCell<T>);
impl<T> SharedCell<T> {
fn new(v: T) -> Self {
Self(UnsafeCell::new(v))
}
#[inline]
fn with_mut<R>(&self, f: impl FnOnce(*mut T) -> R) -> R {
#[cfg(not(ntex_loom))]
return f(self.0.get());
#[cfg(ntex_loom)]
return self.0.with_mut(f);
}
}
const SCHEDULED: u8 = 1;
const RUNNING: u8 = 1 << 1;
const FINISHED: u8 = 1 << 2;
const CLOSE: u8 = 1 << 3;
const WAKES_REPORTED: u8 = 1 << 4;
const DETACHED: u8 = 1 << 5;
const LISTED: u8 = 1 << 6;
const CANCEL_ON_DROP: u8 = 1 << 7;
pub(crate) const MAX_SELF_WAKES: u32 = u16::MAX as u32;
struct VTable {
poll: unsafe fn(NonNull<Header>, &mut Context<'_>) -> bool,
drop_future: unsafe fn(NonNull<Header>),
take_output: unsafe fn(NonNull<Header>, *mut ()),
drop_output: unsafe fn(NonNull<Header>),
dealloc: unsafe fn(NonNull<Header>),
}
#[repr(C)]
struct Header {
vtable: &'static VTable,
queue: NonNull<RunnableQueue>,
location: SpawnLocation,
awaiter: Awaiter,
}
#[repr(C)]
struct Core {
header: Header,
next: Cell<*mut Header>,
refs: Cell<u32>,
state: Cell<u8>,
remote_touched: AtomicBool,
routing: wake::Routing,
self_wakes: Cell<u16>,
streak_start: Cell<u32>,
list_prev: Cell<*mut Header>,
list_next: Cell<*mut Header>,
_pin: PhantomPinned,
}
#[cfg(all(not(ntex_loom), target_pointer_width = "64"))]
const _: () = {
assert!(mem::size_of::<Header>() == 40);
assert!(mem::size_of::<wake::Routing>() == 4);
assert!(mem::size_of::<Core>() == 80);
assert!(mem::offset_of!(Core, list_prev) == 64);
};
enum Stage<F: Future> {
Running(F),
Finished(F::Output),
Consumed,
}
#[repr(C)]
struct TaskCore<F: Future> {
core: Core,
stage: SharedCell<Stage<F>>,
#[cfg(test)]
_allocation: Arc<()>,
}
struct Fns<F>(PhantomData<F>);
impl<F: Future> Fns<F> {
const VTABLE: VTable = VTable {
poll: Self::poll,
drop_future: Self::drop_future,
take_output: Self::take_output,
drop_output: Self::drop_output,
dealloc: Self::dealloc,
};
#[inline]
unsafe fn with_stage<R>(h: NonNull<Header>, f: impl FnOnce(*mut Stage<F>) -> R) -> R {
unsafe { (*h.cast::<TaskCore<F>>().as_ptr()).stage.with_mut(f) }
}
unsafe fn poll(h: NonNull<Header>, cx: &mut Context<'_>) -> bool {
unsafe {
let polled = Self::with_stage(h, |stage| {
let Stage::Running(fut) = &mut *stage else {
unreachable!()
};
Pin::new_unchecked(fut).poll(cx)
});
match polled {
Poll::Ready(out) => {
Self::drop_future(h);
Self::with_stage(h, |stage| ptr::write(stage, Stage::Finished(out)));
true
}
Poll::Pending => false,
}
}
}
unsafe fn drop_future(h: NonNull<Header>) {
struct Guard<F: Future>(*mut Stage<F>);
impl<F: Future> Drop for Guard<F> {
fn drop(&mut self) {
unsafe { ptr::write(self.0, Stage::Consumed) };
}
}
unsafe {
Self::with_stage(h, |stage| {
let guard = Guard(stage);
ptr::drop_in_place(guard.0);
});
}
}
unsafe fn take_output(h: NonNull<Header>, out: *mut ()) {
if let Some(v) = unsafe { Self::take_output_value(h) } {
unsafe { *out.cast::<Option<F::Output>>() = Some(v) };
}
}
unsafe fn drop_output(h: NonNull<Header>) {
drop(unsafe { Self::take_output_value(h) });
}
unsafe fn take_output_value(h: NonNull<Header>) -> Option<F::Output> {
unsafe {
Self::with_stage(h, |stage| {
if matches!(*stage, Stage::Finished(_))
&& let Stage::Finished(v) = ptr::replace(stage, Stage::Consumed)
{
Some(v)
} else {
None
}
})
}
}
unsafe fn dealloc(h: NonNull<Header>) {
drop(unsafe { Box::from_raw(h.cast::<TaskCore<F>>().as_ptr()) });
}
}
struct Awaiter(SharedCell<Option<crate::LocalWaker>>);
impl Awaiter {
fn new() -> Self {
Self(SharedCell::new(None))
}
unsafe fn take(&self) -> Option<crate::LocalWaker> {
self.0.with_mut(|p| unsafe { (*p).take() })
}
unsafe fn register(&self, waker: &Waker) -> Option<crate::LocalWaker> {
if self.0.with_mut(|p| unsafe {
(*p).as_ref()
.is_some_and(|current| current.will_wake(waker))
}) {
return None;
}
let new = crate::LocalWaker::from_waker(waker);
self.0.with_mut(|p| unsafe { (*p).replace(new) })
}
}
impl Drop for Awaiter {
fn drop(&mut self) {
drop(unsafe { self.take() });
}
}
static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
wake::clone_waker,
wake::wake::<true>,
wake::wake::<false>,
wake::drop_waker,
);
#[inline]
pub(crate) fn local_waker_ptr(waker: &Waker) -> Option<*const ()> {
if ptr::eq(waker.vtable(), &raw const WAKER_VTABLE)
&& unsafe { wake::active_owner(waker.data()) }
{
Some(waker.data())
} else {
None
}
}
#[inline]
pub(crate) unsafe fn clone_local_waker(ptr: *const ()) {
inc_local(unsafe { task_ptr(ptr) });
}
#[inline]
pub(crate) unsafe fn wake_local_waker(ptr: *const (), owned: bool) {
wake_local::<true>(unsafe { task_ptr(ptr) }, owned);
}
#[inline]
pub(crate) unsafe fn drop_local_waker(ptr: *const ()) {
release_unshared(unsafe { task_ptr(ptr) });
}
#[inline]
pub(crate) fn raw_waker(ptr: *const ()) -> RawWaker {
RawWaker::new(ptr, &WAKER_VTABLE)
}
pub(crate) unsafe fn into_waker(ptr: *const ()) -> Waker {
let raw = ManuallyDrop::new(unsafe { Waker::from_raw(raw_waker(ptr)) });
let waker = (*raw).clone();
unsafe { drop_local_waker(ptr) };
waker
}
#[inline]
fn header<'a>(h: NonNull<Header>) -> &'a Header {
unsafe { h.as_ref() }
}
#[inline]
fn core<'a>(h: NonNull<Header>) -> &'a Core {
unsafe { h.cast::<Core>().as_ref() }
}
#[inline]
fn is_owner(h: NonNull<Header>) -> bool {
Some(unsafe { (*h.cast::<Core>().as_ptr()).routing.owner }) == try_current_thread_id()
}
fn owner_queue<'a>(h: NonNull<Header>) -> &'a RunnableQueue {
unsafe { header(h).queue.as_ref() }
}
fn live_owner_queue<'a>(h: NonNull<Header>) -> Option<&'a RunnableQueue> {
if core(h).state.get() & FINISHED == 0 || unsafe { wake::active_owner(h.as_ptr().cast()) } {
Some(owner_queue(h))
} else {
None
}
}
fn has_pending_commands(h: NonNull<Header>) -> bool {
if let Some(queue) = live_owner_queue(h) {
!queue.wake_queue.is_empty()
} else {
wake::retain_owner_queue(h).is_some_and(|queue| !queue.wake_queue.is_empty())
}
}
#[inline]
unsafe fn task_ptr(ptr: *const ()) -> NonNull<Header> {
unsafe { NonNull::new_unchecked(ptr.cast_mut().cast::<Header>()) }
}
#[inline]
fn inc_local(h: NonNull<Header>) {
let refs = &core(h).refs;
match refs.get().checked_add(1) {
Some(n) => refs.set(n),
None => abort(),
}
}
#[inline]
fn mark_scheduled(h: NonNull<Header>) -> bool {
let local = core(h);
let state = local.state.get();
if state & (SCHEDULED | FINISHED) != 0 {
return false;
}
local.state.set(state | SCHEDULED);
state & RUNNING == 0
}
fn wake_local<const UNSHARED: bool>(h: NonNull<Header>, owned: bool) {
if mark_scheduled(h) {
if !owned {
inc_local(h);
}
owner_queue(h).schedule_local(Runnable(h));
} else if owned {
if UNSHARED {
release_unshared(h);
} else {
release_local(h);
}
}
}
#[inline]
fn release_unshared(h: NonNull<Header>) {
let refs = &core(h).refs;
let n = refs.get();
if n > 1 {
refs.set(n - 1);
} else {
release_local(h);
}
}
#[inline]
fn release_local(h: NonNull<Header>) {
if core(h).remote_touched.load(Ordering::Acquire) {
release_touched(h);
} else {
release_local_ordered(h);
}
}
#[inline(never)]
fn release_touched(h: NonNull<Header>) {
if !has_pending_commands(h) && core(h).refs.get() > 1 {
release_local_ordered(h);
} else {
wake::defer_release(h);
}
}
#[inline]
fn release_local_ordered(h: NonNull<Header>) {
let local = core(h);
let refs = local.refs.get().checked_sub(1).unwrap();
local.refs.set(refs);
if refs == 0 {
last_ref(h);
}
}
#[cold]
fn last_ref(h: NonNull<Header>) {
let local = core(h);
let state = local.state.get();
if state & FINISHED != 0 {
unsafe { (header(h).vtable.dealloc)(h) };
return;
}
local.refs.set(1);
local.state.set(state | SCHEDULED | CLOSE);
owner_queue(h).schedule_local(Runnable(h));
}
fn close(h: NonNull<Header>) {
struct Finish(NonNull<Header>);
impl Drop for Finish {
fn drop(&mut self) {
let completed = complete(self.0);
release_local(self.0);
drop(completed);
}
}
let local = core(h);
local.state.set(local.state.get() | RUNNING);
let finish = Finish(h);
unsafe { (header(finish.0).vtable.drop_future)(finish.0) };
}
struct Completed {
detached: bool,
awaiter: Option<crate::LocalWaker>,
}
impl Drop for Completed {
fn drop(&mut self) {
let Some(w) = self.awaiter.take() else {
return;
};
if !self.detached {
w.wake();
}
}
}
fn complete(h: NonNull<Header>) -> Completed {
let local = core(h);
let state = local.state.get();
local
.state
.set((state | FINISHED) & !(RUNNING | SCHEDULED | CLOSE | LISTED));
if state & LISTED != 0 {
owner_queue(h).tasks.remove(h);
}
Completed {
detached: state & DETACHED != 0,
awaiter: unsafe { header(h).awaiter.take() },
}
}
struct Release(NonNull<Header>);
impl Drop for Release {
fn drop(&mut self) {
release_local(self.0);
}
}
fn drop_output(h: NonNull<Header>) {
let release = Release(h);
unsafe { (header(release.0).vtable.drop_output)(release.0) };
}
pub(crate) unsafe fn spawn<F: Future>(
future: F,
queue: &Arc<RunnableQueue>,
location: SpawnLocation,
) -> Task<F::Output> {
match unsafe { create::<_, true>(future, queue, location, false) } {
Ok(h) => Task { h, _t: PhantomData },
Err(future) => {
let h = alloc::<F>(Stage::Consumed, queue, location, FINISHED, 1);
let task = Task { h, _t: PhantomData };
cancel_closed(future, location);
task
}
}
}
pub(crate) unsafe fn spawn_detached<F: Future, const LOCAL: bool>(
future: F,
queue: &Arc<RunnableQueue>,
location: SpawnLocation,
) {
if let Err(future) = unsafe { create::<_, LOCAL>(future, queue, location, true) } {
cancel_closed(future, location);
}
}
#[cold]
fn cancel_closed<F>(future: F, location: SpawnLocation) {
log::warn!("Task spawned at {location} after its runtime is dropped, the task is cancelled");
drop(future);
}
unsafe fn create<F: Future, const LOCAL: bool>(
future: F,
queue: &Arc<RunnableQueue>,
location: SpawnLocation,
detached: bool,
) -> Result<NonNull<Header>, F> {
let local = LOCAL || Some(queue.id) == try_current_thread_id();
let permit = if local {
if queue.is_closed() {
return Err(future);
}
None
} else {
let Some(permit) = queue.admit(false) else {
return Err(future);
};
Some(permit)
};
let h = alloc(
Stage::Running(future),
queue,
location,
SCHEDULED | if local { LISTED } else { 0 } | if detached { DETACHED } else { 0 },
if detached { 1 } else { 2 },
);
let join = (!detached).then(|| Task::<F::Output> { h, _t: PhantomData });
if local {
queue.tasks.push(h);
queue.schedule_local(Runnable(h));
} else {
permit.unwrap().schedule(Remote(h));
}
mem::forget(join);
Ok(h)
}
fn alloc<F: Future>(
stage: Stage<F>,
queue: &Arc<RunnableQueue>,
location: SpawnLocation,
flags: u8,
refs: u32,
) -> NonNull<Header> {
let raw = Box::new(TaskCore {
core: Core {
header: Header {
vtable: &Fns::<F>::VTABLE,
queue: NonNull::from(queue.as_ref()),
location,
awaiter: Awaiter::new(),
},
next: Cell::new(ptr::null_mut()),
refs: Cell::new(refs),
state: Cell::new(flags),
remote_touched: AtomicBool::new(false),
routing: wake::Routing {
runtime: queue.runtime_id,
owner: queue.id,
},
self_wakes: Cell::new(0),
streak_start: Cell::new(0),
list_prev: Cell::new(ptr::null_mut()),
list_next: Cell::new(ptr::null_mut()),
_pin: PhantomPinned,
},
stage: SharedCell::new(stage),
#[cfg(test)]
_allocation: queue.task_allocations.clone(),
});
NonNull::from(Box::leak(raw)).cast()
}
pub(crate) struct Runnable(NonNull<Header>);
pub(crate) fn task_ref(task: &Runnable) -> TaskRef {
TaskRef(task.0)
}
impl Runnable {
pub(crate) fn run(self) -> bool {
struct Guard(NonNull<Header>);
impl Drop for Guard {
fn drop(&mut self) {
let local = core(self.0);
local
.state
.set(local.state.get() & !(RUNNING | SCHEDULED | CLOSE));
close(self.0);
}
}
debug_assert!(is_owner(self.0), "task polled off its runtime thread");
let h = ManuallyDrop::new(self).0;
let hdr = header(h);
let local = core(h);
let state = local.state.get() & !SCHEDULED;
if state & (CLOSE | FINISHED) != 0 {
discard(h, state);
return false;
}
local.state.set(state | RUNNING);
let waker = ManuallyDrop::new(unsafe { Waker::from_raw(raw_waker(h.as_ptr().cast())) });
let mut cx = Context::from_waker(&waker);
let guard = Guard(h);
let ready = unsafe { (hdr.vtable.poll)(h, &mut cx) };
mem::forget(guard);
if ready {
finish_ready(h);
return false;
}
let state = local.state.get() & !RUNNING;
if state & CLOSE != 0 {
discard(h, state);
return false;
}
local.state.set(state);
if state & SCHEDULED != 0 {
owner_queue(h).schedule_local(Runnable(h));
return true;
}
release_local(h);
false
}
}
impl Drop for Runnable {
fn drop(&mut self) {
if is_owner(self.0) {
discard(self.0, core(self.0).state.get());
} else {
leaked(self.0);
}
}
}
#[inline(never)]
fn finish_ready(h: NonNull<Header>) {
let completed = complete(h);
if completed.detached {
drop_output(h);
} else {
release_local(h);
}
drop(completed);
}
fn discard(h: NonNull<Header>, state: u8) {
core(h).state.set(state & !SCHEDULED);
if state & FINISHED == 0 {
close(h);
} else {
release_local(h);
}
}
#[cold]
fn leaked(h: NonNull<Header>) {
log::error!(
"Task spawned at {} is dropped off its runtime thread, the task is leaked",
header(h).location
);
}
impl fmt::Debug for Runnable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Runnable")
.field("location", &header(self.0).location)
.finish()
}
}
pub(crate) struct Remote(NonNull<Header>);
unsafe impl Send for Remote {}
impl Remote {
pub(crate) fn into_runnable(self) -> Runnable {
debug_assert!(is_owner(self.0), "task merged off its runtime thread");
let h = ManuallyDrop::new(self).0;
let local = core(h);
debug_assert_eq!(local.state.get(), SCHEDULED | DETACHED);
local.state.set(local.state.get() | LISTED);
owner_queue(h).tasks.push(h);
Runnable(h)
}
}
impl Drop for Remote {
fn drop(&mut self) {
if is_owner(self.0) {
drop(Remote(self.0).into_runnable());
} else {
leaked(self.0);
}
}
}
impl fmt::Debug for Remote {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Remote").finish()
}
}
#[derive(Clone, Copy)]
pub(crate) struct TaskRef(NonNull<Header>);
impl TaskStats for TaskRef {
fn location(self) -> SpawnLocation {
header(self.0).location
}
fn self_wakes(self) -> u32 {
let local = core(self.0);
let reported = if local.state.get() & WAKES_REPORTED != 0 {
REPORTED
} else {
0
};
u32::from(local.self_wakes.get()) | reported
}
fn set_self_wakes(self, wakes: u32) {
let local = core(self.0);
let count = (wakes & !REPORTED).min(MAX_SELF_WAKES);
let reported = if wakes & REPORTED != 0 { WAKES_REPORTED } else { 0 };
local
.state
.set((local.state.get() & !WAKES_REPORTED) | reported);
local.self_wakes.set(count as u16);
}
fn streak_start(self) -> u32 {
core(self.0).streak_start.get()
}
fn set_streak_start(self, start: u32) {
core(self.0).streak_start.set(start);
}
}
pub(crate) struct LocalQueue {
head: Cell<*mut Header>,
tail: Cell<*mut Header>,
}
impl LocalQueue {
pub(crate) const fn new() -> Self {
Self {
head: Cell::new(ptr::null_mut()),
tail: Cell::new(ptr::null_mut()),
}
}
pub(crate) fn push(&self, task: Runnable) {
let h = ManuallyDrop::new(task).0;
core(h).next.set(ptr::null_mut());
if let Some(tail) = NonNull::new(self.tail.replace(h.as_ptr())) {
core(tail).next.set(h.as_ptr());
} else {
self.head.set(h.as_ptr());
}
}
pub(crate) fn pop(&self) -> Option<Runnable> {
let h = NonNull::new(self.head.get())?;
let next = core(h).next.get();
self.head.set(next);
if next.is_null() {
self.tail.set(ptr::null_mut());
}
Some(Runnable(h))
}
pub(crate) fn is_empty(&self) -> bool {
self.head.get().is_null()
}
}
impl fmt::Debug for LocalQueue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalQueue").finish_non_exhaustive()
}
}
pub(crate) struct TaskList {
head: Cell<*mut Header>,
}
impl TaskList {
pub(crate) const fn new() -> Self {
Self {
head: Cell::new(ptr::null_mut()),
}
}
fn push(&self, h: NonNull<Header>) {
let local = core(h);
let head = self.head.replace(h.as_ptr());
local.list_prev.set(ptr::null_mut());
local.list_next.set(head);
if let Some(head) = NonNull::new(head) {
core(head).list_prev.set(h.as_ptr());
}
}
fn remove(&self, h: NonNull<Header>) {
let local = core(h);
let (prev, next) = (local.list_prev.get(), local.list_next.get());
if let Some(prev) = NonNull::new(prev) {
core(prev).list_next.set(next);
} else {
self.head.set(next);
}
if let Some(next) = NonNull::new(next) {
core(next).list_prev.set(prev);
}
}
pub(crate) fn close_first(&self) -> bool {
let Some(h) = NonNull::new(self.head.get()) else {
return false;
};
let local = core(h);
let state = local.state.get();
if state & RUNNING != 0 {
local.state.set((state | CLOSE) & !LISTED);
self.remove(h);
} else {
inc_local(h);
close(h);
}
true
}
}
impl fmt::Debug for TaskList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaskList").finish_non_exhaustive()
}
}
pub(crate) struct Task<T> {
h: NonNull<Header>,
_t: PhantomData<T>,
}
impl<T> Unpin for Task<T> {}
impl<T> Task<T> {
pub(crate) fn is_finished(&self) -> bool {
core(self.h).state.get() & FINISHED != 0
}
pub(crate) fn set_cancel_on_drop(&self, enabled: bool) {
let state = &core(self.h).state;
state.set(if enabled {
state.get() | CANCEL_ON_DROP
} else {
state.get() & !CANCEL_ON_DROP
});
}
pub(crate) fn cancel(self) {
let h = ManuallyDrop::new(self).h;
detach_local(h, true);
}
pub(crate) fn poll_join(&mut self, cx: &Context<'_>) -> Poll<Option<T>> {
let hdr = header(self.h);
let local = core(self.h);
if local.state.get() & FINISHED == 0 {
drop(unsafe { hdr.awaiter.register(cx.waker()) });
if local.state.get() & FINISHED == 0 {
return Poll::Pending;
}
drop(unsafe { hdr.awaiter.take() });
}
let mut out = None;
unsafe { (header(self.h).vtable.take_output)(self.h, (&raw mut out).cast()) };
Poll::Ready(out)
}
}
impl<T> Drop for Task<T> {
fn drop(&mut self) {
let cancel = core(self.h).state.get() & CANCEL_ON_DROP != 0;
detach_local(self.h, cancel);
}
}
fn detach_local(h: NonNull<Header>, cancel: bool) {
let hdr = header(h);
let local = core(h);
let state = local.state.get();
if state & FINISHED != 0 {
drop_output(h);
return;
}
let awaiter = unsafe { hdr.awaiter.take() };
let state = state | DETACHED | if cancel { CLOSE } else { 0 };
if cancel && state & (SCHEDULED | RUNNING) == 0 {
local.state.set(state | SCHEDULED);
owner_queue(h).schedule_local(Runnable(h));
} else {
local.state.set(state);
release_unshared(h);
}
drop(awaiter);
}
impl<T> fmt::Debug for Task<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Task")
.field("location", &header(self.h).location)
.finish()
}
}
#[cfg(all(test, not(ntex_loom)))]
#[path = "../../tests/rt/task_core_tests.rs"]
mod core_tests;
#[cfg(all(test, ntex_loom))]
#[path = "../../tests/rt/task_loom_shared.rs"]
mod tests;