use std::sync::Arc;
use std::thread::LocalKey;
#[derive(Debug)]
pub struct StaticInstancePerThreadSync<T>
where
T: linked::Object + Send + Sync,
{
get_storage: fn() -> &'static LocalKey<Arc<T>>,
}
impl<T> StaticInstancePerThreadSync<T>
where
T: linked::Object + Send + Sync,
{
#[doc(hidden)]
#[must_use]
pub const fn new(get_storage: fn() -> &'static LocalKey<Arc<T>>) -> Self {
Self { get_storage }
}
#[inline]
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&Arc<T>) -> R,
{
(self.get_storage)().with(f)
}
#[must_use]
#[inline]
pub fn to_arc(&self) -> Arc<T> {
(self.get_storage)().with(Arc::clone)
}
}
#[macro_export]
macro_rules! thread_local_arc {
() => {};
($(#[$attr:meta])* $vis:vis static $NAME:ident: $t:ty = $e:expr; $($rest:tt)*) => (
$crate::thread_local_arc!($(#[$attr])* $vis static $NAME: $t = $e);
$crate::thread_local_arc!($($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 _ARC >]: ::std::sync::Arc<$t> = ::std::sync::Arc::new([< $NAME _INITIALIZER >].get()));
$(#[$attr])* $vis const $NAME: $crate::StaticInstancePerThreadSync<$t> =
$crate::StaticInstancePerThreadSync::new(move || &[< $NAME _ARC >]);
}
};
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::atomic::{self, AtomicUsize};
use std::thread;
use static_assertions::assert_impl_all;
use crate::StaticInstancePerThreadSync;
assert_impl_all!(
StaticInstancePerThreadSync<TokenCache>: UnwindSafe,
RefUnwindSafe
);
#[linked::object]
struct TokenCache {
local_value: AtomicUsize,
}
impl TokenCache {
fn new(value: usize) -> Self {
linked::new!(Self {
local_value: AtomicUsize::new(value)
})
}
fn value(&self) -> usize {
self.local_value.load(atomic::Ordering::Relaxed)
}
fn increment(&self) {
self.local_value.fetch_add(1, atomic::Ordering::Relaxed);
}
}
#[test]
fn smoke_test() {
linked::thread_local_arc! {
static BLUE_TOKEN_CACHE: TokenCache = TokenCache::new(1000);
static YELLOW_TOKEN_CACHE: TokenCache = TokenCache::new(2000);
}
assert_eq!(BLUE_TOKEN_CACHE.to_arc().value(), 1000);
assert_eq!(YELLOW_TOKEN_CACHE.to_arc().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_arc().increment();
YELLOW_TOKEN_CACHE.to_arc().increment();
assert_eq!(BLUE_TOKEN_CACHE.to_arc().value(), 1001);
assert_eq!(YELLOW_TOKEN_CACHE.to_arc().value(), 2001);
thread::spawn(move || {
assert_eq!(BLUE_TOKEN_CACHE.to_arc().value(), 1000);
assert_eq!(YELLOW_TOKEN_CACHE.to_arc().value(), 2000);
BLUE_TOKEN_CACHE.to_arc().increment();
YELLOW_TOKEN_CACHE.to_arc().increment();
assert_eq!(BLUE_TOKEN_CACHE.to_arc().value(), 1001);
assert_eq!(YELLOW_TOKEN_CACHE.to_arc().value(), 2001);
})
.join()
.unwrap();
assert_eq!(BLUE_TOKEN_CACHE.to_arc().value(), 1001);
assert_eq!(YELLOW_TOKEN_CACHE.to_arc().value(), 2001);
let blue_cache = BLUE_TOKEN_CACHE.to_arc();
let yellow_cache = YELLOW_TOKEN_CACHE.to_arc();
thread::spawn(move || {
assert_eq!(blue_cache.value(), 1001);
assert_eq!(yellow_cache.value(), 2001);
blue_cache.increment();
yellow_cache.increment();
assert_eq!(blue_cache.value(), 1002);
assert_eq!(yellow_cache.value(), 2002);
})
.join()
.unwrap();
}
}