use std::rc::Rc;
use std::thread::LocalKey;
#[derive(Debug)]
pub struct StaticInstancePerThread<T>
where
T: linked::Object,
{
get_storage: fn() -> &'static LocalKey<Rc<T>>,
}
impl<T> StaticInstancePerThread<T>
where
T: linked::Object,
{
#[cfg_attr(coverage_nightly, coverage(off))]
#[doc(hidden)]
#[must_use]
pub const fn new(get_storage: fn() -> &'static LocalKey<Rc<T>>) -> Self {
Self { get_storage }
}
#[inline]
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&Rc<T>) -> R,
{
(self.get_storage)().with(f)
}
#[must_use]
#[inline]
pub fn to_rc(&self) -> Rc<T> {
(self.get_storage)().with(Rc::clone)
}
}
#[macro_export]
macro_rules! thread_local_rc {
() => {};
($(#[$attr:meta])* $vis:vis static $NAME:ident: $t:ty = $e:expr; $($rest:tt)*) => (
$crate::thread_local_rc!($(#[$attr])* $vis static $NAME: $t = $e);
$crate::thread_local_rc!($($rest)*);
);
($(#[$attr:meta])* $vis:vis static $NAME:ident: $t:ty = $e:expr) => {
$crate::__private::paste! {
$crate::instances!(#[doc(hidden)] static [< $NAME _INITIALIZER >]: $t = $e;);
::std::thread_local!(#[doc(hidden)] static [< $NAME _RC >]: ::std::rc::Rc<$t> = ::std::rc::Rc::new([< $NAME _INITIALIZER >].get()));
$(#[$attr])* $vis const $NAME: $crate::StaticInstancePerThread<$t> =
$crate::StaticInstancePerThread::new(move || &[< $NAME _RC >]);
}
};
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::cell::Cell;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::thread;
use static_assertions::assert_impl_all;
use crate::StaticInstancePerThread;
assert_impl_all!(
StaticInstancePerThread<TokenCache>: UnwindSafe, RefUnwindSafe
);
#[linked::object]
struct TokenCache {
local_value: Cell<usize>,
}
impl TokenCache {
fn new(value: usize) -> Self {
linked::new!(Self {
local_value: Cell::new(value)
})
}
fn value(&self) -> usize {
self.local_value.get()
}
fn increment(&self) {
self.local_value
.set(self.local_value.get().saturating_add(1));
}
}
#[test]
fn smoke_test() {
linked::thread_local_rc! {
static BLUE_TOKEN_CACHE: TokenCache = TokenCache::new(1000);
static YELLOW_TOKEN_CACHE: TokenCache = TokenCache::new(2000);
}
assert_eq!(BLUE_TOKEN_CACHE.to_rc().value(), 1000);
assert_eq!(YELLOW_TOKEN_CACHE.to_rc().value(), 2000);
BLUE_TOKEN_CACHE.with(|cache| {
assert_eq!(cache.value(), 1000);
});
YELLOW_TOKEN_CACHE.with(|cache| {
assert_eq!(cache.value(), 2000);
});
BLUE_TOKEN_CACHE.to_rc().increment();
YELLOW_TOKEN_CACHE.to_rc().increment();
assert_eq!(BLUE_TOKEN_CACHE.to_rc().value(), 1001);
assert_eq!(YELLOW_TOKEN_CACHE.to_rc().value(), 2001);
thread::spawn(move || {
assert_eq!(BLUE_TOKEN_CACHE.to_rc().value(), 1000);
assert_eq!(YELLOW_TOKEN_CACHE.to_rc().value(), 2000);
BLUE_TOKEN_CACHE.to_rc().increment();
YELLOW_TOKEN_CACHE.to_rc().increment();
assert_eq!(BLUE_TOKEN_CACHE.to_rc().value(), 1001);
assert_eq!(YELLOW_TOKEN_CACHE.to_rc().value(), 2001);
})
.join()
.unwrap();
assert_eq!(BLUE_TOKEN_CACHE.to_rc().value(), 1001);
assert_eq!(YELLOW_TOKEN_CACHE.to_rc().value(), 2001);
}
}