use std::cell::Cell;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::{Arc, atomic::Ordering};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::{fmt, future::Future, marker::PhantomData, mem, mem::ManuallyDrop, pin::Pin};
use std::{process::abort, ptr, ptr::NonNull, thread};
use crate::handle::SpawnLocation;
use crate::rt::{REPORTED, RunnableQueue, TaskStats, current_thread_id};
#[cfg(not(ntex_loom))]
use std::sync::atomic::{AtomicPtr, AtomicU32};
#[cfg(ntex_loom)]
use loom::sync::atomic::{AtomicPtr, AtomicU32};
struct SharedCell<T>(
#[cfg(not(ntex_loom))] std::cell::UnsafeCell<T>,
#[cfg(ntex_loom)] loom::cell::UnsafeCell<T>,
);
impl<T> SharedCell<T> {
fn new(v: T) -> Self {
#[cfg(not(ntex_loom))]
return Self(std::cell::UnsafeCell::new(v));
#[cfg(ntex_loom)]
return Self(loom::cell::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: u32 = 1;
const RUNNING: u32 = 1 << 1;
const FINISHED: u32 = 1 << 2;
const CLOSE: u32 = 1 << 3;
const WAKES_REPORTED: u32 = 1 << 4;
const DETACHED: u32 = 1 << 5;
const LISTED: u32 = 1 << 6;
const WAKES_SHIFT: u32 = 8;
pub(crate) const MAX_SELF_WAKES: u32 = u32::MAX >> WAKES_SHIFT;
const R_QUEUED: u32 = 1;
const R_WOKEN: u32 = 1 << 1;
const R_CANCEL: u32 = 1 << 2;
const R_FINISHED: u32 = 1 << 3;
const R_REGISTERING: u32 = 1 << 4;
const R_DETACHED: u32 = 1 << 5;
const R_SHIFT: u32 = 6;
const R_REF: u32 = 1 << R_SHIFT;
const R_FLAGS: u32 = R_REF - 1;
const MAX_REFS: i32 = i32::MAX >> (R_SHIFT + 1);
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>),
}
struct Link {
next: AtomicPtr<Link>,
}
#[repr(C)]
struct Header {
link: Link,
next: Cell<*mut Header>,
list_prev: Cell<*mut Header>,
list_next: Cell<*mut Header>,
vtable: &'static VTable,
queue: Arc<RunnableQueue>,
location: SpawnLocation,
awaiter: Awaiter,
remote: AtomicU32,
state: Cell<u32>,
refs: Cell<u32>,
streak_start: Cell<u32>,
}
#[cfg(all(target_pointer_width = "64", not(ntex_loom)))]
const _: () = assert!(mem::size_of::<Header>() == 80);
enum Stage<F: Future> {
Running(F),
Finished(F::Output),
Consumed,
}
#[repr(C)]
struct RawTask<F: Future> {
header: Header,
stage: SharedCell<Stage<F>>,
}
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::<RawTask<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::<RawTask<F>>().as_ptr()) });
}
}
#[inline]
fn delta(word: u32) -> i32 {
word.cast_signed() >> R_SHIFT
}
struct Awaiter(SharedCell<*mut ()>);
const BOXED: usize = 1;
impl Awaiter {
fn new() -> Self {
Self(SharedCell::new(ptr::null_mut()))
}
unsafe fn take(&self) -> Option<Waker> {
let p = self.0.with_mut(|p| unsafe { p.replace(ptr::null_mut()) });
if p.is_null() {
None
} else if p.addr() & BOXED != 0 {
let w = unsafe { Box::from_raw(p.map_addr(|a| a & !BOXED).cast::<Waker>()) };
Some(*w)
} else {
Some(unsafe { Waker::from_raw(RawWaker::new(p, &WAKER_VTABLE)) })
}
}
unsafe fn register(&self, waker: &Waker) -> Option<Waker> {
let cur = self.0.with_mut(|p| unsafe { *p });
let new = if ptr::eq(waker.vtable(), &raw const WAKER_VTABLE) {
if ptr::eq(cur, waker.data()) {
return None;
}
ManuallyDrop::new(waker.clone()).data().cast_mut()
} else {
if cur.addr() & BOXED != 0
&& unsafe { &*cur.map_addr(|a| a & !BOXED).cast::<Waker>() }.will_wake(waker)
{
return None;
}
Box::into_raw(Box::new(waker.clone()))
.cast::<()>()
.map_addr(|a| a | BOXED)
};
let old = unsafe { self.take() };
self.0.with_mut(|p| unsafe { *p = new });
old
}
}
impl Drop for Awaiter {
fn drop(&mut self) {
drop(unsafe { self.take() });
}
}
static WAKER_VTABLE: RawWakerVTable =
RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker);
#[inline]
fn header<'a>(h: NonNull<Header>) -> &'a Header {
unsafe { h.as_ref() }
}
#[inline]
fn is_owner(h: NonNull<Header>) -> bool {
header(h).queue.id == current_thread_id()
}
#[inline]
unsafe fn task_ptr(ptr: *const ()) -> NonNull<Header> {
unsafe { NonNull::new_unchecked(ptr.cast_mut().cast::<Header>()) }
}
unsafe fn clone_waker(ptr: *const ()) -> RawWaker {
let h = unsafe { task_ptr(ptr) };
if is_owner(h) && header(h).state.get() & FINISHED == 0 {
inc_local(h);
} else {
let prev = header(h).remote.fetch_add(R_REF, Ordering::Relaxed);
if delta(prev) > MAX_REFS {
abort();
}
}
RawWaker::new(ptr, &WAKER_VTABLE)
}
unsafe fn wake(ptr: *const ()) {
wake_any(unsafe { task_ptr(ptr) }, true);
}
unsafe fn wake_by_ref(ptr: *const ()) {
wake_any(unsafe { task_ptr(ptr) }, false);
}
unsafe fn drop_waker(ptr: *const ()) {
release(unsafe { task_ptr(ptr) });
}
fn wake_any(h: NonNull<Header>, owned: bool) {
if is_owner(h) {
wake_local(h, owned);
} else {
remote_signal(h, R_WOKEN, owned);
}
}
fn inc_local(h: NonNull<Header>) {
let refs = &header(h).refs;
match refs.get().checked_add(1) {
Some(n) => refs.set(n),
None => abort(),
}
}
fn release(h: NonNull<Header>) {
if is_owner(h) {
release_local(h);
} else {
remote_signal(h, 0, true);
}
}
fn wake_local(h: NonNull<Header>, owned: bool) {
let hdr = header(h);
let state = hdr.state.get();
if state & (SCHEDULED | FINISHED) == 0 {
hdr.state.set(state | SCHEDULED);
if state & RUNNING == 0 {
if !owned {
inc_local(h);
}
hdr.queue.schedule_local(Runnable(h));
return;
}
}
if owned {
release_local(h);
}
}
fn remote_signal(h: NonNull<Header>, flags: u32, owned: bool) {
let remote = &header(h).remote;
let mut permit = None;
let mut cur = remote.load(Ordering::Relaxed);
loop {
if cur & R_FINISHED != 0 {
drop(permit);
if owned {
release_shared(h);
}
return;
}
if delta(cur).abs() > MAX_REFS {
abort();
}
let set = flags & !cur;
let (new, push) = if cur & R_QUEUED != 0 {
if owned {
((cur | set).wrapping_sub(R_REF), false)
} else {
(cur | set, false)
}
} else if owned && set == 0 && delta(cur) > 0 {
(cur.wrapping_sub(R_REF), false)
} else if owned {
(cur | set | R_QUEUED, true)
} else if set != 0 {
((cur | set | R_QUEUED).wrapping_add(R_REF), true)
} else {
(cur, false)
};
if push && permit.is_none() {
permit = header(h).queue.admit(owned);
if permit.is_none() {
if owned {
let prev = remote.fetch_sub(R_REF, Ordering::AcqRel);
if prev & R_FINISHED != 0 && delta(prev) == 1 {
unsafe { (header(h).vtable.dealloc)(h) };
}
}
return;
}
}
match remote.compare_exchange_weak(cur, new, Ordering::AcqRel, Ordering::Relaxed) {
Ok(_) if push => break,
Ok(_) => return,
Err(v) => cur = v,
}
}
permit.unwrap().schedule(Remote(h));
}
fn release_local(h: NonNull<Header>) {
let hdr = header(h);
if hdr.state.get() & FINISHED != 0 {
release_shared(h);
return;
}
let refs = hdr.refs.get() - 1;
hdr.refs.set(refs);
if refs == 0 {
last_ref(h);
}
}
#[cold]
fn last_ref(h: NonNull<Header>) {
let hdr = header(h);
let mut cur = hdr.remote.load(Ordering::Acquire);
loop {
let delta = delta(cur);
if delta == 0 {
break;
}
match hdr.remote.compare_exchange_weak(
cur,
cur & R_FLAGS,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
hdr.refs
.set(u32::try_from(delta).unwrap_or_else(|_| abort()));
return;
}
Err(v) => cur = v,
}
}
let state = hdr.state.get();
debug_assert_eq!(state & FINISHED, 0);
hdr.refs.set(1);
hdr.state.set(state | SCHEDULED | CLOSE);
hdr.queue.schedule_local(Runnable(h));
}
fn release_shared(h: NonNull<Header>) {
let prev = header(h).remote.fetch_sub(R_REF, Ordering::AcqRel);
if delta(prev) == 1 {
unsafe { (header(h).vtable.dealloc)(h) };
}
}
fn close(h: NonNull<Header>) {
struct Finish(NonNull<Header>);
impl Drop for Finish {
fn drop(&mut self) {
let hdr = header(self.0);
finish(self.0, hdr.state.get() & !(RUNNING | SCHEDULED | CLOSE));
let completed = complete(self.0);
release_local(self.0);
completed.notify();
}
}
let hdr = header(h);
hdr.state.set(hdr.state.get() | RUNNING);
let finish = Finish(h);
unsafe { (header(finish.0).vtable.drop_future)(finish.0) };
}
fn finish(h: NonNull<Header>, state: u32) {
let hdr = header(h);
hdr.state.set((state | FINISHED) & !LISTED);
if state & LISTED != 0 {
hdr.queue.tasks.remove(h);
}
}
struct Completed {
detached: bool,
awaiter: Option<Waker>,
}
impl Completed {
fn notify(self) {
drop(self);
}
}
impl Drop for Completed {
fn drop(&mut self) {
let Some(w) = self.awaiter.take() else {
return;
};
let detached = self.detached;
let notify = move || {
if !detached {
w.wake();
}
};
if thread::panicking() {
if let Err(payload) = catch_unwind(AssertUnwindSafe(notify)) {
super::suppress_cleanup_panic(payload);
}
} else {
notify();
}
}
}
fn complete(h: NonNull<Header>) -> Completed {
let hdr = header(h);
let refs = hdr.refs.replace(0);
if refs > MAX_REFS.cast_unsigned() {
abort();
}
let prev = hdr
.remote
.fetch_add((refs << R_SHIFT) | R_FINISHED, Ordering::AcqRel);
let detached = hdr.state.get() & DETACHED != 0 || prev & R_DETACHED != 0;
let awaiter = if prev & R_REGISTERING == 0 {
unsafe { hdr.awaiter.take() }
} else {
None
};
Completed { detached, awaiter }
}
#[cfg(test)]
thread_local! {
pub(crate) static UNSHARED_FINISHES: Cell<usize> = const { Cell::new(0) };
}
fn finish_unshared(h: NonNull<Header>) {
struct Dealloc(NonNull<Header>);
impl Drop for Dealloc {
fn drop(&mut self) {
unsafe { (header(self.0).vtable.dealloc)(self.0) };
}
}
#[cfg(test)]
UNSHARED_FINISHES.with(|n| n.set(n.get() + 1));
let task = Dealloc(h);
let hdr = header(task.0);
debug_assert_ne!(hdr.state.get() & DETACHED, 0);
unsafe { (hdr.vtable.drop_output)(task.0) };
}
struct Registering<'a>(&'a AtomicU32);
impl Drop for Registering<'_> {
fn drop(&mut self) {
self.0.fetch_and(!R_REGISTERING, Ordering::AcqRel);
}
}
struct Release(NonNull<Header>);
impl Drop for Release {
fn drop(&mut self) {
let h = self.0;
let cur = header(h).remote.load(Ordering::Acquire);
debug_assert_ne!(cur & R_FINISHED, 0);
if delta(cur) == 1 {
unsafe { (header(h).vtable.dealloc)(h) };
} else {
release_shared(h);
}
}
}
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, const LOCAL: bool>(
future: F,
queue: &Arc<RunnableQueue>,
location: SpawnLocation,
) -> Task<F::Output> {
match unsafe { create::<_, LOCAL>(future, queue, location, false) } {
Ok(h) => Task { h, _t: PhantomData },
Err(future) => {
let h = alloc::<F>(
Stage::Consumed,
queue,
location,
FINISHED,
R_REF | R_FINISHED,
0,
);
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 || queue.id == 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,
if local { SCHEDULED | LISTED } else { 0 } | if detached { DETACHED } else { 0 },
if local { 0 } else { R_QUEUED | R_WOKEN },
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: u32,
remote: u32,
refs: u32,
) -> NonNull<Header> {
let raw = Box::new(RawTask {
header: Header {
link: Link {
next: AtomicPtr::new(ptr::null_mut()),
},
next: Cell::new(ptr::null_mut()),
list_prev: Cell::new(ptr::null_mut()),
list_next: Cell::new(ptr::null_mut()),
vtable: &Fns::<F>::VTABLE,
queue: queue.clone(),
location,
awaiter: Awaiter::new(),
remote: AtomicU32::new(remote),
state: Cell::new(flags),
refs: Cell::new(refs),
streak_start: Cell::new(0),
},
stage: SharedCell::new(stage),
});
NonNull::from(Box::leak(raw)).cast::<Header>()
}
pub(crate) struct Runnable(NonNull<Header>);
unsafe impl Send for Runnable {}
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 hdr = header(self.0);
hdr.state
.set(hdr.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 state = hdr.state.get() & !SCHEDULED;
if state & (CLOSE | FINISHED) != 0 {
discard(h, state);
return false;
}
hdr.state.set(state | RUNNING);
let waker = ManuallyDrop::new(unsafe {
Waker::from_raw(RawWaker::new(h.as_ptr().cast(), &WAKER_VTABLE))
});
let mut cx = Context::from_waker(&waker);
let guard = Guard(h);
let ready = unsafe { (hdr.vtable.poll)(h, &mut cx) };
mem::forget(guard);
let state = hdr.state.get() & !RUNNING;
if ready {
finish(h, state & !(SCHEDULED | CLOSE));
if hdr.refs.get() == 1 && hdr.remote.load(Ordering::Acquire) == 0 {
finish_unshared(h);
return false;
}
let completed = complete(h);
if completed.detached {
drop_output(h);
} else {
release_local(h);
}
completed.notify();
return false;
} else if state & CLOSE != 0 {
discard(h, state);
return false;
}
hdr.state.set(state);
if state & SCHEDULED != 0 {
hdr.queue.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, header(self.0).state.get());
} else {
leaked(self.0);
}
}
}
fn discard(h: NonNull<Header>, state: u32) {
header(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) -> Option<Runnable> {
debug_assert!(is_owner(self.0), "task merged off its runtime thread");
let h = ManuallyDrop::new(self).0;
let hdr = header(h);
if hdr.state.get() & FINISHED != 0 {
release_shared(h);
return None;
}
let cur = hdr
.remote
.fetch_and(R_FINISHED | R_REGISTERING | R_DETACHED, Ordering::AcqRel);
let refs = i64::from(hdr.refs.get()) + i64::from(delta(cur));
hdr.refs
.set(u32::try_from(refs).unwrap_or_else(|_| abort()));
let mut state = hdr.state.get();
if state & LISTED == 0 {
state |= LISTED;
hdr.state.set(state);
hdr.queue.tasks.push(h);
}
if cur & R_CANCEL != 0 && state & FINISHED == 0 {
state |= CLOSE;
hdr.state.set(state);
}
if cur & (R_WOKEN | R_CANCEL) != 0 && state & (SCHEDULED | FINISHED) == 0 {
hdr.state.set(state | SCHEDULED);
if state & RUNNING == 0 {
return Some(Runnable(h));
}
}
release_local(h);
None
}
}
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 state = header(self.0).state.get();
let reported = if state & WAKES_REPORTED != 0 { REPORTED } else { 0 };
(state >> WAKES_SHIFT) | reported
}
fn set_self_wakes(self, wakes: u32) {
let hdr = header(self.0);
let count = (wakes & !REPORTED).min(MAX_SELF_WAKES);
let reported = if wakes & REPORTED != 0 { WAKES_REPORTED } else { 0 };
let flags = hdr.state.get() & ((1 << WAKES_SHIFT) - 1) & !WAKES_REPORTED;
hdr.state.set(flags | reported | (count << WAKES_SHIFT));
}
fn streak_start(self) -> u32 {
header(self.0).streak_start.get()
}
fn set_streak_start(self, start: u32) {
header(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;
header(h).next.set(ptr::null_mut());
let tail = self.tail.replace(h.as_ptr());
if tail.is_null() {
self.head.set(h.as_ptr());
} else {
unsafe { (*tail).next.set(h.as_ptr()) };
}
}
pub(crate) fn pop(&self) -> Option<Runnable> {
let h = NonNull::new(self.head.get())?;
let next = header(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 hdr = header(h);
let head = self.head.replace(h.as_ptr());
hdr.list_prev.set(ptr::null_mut());
hdr.list_next.set(head);
if !head.is_null() {
unsafe { (*head).list_prev.set(h.as_ptr()) };
}
}
fn remove(&self, h: NonNull<Header>) {
let hdr = header(h);
let (prev, next) = (hdr.list_prev.get(), hdr.list_next.get());
if prev.is_null() {
self.head.set(next);
} else {
unsafe { (*prev).list_next.set(next) };
}
if !next.is_null() {
unsafe { (*next).list_prev.set(prev) };
}
}
pub(crate) fn close_first(&self) -> bool {
let Some(h) = NonNull::new(self.head.get()) else {
return false;
};
let hdr = header(h);
let state = hdr.state.get();
if state & RUNNING != 0 {
hdr.state.set(state & !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()
}
}
#[derive(Debug)]
pub(crate) struct Busy;
#[repr(C)]
pub(crate) struct RemoteQueue {
tail: AtomicPtr<Link>,
_consumer: [super::CacheLine; 0],
head: Cell<*mut Link>,
stub: NonNull<Link>,
}
impl RemoteQueue {
pub(crate) fn new() -> Self {
let stub = NonNull::from(Box::leak(Box::new(Link {
next: AtomicPtr::new(ptr::null_mut()),
})));
Self {
tail: AtomicPtr::new(stub.as_ptr()),
_consumer: [],
head: Cell::new(stub.as_ptr()),
stub,
}
}
pub(crate) fn push(&self, task: Remote) {
unsafe { self.push_link(ManuallyDrop::new(task).0.cast::<Link>().as_ptr()) };
}
unsafe fn push_link(&self, link: *mut Link) {
unsafe { (*link).next.store(ptr::null_mut(), Ordering::Relaxed) };
let prev = self.tail.swap(link, Ordering::AcqRel);
unsafe { (*prev).next.store(link, Ordering::Release) };
}
pub(crate) fn pop(&self) -> Result<Option<Remote>, Busy> {
let stub = self.stub.as_ptr();
let mut head = self.head.get();
let mut next = unsafe { (*head).next.load(Ordering::Acquire) };
if head == stub {
if next.is_null() {
return if self.tail.load(Ordering::Acquire) == stub {
Ok(None)
} else {
Err(Busy)
};
}
self.head.set(next);
head = next;
next = unsafe { (*head).next.load(Ordering::Acquire) };
}
if next.is_null() {
if self.tail.load(Ordering::Acquire) != head {
return Err(Busy);
}
unsafe { self.push_link(stub) };
next = unsafe { (*head).next.load(Ordering::Acquire) };
if next.is_null() {
return Err(Busy);
}
}
self.head.set(next);
Ok(Some(Remote(unsafe {
NonNull::new_unchecked(head.cast::<Header>())
})))
}
pub(crate) fn is_empty(&self) -> bool {
let stub = self.stub.as_ptr();
self.head.get() == stub && self.tail.load(Ordering::Acquire) == stub
}
}
impl Drop for RemoteQueue {
fn drop(&mut self) {
drop(unsafe { Box::from_raw(self.stub.as_ptr()) });
}
}
impl fmt::Debug for RemoteQueue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RemoteQueue").finish_non_exhaustive()
}
}
pub(crate) struct Task<T> {
h: NonNull<Header>,
_t: PhantomData<T>,
}
unsafe impl<T: Send> Send for Task<T> {}
unsafe impl<T: Send> Sync for Task<T> {}
impl<T> Unpin for Task<T> {}
impl<T> Task<T> {
pub(crate) fn is_finished(&self) -> bool {
if is_owner(self.h) {
header(self.h).state.get() & FINISHED != 0
} else {
header(self.h).remote.load(Ordering::Acquire) & R_FINISHED != 0
}
}
pub(crate) fn cancel(self) {
let h = ManuallyDrop::new(self).h;
if is_owner(h) {
detach_local(h, true);
} else {
detach_remote(h, R_CANCEL);
}
}
pub(crate) fn detach(self) {
drop(self);
}
pub(crate) fn poll_join(&mut self, cx: &Context<'_>) -> Poll<Option<T>> {
let hdr = header(self.h);
if is_owner(self.h) {
if hdr.state.get() & FINISHED == 0 {
drop(unsafe { hdr.awaiter.register(cx.waker()) });
if hdr.state.get() & FINISHED == 0 {
return Poll::Pending;
}
drop(unsafe { hdr.awaiter.take() });
}
} else {
let mut cur = hdr.remote.load(Ordering::Acquire);
while cur & R_FINISHED == 0 {
if let Err(v) = hdr.remote.compare_exchange_weak(
cur,
cur | R_REGISTERING,
Ordering::Acquire,
Ordering::Acquire,
) {
cur = v;
continue;
}
let guard = Registering(&hdr.remote);
let old = unsafe { hdr.awaiter.register(cx.waker()) };
mem::forget(guard);
let prev = hdr.remote.fetch_and(!R_REGISTERING, Ordering::AcqRel);
drop(old);
if prev & R_FINISHED == 0 {
return Poll::Pending;
}
drop(unsafe { hdr.awaiter.take() });
break;
}
}
Poll::Ready(self.take_output())
}
fn take_output(&mut self) -> Option<T> {
let mut out = None;
unsafe { (header(self.h).vtable.take_output)(self.h, (&raw mut out).cast()) };
out
}
}
impl<T> Drop for Task<T> {
fn drop(&mut self) {
if is_owner(self.h) {
detach_local(self.h, false);
} else {
detach_remote(self.h, 0);
}
}
}
fn detach_local(h: NonNull<Header>, cancel: bool) {
let hdr = header(h);
let state = hdr.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 {
hdr.state.set(state | SCHEDULED);
hdr.queue.schedule_local(Runnable(h));
} else {
hdr.state.set(state);
release_local(h);
}
drop(awaiter);
}
fn detach_remote(h: NonNull<Header>, flags: u32) {
let remote = &header(h).remote;
let mut cur = remote.load(Ordering::Acquire);
loop {
if cur & R_FINISHED != 0 {
drop_output(h);
return;
}
if delta(cur).abs() > MAX_REFS {
abort();
}
let release = flags == 0 && (cur & R_QUEUED != 0 || delta(cur) > 0);
let new = if release {
(cur | R_DETACHED).wrapping_sub(R_REF)
} else {
cur | R_DETACHED
};
match remote.compare_exchange_weak(cur, new, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) if release => return,
Ok(_) => break,
Err(v) => cur = v,
}
}
remote_signal(h, flags, true);
}
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, ntex_loom))]
#[test]
#[should_panic(expected = "currently writing to cell")]
fn task_loom_shared_cell_access() {
struct Shared(SharedCell<usize>);
unsafe impl Sync for Shared {}
loom::model(|| {
let cell = Arc::new(Shared(SharedCell::new(0)));
let entered = Arc::new(loom::sync::atomic::AtomicBool::new(false));
let (other, entered2) = (cell.clone(), entered.clone());
let writer = loom::thread::spawn(move || {
other.0.with_mut(|p| {
entered2.store(true, Ordering::Release);
loom::thread::yield_now();
unsafe { *p = 1 };
});
});
while !entered.load(Ordering::Acquire) {
loom::thread::yield_now();
}
cell.0.with_mut(|p| unsafe { *p = 2 });
writer.join().unwrap();
});
}