use alloc::collections::BinaryHeap;
use core::cell::Cell;
use core::fmt::{Debug, Formatter};
use nonmax::NonMaxUsize;
use crate::utils::OnceCell;
use crate::utils::sync::{Mutex, MutexGuard};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[must_use]
#[repr(transparent)]
pub struct LiveThreadId {
index: NonMaxUsize,
}
impl LiveThreadId {
#[inline]
pub fn current() -> Self {
LIVE_ID.with(|cell| match cell.get() {
Some(existing) => existing,
None => {
let new_id = Self::alloc();
cell.set(Some(new_id));
new_id
}
})
}
}
unsafe impl crate::IThreadId for LiveThreadId {
#[inline]
fn current() -> Self {
<Self>::current()
}
}
impl Debug for LiveThreadId {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("LiveThreadId").field(&self.index()).finish()
}
}
impl LiveThreadId {
#[cold]
fn alloc() -> LiveThreadId {
GUARD
.try_with(|cell| {
assert!(cell.get().is_none(), "already initialized");
})
.unwrap_or_else(|_| panic!("thread already destroyed"));
let mut alloc = ThreadIdAllocator::lock();
let alloc = ThreadIdAllocator::lazy_init(&mut alloc);
let new_id = if let Some(existing) = alloc.free_list.pop() {
LiveThreadId { index: existing.0 }
} else {
let next_id = alloc.next_id.get();
alloc.next_id.set(
next_id
.get()
.checked_add(1)
.and_then(NonMaxUsize::new)
.expect("LiveThreadId overflowed a usize"),
);
LiveThreadId { index: next_id }
};
GUARD.with(|cell| {
cell.set(ThreadGuard { id: new_id })
.unwrap_or_else(|_| panic!("already initialized"));
});
new_id
}
#[inline]
#[must_use]
pub fn index(self) -> usize {
self.index.get()
}
#[inline]
#[must_use]
pub fn to_int(self) -> usize {
self.index.get()
}
}
simple_serde_serialize!(LiveThreadId, |this| this.to_int());
#[cfg(feature = "bytemuck")]
#[cfg_attr(feature = "nightly-docs", doc(cfg(feature = "bytemuck")))]
unsafe impl bytemuck::ZeroableInOption for LiveThreadId {}
#[cfg(feature = "bytemuck")]
#[cfg_attr(feature = "nightly-docs", doc(cfg(feature = "bytemuck")))]
unsafe impl bytemuck::NoUninit for LiveThreadId {}
#[cfg(feature = "slog")]
#[cfg_attr(feature = "nightly-docs", doc(cfg(feature = "slog")))]
impl slog::Value for LiveThreadId {
fn serialize(&self, _record: &slog::Record, key: slog::Key, serializer: &mut dyn slog::Serializer) -> slog::Result {
serializer.emit_arguments(key, &format_args!("{self:?}"))
}
}
fast_thread_local! {
static LIVE_ID: Cell<Option<LiveThreadId>> = Cell::new(None);
}
std::thread_local! {
static GUARD: OnceCell<ThreadGuard> = const { OnceCell::new() };
}
struct ThreadGuard {
id: LiveThreadId,
}
impl Drop for ThreadGuard {
fn drop(&mut self) {
let _ = LIVE_ID.try_with(|id| id.set(None));
let mut alloc = ThreadIdAllocator::lock();
let alloc = ThreadIdAllocator::lazy_init(&mut alloc);
alloc.free_list.push(core::cmp::Reverse(self.id.index));
}
}
static ALLOCATOR: Mutex<Option<ThreadIdAllocator>> = Mutex::new(None);
struct ThreadIdAllocator {
next_id: Cell<NonMaxUsize>,
free_list: BinaryHeap<core::cmp::Reverse<NonMaxUsize>>,
}
impl ThreadIdAllocator {
#[inline]
fn lock() -> MutexGuard<'static, Option<ThreadIdAllocator>> {
ALLOCATOR.lock()
}
#[inline]
fn lazy_init<'a>(lock: &'a mut MutexGuard<'static, Option<ThreadIdAllocator>>) -> &'a mut ThreadIdAllocator {
#[cold]
fn init() -> ThreadIdAllocator {
ThreadIdAllocator {
free_list: BinaryHeap::new(),
next_id: Cell::new(NonMaxUsize::ZERO),
}
}
lock.get_or_insert_with(init)
}
}