use core::fmt;
use crate::rt::context;
use crate::rt::shard::locals::Missing;
use core::sync::atomic::{AtomicUsize, Ordering};
#[macro_export]
macro_rules! shard_local {
() => {};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => {
$(#[$attr])*
$vis static $name: $crate::runtime::ShardLocal<$t> =
$crate::runtime::ShardLocal::__new(|| $init);
$crate::shard_local!($($rest)*);
};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => {
$(#[$attr])*
$vis static $name: $crate::runtime::ShardLocal<$t> =
$crate::runtime::ShardLocal::__new(|| $init);
};
}
pub struct ShardLocal<T: Send + 'static> {
init: fn() -> T,
key: AtomicUsize,
}
static KEYS: AtomicUsize = AtomicUsize::new(0);
impl<T: Send + 'static> ShardLocal<T> {
#[doc(hidden)]
pub const fn __new(init: fn() -> T) -> Self {
Self {
init,
key: AtomicUsize::new(0),
}
}
fn key(&self) -> usize {
let key = self.key.load(Ordering::Acquire);
if key != 0 {
return key - 1;
}
let mine = KEYS.fetch_add(1, Ordering::Relaxed) + 1;
match self
.key
.compare_exchange(0, mine, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => mine - 1,
Err(won) => won - 1,
}
}
#[expect(
clippy::panic,
reason = "documented under `# Panics`; `try_with` answers the error instead"
)]
pub fn with<R>(&'static self, f: impl FnOnce(&T) -> R) -> R {
match self.try_with(f) {
Ok(r) => r,
Err(err) => panic!("{err}"),
}
}
pub fn try_with<R>(&'static self, f: impl FnOnce(&T) -> R) -> Result<R, ShardAccessError> {
let key = self.key();
context::current_pinned(|shard| {
let locals = shard
.and_then(|shard| shard.locals())
.ok_or(ShardAccessError { making: false })?;
match locals.get_or_init(key, self.init) {
Ok(value) => Ok(f(value)),
Err(Missing::Making) => Err(ShardAccessError { making: true }),
}
})
}
}
impl<T: Send + 'static> fmt::Debug for ShardLocal<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ShardLocal").finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ShardAccessError {
making: bool,
}
impl ShardAccessError {
pub fn is_initializing(&self) -> bool {
self.making
}
}
impl fmt::Display for ShardAccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(if self.making {
"the shard-local value was used by its own initializer"
} else {
"this code runs on no worker, so it has no shard-local value: use it in a task"
})
}
}
impl std::error::Error for ShardAccessError {}