use std::{
cell::{Cell, RefCell},
collections::HashMap,
};
use super::{
Arc, Core, Header, ManuallyDrop, NonNull, Ordering, RawWaker, Remote, Runnable, RunnableQueue,
Waker, abort, core, inc_local, live_owner_queue, mark_scheduled, raw_waker, release_local,
release_local_ordered, task_ptr, wake_local,
};
use crate::rt::{ThreadId, try_current_thread_id};
#[cfg(ntex_loom)]
use loom::sync::RwLock;
#[cfg(not(ntex_loom))]
use std::sync::{OnceLock, RwLock};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct RuntimeId(u16);
#[derive(Clone, Copy)]
enum OwnerEndpoint {
Unknown,
Active(RuntimeId),
Closed(RuntimeId),
}
enum Owner {
Active,
Closed,
Remote,
}
#[cfg(not(ntex_loom))]
thread_local! {
static OWNER_ENDPOINT: Cell<OwnerEndpoint> = const { Cell::new(OwnerEndpoint::Unknown) };
static REMOTE_ENDPOINT: RefCell<Option<Arc<RunnableQueue>>> = const { RefCell::new(None) };
}
#[cfg(ntex_loom)]
loom::thread_local! {
static OWNER_ENDPOINT: Cell<OwnerEndpoint> = Cell::new(OwnerEndpoint::Unknown);
static REMOTE_ENDPOINT: RefCell<Option<Arc<RunnableQueue>>> = RefCell::new(None);
}
struct Registry {
next: u16,
queues: HashMap<RuntimeId, Arc<RunnableQueue>>,
}
impl Registry {
fn new() -> Self {
Self {
next: 0,
queues: HashMap::new(),
}
}
}
#[cfg(not(ntex_loom))]
fn registry() -> &'static RwLock<Registry> {
static REGISTRY: OnceLock<RwLock<Registry>> = OnceLock::new();
REGISTRY.get_or_init(|| RwLock::new(Registry::new()))
}
#[cfg(ntex_loom)]
fn registry() -> &'static RwLock<Registry> {
loom::lazy_static! {
static ref REGISTRY: RwLock<Registry> = RwLock::new(Registry::new());
}
®ISTRY
}
pub(crate) fn runtime_id() -> RuntimeId {
let mut registry = registry().write().unwrap();
registry.next = registry.next.checked_add(1).unwrap_or_else(|| {
log::error!(
target: crate::rt::LOG_TARGET,
"Runtime identities are exhausted, {} runtimes per process",
u16::MAX
);
abort()
});
RuntimeId(registry.next)
}
#[cfg(all(test, not(ntex_loom)))]
pub(crate) fn set_runtime_index(last: u16) {
registry().write().unwrap().next = last;
}
pub(crate) fn register(queue: &Arc<RunnableQueue>) {
let old = registry()
.write()
.unwrap()
.queues
.insert(queue.runtime_id, queue.clone());
assert!(old.is_none(), "runtime registered twice");
}
#[must_use]
pub(crate) struct Shutdown(RuntimeId);
impl Drop for Shutdown {
fn drop(&mut self) {
let removed = registry().write().unwrap().queues.remove(&self.0);
drop(removed);
}
}
#[allow(
clippy::readonly_write_lock,
reason = "shutdown synchronizes in-flight endpoint lookups"
)]
pub(crate) fn unregister(id: RuntimeId) -> Shutdown {
OWNER_ENDPOINT.with(|cached| cached.set(OwnerEndpoint::Closed(id)));
let registry = registry().write().unwrap();
debug_assert!(
registry
.queues
.get(&id)
.is_some_and(|queue| queue.is_closed())
);
Shutdown(id)
}
pub(super) fn retain_owner_queue(h: NonNull<Header>) -> Option<Arc<RunnableQueue>> {
registry()
.read()
.unwrap()
.queues
.get(&core(h).routing.runtime)
.cloned()
}
pub(super) struct Routing {
pub(super) runtime: RuntimeId,
pub(super) owner: ThreadId,
}
unsafe fn routing<'a>(data: *const ()) -> &'a Routing {
unsafe { &(*data.cast::<Core>()).routing }
}
#[inline]
pub(super) unsafe fn active_owner(data: *const ()) -> bool {
matches!(unsafe { owner(data) }, Owner::Active)
}
#[inline]
unsafe fn owner(data: *const ()) -> Owner {
let route = unsafe { routing(data) };
match OWNER_ENDPOINT.with(Cell::get) {
OwnerEndpoint::Active(id) if id == route.runtime => Owner::Active,
OwnerEndpoint::Closed(id) if id == route.runtime => Owner::Closed,
_ => refresh_owner(route),
}
}
#[cold]
fn refresh_owner(route: &Routing) -> Owner {
if Some(route.owner) != try_current_thread_id() {
return Owner::Remote;
}
let id = route.runtime;
if with_registered(id, |_| ()).is_some() {
OWNER_ENDPOINT.with(|cached| cached.set(OwnerEndpoint::Active(id)));
Owner::Active
} else {
OWNER_ENDPOINT.with(|cached| cached.set(OwnerEndpoint::Closed(id)));
Owner::Closed
}
}
pub(super) fn with_registered<R>(
id: RuntimeId,
f: impl FnOnce(&Arc<RunnableQueue>) -> R,
) -> Option<R> {
let registry = registry().read().unwrap();
let queue = registry.queues.get(&id)?;
if queue.is_closed() { None } else { Some(f(queue)) }
}
fn publish(id: RuntimeId, entry: WakeEntry) -> bool {
let mut entry = Some(entry);
let cached = REMOTE_ENDPOINT.try_with(|cached| {
let cached = cached.borrow();
cached
.as_ref()
.filter(|queue| queue.runtime_id == id)
.map(|queue| publish_admitted(queue, entry.take().unwrap()))
});
let notify = match cached {
Ok(Some(notify)) => notify,
_ => publish_uncached(id, entry.unwrap()),
};
let Ok(notify) = notify else {
return false;
};
if let Some(queue) = notify {
queue.notify();
}
true
}
#[cold]
fn publish_uncached(id: RuntimeId, entry: WakeEntry) -> Result<Option<Arc<RunnableQueue>>, ()> {
let queue = with_registered(id, Arc::clone).ok_or(())?;
if let Ok(previous) = REMOTE_ENDPOINT.try_with(|cached| cached.replace(Some(queue.clone()))) {
drop(previous);
}
publish_admitted(&queue, entry)
}
fn publish_admitted(
queue: &Arc<RunnableQueue>,
entry: WakeEntry,
) -> Result<Option<Arc<RunnableQueue>>, ()> {
let permit = queue.admit(false).ok_or(())?;
let touched = unsafe { &(*entry.header().cast::<Core>().as_ptr()).remote_touched };
if !touched.load(Ordering::Acquire) {
touched.store(true, Ordering::Release);
}
Ok(permit.publish(entry))
}
pub(crate) struct WakeEntry {
data: NonNull<Header>,
#[cfg(not(target_pointer_width = "64"))]
op: Operation,
}
#[cfg(target_pointer_width = "64")]
const OP_MASK: usize = 7;
#[cfg(target_pointer_width = "64")]
const _: () = {
assert!(std::mem::align_of::<Header>() > OP_MASK);
assert!(std::mem::size_of::<WakeEntry>() == std::mem::size_of::<usize>());
};
#[derive(Clone, Copy)]
#[repr(u8)]
enum Operation {
Spawn = 0,
AddRef = 1,
Release = 2,
Wake = 3,
WakeAndRelease = 4,
}
unsafe impl Send for WakeEntry {}
impl WakeEntry {
fn new(h: NonNull<Header>, op: Operation) -> Self {
#[cfg(target_pointer_width = "64")]
let h = {
debug_assert_eq!(h.addr().get() & OP_MASK, 0);
h.map_addr(|addr| addr | op as usize)
};
Self {
data: h,
#[cfg(not(target_pointer_width = "64"))]
op,
}
}
fn header(&self) -> NonNull<Header> {
#[cfg(target_pointer_width = "64")]
return self.data.map_addr(|addr| {
unsafe { std::num::NonZeroUsize::new_unchecked(addr.get() & !OP_MASK) }
});
#[cfg(not(target_pointer_width = "64"))]
return self.data;
}
fn operation(&self) -> Operation {
#[cfg(target_pointer_width = "64")]
return match self.data.addr().get() & OP_MASK {
0 => Operation::Spawn,
1 => Operation::AddRef,
2 => Operation::Release,
3 => Operation::Wake,
4 => Operation::WakeAndRelease,
_ => unreachable!("invalid wake operation"),
};
#[cfg(not(target_pointer_width = "64"))]
return self.op;
}
pub(crate) fn spawn(task: Remote) -> Self {
Self::new(ManuallyDrop::new(task).0, Operation::Spawn)
}
#[cfg(test)]
pub(crate) fn into_remote(self) -> Remote {
assert!(
matches!(self.operation(), Operation::Spawn),
"not a submission"
);
Remote(self.header())
}
pub(crate) fn into_runnable(self, queue: &RunnableQueue) -> Option<Runnable> {
let h = self.header();
let op = self.operation();
debug_assert_eq!(core(h).routing.runtime, queue.runtime_id);
match op {
Operation::Spawn => return Some(Remote(h).into_runnable()),
Operation::AddRef => inc_local(h),
Operation::Release => release_local_ordered(h),
Operation::Wake | Operation::WakeAndRelease => {
queue.acquire_wakes();
let runnable = mark_scheduled(h).then(|| {
inc_local(h);
Runnable(h)
});
if matches!(op, Operation::WakeAndRelease) {
release_local_ordered(h);
}
return runnable;
}
}
None
}
}
pub(super) fn defer_release(h: NonNull<Header>) {
let entry = WakeEntry::new(h, Operation::Release);
if let Some(queue) = live_owner_queue(h)
&& !queue.idle.get()
&& !queue.is_closed()
{
queue.wake_queue.push(entry);
return;
}
if let Some(queue) = retain_owner_queue(h) {
queue.owner_entry(entry);
} else {
release_local_ordered(h);
}
}
pub(super) unsafe fn clone_waker(data: *const ()) -> RawWaker {
let h = unsafe { task_ptr(data) };
let acquired = match unsafe { owner(data) } {
Owner::Active => {
inc_local(h);
true
}
Owner::Closed => false,
Owner::Remote => publish(
unsafe { routing(data) }.runtime,
WakeEntry::new(h, Operation::AddRef),
),
};
if acquired {
raw_waker(data)
} else {
RawWaker::new(Waker::noop().data(), Waker::noop().vtable())
}
}
pub(super) unsafe fn wake<const OWNED: bool>(data: *const ()) {
let h = unsafe { task_ptr(data) };
match unsafe { owner(data) } {
Owner::Active => wake_local::<false>(h, OWNED),
Owner::Closed => {}
Owner::Remote => {
publish(
unsafe { routing(data) }.runtime,
WakeEntry::new(
h,
if OWNED {
Operation::WakeAndRelease
} else {
Operation::Wake
},
),
);
}
}
}
pub(super) unsafe fn drop_waker(data: *const ()) {
let h = unsafe { task_ptr(data) };
match unsafe { owner(data) } {
Owner::Active => release_local(h),
Owner::Closed => {}
Owner::Remote => {
publish(
unsafe { routing(data) }.runtime,
WakeEntry::new(h, Operation::Release),
);
}
}
}
pub(crate) struct WakeQueue {
#[cfg(not(ntex_loom))]
queue: crossbeam_queue::SegQueue<WakeEntry>,
#[cfg(ntex_loom)]
queue: loom::sync::Mutex<std::collections::VecDeque<WakeEntry>>,
}
impl WakeQueue {
pub(crate) fn new() -> Self {
Self {
#[cfg(not(ntex_loom))]
queue: crossbeam_queue::SegQueue::new(),
#[cfg(ntex_loom)]
queue: loom::sync::Mutex::new(std::collections::VecDeque::new()),
}
}
pub(crate) fn push(&self, entry: WakeEntry) {
#[cfg(not(ntex_loom))]
self.queue.push(entry);
#[cfg(ntex_loom)]
self.queue.lock().unwrap().push_back(entry);
}
pub(crate) fn pop(&self) -> Option<WakeEntry> {
#[cfg(not(ntex_loom))]
return self.queue.pop();
#[cfg(ntex_loom)]
return self.queue.lock().unwrap().pop_front();
}
pub(crate) fn is_empty(&self) -> bool {
#[cfg(not(ntex_loom))]
return self.queue.is_empty();
#[cfg(ntex_loom)]
return self.queue.lock().unwrap().is_empty();
}
}