use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(feature = "multi_app")]
use crate::{AppId, LocalContext};
use std::fmt;
#[doc(hidden)]
pub struct AppLocalConst<T: Send + Sync + 'static> {
value: RwLock<T>,
}
impl<T: Send + Sync + 'static> AppLocalConst<T> {
pub const fn new(init: T) -> Self {
Self { value: RwLock::new(init) }
}
}
#[doc(hidden)]
pub struct AppLocalOption<T: Send + Sync + 'static> {
value: RwLock<Option<T>>,
init: fn() -> T,
}
impl<T: Send + Sync + 'static> AppLocalOption<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
value: RwLock::new(None),
init,
}
}
fn read_impl(&'static self, read: RwLockReadGuard<'static, Option<T>>) -> MappedRwLockReadGuard<'static, T> {
if read.is_some() {
return RwLockReadGuard::map(read, |v| v.as_ref().unwrap());
}
drop(read);
let mut write = self.value.write();
if write.is_some() {
drop(write);
return self.read();
}
let value = (self.init)();
*write = Some(value);
let read = RwLockWriteGuard::downgrade(write);
RwLockReadGuard::map(read, |v| v.as_ref().unwrap())
}
fn write_impl(&'static self, mut write: RwLockWriteGuard<'static, Option<T>>) -> MappedRwLockWriteGuard<'static, T> {
if write.is_some() {
return RwLockWriteGuard::map(write, |v| v.as_mut().unwrap());
}
let value = (self.init)();
*write = Some(value);
RwLockWriteGuard::map(write, |v| v.as_mut().unwrap())
}
}
#[doc(hidden)]
pub struct AppLocalVec<T: Send + Sync + 'static> {
#[cfg(feature = "multi_app")]
value: append_only_vec::AppendOnlyVec<(AppId, RwLock<Option<T>>)>,
#[cfg(feature = "multi_app")]
init: fn() -> T,
#[cfg(not(feature = "multi_app"))]
_f: std::marker::PhantomData<T>,
}
#[cfg(feature = "multi_app")]
impl<T: Send + Sync + 'static> AppLocalVec<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
value: append_only_vec::AppendOnlyVec::new(),
init,
}
}
fn cleanup(&'static self, id: AppId) {
self.try_cleanup(id, 0);
}
fn try_cleanup(&'static self, id: AppId, tries: u8) {
for (app, data) in self.value.iter() {
if *app == id {
let timeout = if tries == 0 {
std::time::Duration::from_millis(50)
} else {
std::time::Duration::from_millis(500)
};
if let Some(mut w) = data.try_write_for(timeout) {
let _ = w.take();
} else if tries > 5 {
tracing::error!("failed to cleanup `app_local` for {id:?}, was locked after app drop");
} else {
std::thread::Builder::new()
.name("app_local_cleanup".into())
.spawn(move || {
self.try_cleanup(id, tries + 1);
})
.expect("failed to spawn thread");
}
}
}
}
fn app_lock(&'static self) -> &'static RwLock<Option<T>> {
let id = LocalContext::current_app().expect("no app running, `app_local` can only be accessed inside apps");
for (app, lock) in self.value.iter() {
if *app == id {
return lock;
}
}
self.value.push((id, RwLock::new(None)));
for (app, lock) in self.value.iter() {
if *app == id {
return lock;
}
}
unreachable!()
}
}
#[doc(hidden)]
pub trait AppLocalImpl<T: Send + Sync + 'static>: Send + Sync + 'static {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T>;
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>>;
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T>;
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>>;
}
#[cfg(feature = "multi_app")]
impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalVec<T> {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
let lock = self.app_lock();
let mut read = lock.read();
if read.is_none() {
drop(read);
let mut write = lock.write();
if write.is_none() {
*write = Some((self.init)());
LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
}
drop(write);
read = lock.read();
}
RwLockReadGuard::map(read, |o| o.as_ref().unwrap())
}
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
let lock = self.app_lock();
let mut read = lock.try_read()?;
if read.is_none() {
drop(read);
let mut write = lock.try_write()?;
if write.is_none() {
*write = Some((self.init)());
LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
}
drop(write);
read = lock.read();
}
Some(RwLockReadGuard::map(read, |o| o.as_ref().unwrap()))
}
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
let lock = self.app_lock();
let mut write = lock.write();
if write.is_none() {
*write = Some((self.init)());
LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
}
RwLockWriteGuard::map(write, |o| o.as_mut().unwrap())
}
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
let lock = self.app_lock();
let mut write = lock.try_write()?;
if write.is_none() {
*write = Some((self.init)());
LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
}
Some(RwLockWriteGuard::map(write, |o| o.as_mut().unwrap()))
}
}
impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalOption<T> {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
self.read_impl(self.value.read_recursive())
}
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
Some(self.read_impl(self.value.try_read_recursive()?))
}
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
self.write_impl(self.value.write())
}
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
Some(self.write_impl(self.value.try_write()?))
}
}
impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalConst<T> {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
RwLockReadGuard::map(self.value.read(), |l| l)
}
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
Some(RwLockReadGuard::map(self.value.try_read()?, |l| l))
}
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
RwLockWriteGuard::map(self.value.write(), |l| l)
}
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
Some(RwLockWriteGuard::map(self.value.try_write()?, |l| l))
}
}
pub struct AppLocal<T: Send + Sync + 'static> {
inner: fn() -> &'static dyn AppLocalImpl<T>,
}
impl<T: Send + Sync + 'static> AppLocal<T> {
#[doc(hidden)]
pub const fn new(inner: fn() -> &'static dyn AppLocalImpl<T>) -> Self {
AppLocal { inner }
}
#[inline]
pub fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
(self.inner)().read()
}
#[inline]
pub fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
(self.inner)().try_read()
}
#[inline]
pub fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
(self.inner)().write()
}
pub fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
(self.inner)().try_write()
}
#[inline]
pub fn get(&'static self) -> T
where
T: Clone,
{
self.read().clone()
}
#[inline]
pub fn set(&'static self, value: T) {
*self.write() = value;
}
#[inline]
pub fn try_get(&'static self) -> Option<T>
where
T: Clone,
{
self.try_read().map(|l| l.clone())
}
#[inline]
pub fn try_set(&'static self, value: T) -> Result<(), T> {
match self.try_write() {
Some(mut l) => {
*l = value;
Ok(())
}
None => Err(value),
}
}
#[inline]
pub fn read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> MappedRwLockReadGuard<'static, O> {
MappedRwLockReadGuard::map(self.read(), map)
}
#[inline]
pub fn try_read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> Option<MappedRwLockReadGuard<'static, O>> {
let lock = self.try_read()?;
Some(MappedRwLockReadGuard::map(lock, map))
}
#[inline]
pub fn write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> MappedRwLockWriteGuard<'static, O> {
MappedRwLockWriteGuard::map(self.write(), map)
}
#[inline]
pub fn try_write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> Option<MappedRwLockWriteGuard<'static, O>> {
let lock = self.try_write()?;
Some(MappedRwLockWriteGuard::map(lock, map))
}
pub fn id(&'static self) -> AppLocalId {
AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _)
}
}
impl<T: Send + Sync + 'static> PartialEq for AppLocal<T> {
fn eq(&self, other: &Self) -> bool {
let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
let b = AppLocalId((other.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
a == b
}
}
impl<T: Send + Sync + 'static> Eq for AppLocal<T> {}
impl<T: Send + Sync + 'static> std::hash::Hash for AppLocal<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
std::hash::Hash::hash(&a, state)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct AppLocalId(pub(crate) usize);
impl AppLocalId {
pub fn get(self) -> usize {
self.0 as _
}
}
impl fmt::Debug for AppLocalId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AppLocalId({:#x})", self.0)
}
}
#[macro_export]
macro_rules! app_local {
($(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = $(const { $init_const:expr })? $($init:expr_2021)?;
)+) => {$(
$crate::app_local_impl! {
$(#[$meta])*
$vis static $IDENT: $T = $(const { $init_const })? $($init)?;
}
)+};
}
#[doc(hidden)]
#[macro_export]
macro_rules! app_local_impl_single {
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
$crate::hot_static! {
static IMPL: $crate::AppLocalConst<$T> = $crate::AppLocalConst::new($init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
fn init() -> $T {
std::convert::Into::into($init)
}
$crate::hot_static! {
static IMPL: $crate::AppLocalOption<$T> = $crate::AppLocalOption::new(init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
) => {
std::compile_error!("expected `const { $expr };` or `$expr;`")
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! app_local_impl_multi {
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
const fn init() -> $T {
$init
}
$crate::hot_static! {
static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
fn init() -> $T {
std::convert::Into::into($init)
}
$crate::hot_static! {
static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
) => {
std::compile_error!("expected `const { $expr };` or `$expr;`")
};
}
#[cfg(feature = "multi_app")]
#[doc(hidden)]
pub use app_local_impl_multi as app_local_impl;
#[cfg(not(feature = "multi_app"))]
#[doc(hidden)]
pub use app_local_impl_single as app_local_impl;