use core::fmt;
use crate::rt::context::{self, Unlent};
use crate::rt::shard::Shard;
use crate::rt::shard::locals::{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| match shard.and_then(Shard::locals) {
Some(locals) => self.lend(locals, key, f),
None => context::lent_back(|lent| match lent.map(Shard::locals) {
Ok(Some(locals)) => self.lend(locals, key, f),
Ok(None) | Err(Unlent::Off) => Err(ShardAccessError { why: Why::Off }),
Err(Unlent::Gone) => Err(ShardAccessError { why: Why::Gone }),
}),
})
}
#[inline(always)]
fn lend<R>(
&self,
locals: &Locals,
key: usize,
f: impl FnOnce(&T) -> R,
) -> Result<R, ShardAccessError> {
match locals.get_or_init(key, self.init) {
Ok(value) => Ok(f(value)),
Err(Missing::Making) => Err(ShardAccessError { why: Why::Making }),
}
}
}
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 {
why: Why,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Why {
Off,
Making,
Gone,
}
impl ShardAccessError {
pub fn is_initializing(&self) -> bool {
self.why == Why::Making
}
}
impl fmt::Display for ShardAccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self.why {
Why::Off => {
"this code runs on no worker, so it has no shard-local value: use it in a task"
}
Why::Making => "the shard-local value was used by its own initializer",
Why::Gone => "the runtime shut down, so this worker's shard-local value is gone",
})
}
}
impl std::error::Error for ShardAccessError {}