zng_app_context/
app_local.rs

1use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
2
3#[cfg(feature = "multi_app")]
4use crate::{AppId, LocalContext};
5
6use std::fmt;
7
8#[doc(hidden)]
9pub struct AppLocalConst<T: Send + Sync + 'static> {
10    value: RwLock<T>,
11}
12impl<T: Send + Sync + 'static> AppLocalConst<T> {
13    pub const fn new(init: T) -> Self {
14        Self { value: RwLock::new(init) }
15    }
16}
17#[doc(hidden)]
18pub struct AppLocalOption<T: Send + Sync + 'static> {
19    value: RwLock<Option<T>>,
20    init: fn() -> T,
21}
22impl<T: Send + Sync + 'static> AppLocalOption<T> {
23    pub const fn new(init: fn() -> T) -> Self {
24        Self {
25            value: RwLock::new(None),
26            init,
27        }
28    }
29
30    fn read_impl(&'static self, read: RwLockReadGuard<'static, Option<T>>) -> MappedRwLockReadGuard<'static, T> {
31        if read.is_some() {
32            return RwLockReadGuard::map(read, |v| v.as_ref().unwrap());
33        }
34        drop(read);
35
36        let mut write = self.value.write();
37        if write.is_some() {
38            drop(write);
39            return self.read();
40        }
41
42        let value = (self.init)();
43        *write = Some(value);
44
45        let read = RwLockWriteGuard::downgrade(write);
46
47        RwLockReadGuard::map(read, |v| v.as_ref().unwrap())
48    }
49
50    fn write_impl(&'static self, mut write: RwLockWriteGuard<'static, Option<T>>) -> MappedRwLockWriteGuard<'static, T> {
51        if write.is_some() {
52            return RwLockWriteGuard::map(write, |v| v.as_mut().unwrap());
53        }
54
55        let value = (self.init)();
56        *write = Some(value);
57
58        RwLockWriteGuard::map(write, |v| v.as_mut().unwrap())
59    }
60}
61
62#[doc(hidden)]
63pub struct AppLocalVec<T: Send + Sync + 'static> {
64    // we don't use a single RwLock here to avoid lock inversion deadlock between apps,
65    // but data still needs to drop on app deinit so we use an Option.
66    #[cfg(feature = "multi_app")]
67    value: append_only_vec::AppendOnlyVec<(AppId, RwLock<Option<T>>)>,
68    #[cfg(feature = "multi_app")]
69    init: fn() -> T,
70    #[cfg(not(feature = "multi_app"))]
71    _f: std::marker::PhantomData<T>,
72}
73#[cfg(feature = "multi_app")]
74impl<T: Send + Sync + 'static> AppLocalVec<T> {
75    pub const fn new(init: fn() -> T) -> Self {
76        Self {
77            value: append_only_vec::AppendOnlyVec::new(),
78            init,
79        }
80    }
81
82    fn cleanup(&'static self, id: AppId) {
83        self.try_cleanup(id, 0);
84    }
85    fn try_cleanup(&'static self, id: AppId, tries: u8) {
86        for (app, data) in self.value.iter() {
87            if *app == id {
88                let timeout = if tries == 0 {
89                    std::time::Duration::from_millis(50)
90                } else {
91                    std::time::Duration::from_millis(500)
92                };
93                if let Some(mut w) = data.try_write_for(timeout) {
94                    // drop
95                    let _ = w.take();
96                } else if tries > 5 {
97                    tracing::error!("failed to cleanup `app_local` for {id:?}, was locked after app drop");
98                } else {
99                    std::thread::Builder::new()
100                        .name("app_local_cleanup".into())
101                        .spawn(move || {
102                            self.try_cleanup(id, tries + 1);
103                        })
104                        .expect("failed to spawn thread");
105                }
106            }
107        }
108    }
109
110    fn app_lock(&'static self) -> &'static RwLock<Option<T>> {
111        let id = LocalContext::current_app().expect("no app running, `app_local` can only be accessed inside apps");
112        for (app, lock) in self.value.iter() {
113            if *app == id {
114                return lock;
115            }
116        }
117        self.value.push((id, RwLock::new(None)));
118        // ensure we always ref the first entry, in case of a race condition inserting multiple
119        for (app, lock) in self.value.iter() {
120            if *app == id {
121                return lock;
122            }
123        }
124        unreachable!()
125    }
126}
127#[doc(hidden)]
128pub trait AppLocalImpl<T: Send + Sync + 'static>: Send + Sync + 'static {
129    fn read(&'static self) -> MappedRwLockReadGuard<'static, T>;
130    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>>;
131    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T>;
132    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>>;
133}
134
135#[cfg(feature = "multi_app")]
136impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalVec<T> {
137    fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
138        let lock = self.app_lock();
139        let mut read = lock.read();
140        if read.is_none() {
141            drop(read);
142            let mut write = lock.write();
143            if write.is_none() {
144                *write = Some((self.init)());
145                LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
146            }
147            drop(write);
148            read = lock.read();
149        }
150        RwLockReadGuard::map(read, |o| o.as_ref().unwrap())
151    }
152
153    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
154        let lock = self.app_lock();
155        let mut read = lock.try_read()?;
156        if read.is_none() {
157            drop(read);
158            let mut write = lock.try_write()?;
159            if write.is_none() {
160                *write = Some((self.init)());
161                LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
162            }
163            drop(write);
164            read = lock.read();
165        }
166        Some(RwLockReadGuard::map(read, |o| o.as_ref().unwrap()))
167    }
168
169    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
170        let lock = self.app_lock();
171        let mut write = lock.write();
172        if write.is_none() {
173            *write = Some((self.init)());
174            LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
175        }
176        RwLockWriteGuard::map(write, |o| o.as_mut().unwrap())
177    }
178
179    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
180        let lock = self.app_lock();
181        let mut write = lock.try_write()?;
182        if write.is_none() {
183            *write = Some((self.init)());
184            LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
185        }
186        Some(RwLockWriteGuard::map(write, |o| o.as_mut().unwrap()))
187    }
188}
189impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalOption<T> {
190    fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
191        self.read_impl(self.value.read_recursive())
192    }
193
194    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
195        Some(self.read_impl(self.value.try_read_recursive()?))
196    }
197
198    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
199        self.write_impl(self.value.write())
200    }
201
202    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
203        Some(self.write_impl(self.value.try_write()?))
204    }
205}
206impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalConst<T> {
207    fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
208        RwLockReadGuard::map(self.value.read(), |l| l)
209    }
210
211    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
212        Some(RwLockReadGuard::map(self.value.try_read()?, |l| l))
213    }
214
215    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
216        RwLockWriteGuard::map(self.value.write(), |l| l)
217    }
218
219    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
220        Some(RwLockWriteGuard::map(self.value.try_write()?, |l| l))
221    }
222}
223
224/// An app local storage.
225///
226/// This is similar to [`std::thread::LocalKey`], but works across all threads of the app.
227///
228/// Use the [`app_local!`] macro to declare a static variable in the same style as [`thread_local!`].
229///
230/// Note that in `"multi_app"` builds the app local can only be used if an app is running in the thread,
231/// if no app is running read and write **will panic**.
232///
233/// [`app_local!`]: crate::app_local!
234pub struct AppLocal<T: Send + Sync + 'static> {
235    inner: fn() -> &'static dyn AppLocalImpl<T>,
236}
237impl<T: Send + Sync + 'static> AppLocal<T> {
238    #[doc(hidden)]
239    pub const fn new(inner: fn() -> &'static dyn AppLocalImpl<T>) -> Self {
240        AppLocal { inner }
241    }
242
243    /// Read lock the value associated with the current app.
244    ///
245    /// Initializes the default value for the app if this is the first value access.
246    ///
247    /// # Panics
248    ///
249    /// Panics if no app is running in `"multi_app"` builds.
250    #[inline]
251    pub fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
252        (self.inner)().read()
253    }
254
255    /// Try read lock the value associated with the current app.
256    ///
257    /// Initializes the default value for the app if this is the first value access.
258    ///
259    /// Returns `None` if can’t acquire a read lock.
260    ///
261    /// # Panics
262    ///
263    /// Panics if no app is running in `"multi_app"` builds.
264    #[inline]
265    pub fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
266        (self.inner)().try_read()
267    }
268
269    /// Write lock the value associated with the current app.
270    ///
271    /// Initializes the default value for the app if this is the first value access.
272    ///
273    /// # Panics
274    ///
275    /// Panics if no app is running in `"multi_app"` builds.
276    #[inline]
277    pub fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
278        (self.inner)().write()
279    }
280
281    /// Try to write lock the value associated with the current app.
282    ///
283    /// Initializes the default value for the app if this is the first value access.
284    ///
285    /// Returns `None` if can’t acquire a write lock.
286    ///
287    /// # Panics
288    ///
289    /// Panics if no app is running in `"multi_app"` builds.
290    pub fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
291        (self.inner)().try_write()
292    }
293
294    /// Get a clone of the value.
295    #[inline]
296    pub fn get(&'static self) -> T
297    where
298        T: Clone,
299    {
300        self.read().clone()
301    }
302
303    /// Set the value.
304    #[inline]
305    pub fn set(&'static self, value: T) {
306        *self.write() = value;
307    }
308
309    /// Try to get a clone of the value.
310    ///
311    /// Returns `None` if can't acquire a read lock.
312    #[inline]
313    pub fn try_get(&'static self) -> Option<T>
314    where
315        T: Clone,
316    {
317        self.try_read().map(|l| l.clone())
318    }
319
320    /// Try to set the value.
321    ///
322    /// Returns `Err(value)` if can't acquire a write lock.
323    #[inline]
324    pub fn try_set(&'static self, value: T) -> Result<(), T> {
325        match self.try_write() {
326            Some(mut l) => {
327                *l = value;
328                Ok(())
329            }
330            None => Err(value),
331        }
332    }
333
334    /// Create a read lock and `map` it to a sub-value.
335    #[inline]
336    pub fn read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> MappedRwLockReadGuard<'static, O> {
337        MappedRwLockReadGuard::map(self.read(), map)
338    }
339
340    /// Try to create a read lock and `map` it to a sub-value.
341    #[inline]
342    pub fn try_read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> Option<MappedRwLockReadGuard<'static, O>> {
343        let lock = self.try_read()?;
344        Some(MappedRwLockReadGuard::map(lock, map))
345    }
346
347    /// Create a write lock and `map` it to a sub-value.
348    #[inline]
349    pub fn write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> MappedRwLockWriteGuard<'static, O> {
350        MappedRwLockWriteGuard::map(self.write(), map)
351    }
352
353    /// Try to create a write lock and `map` it to a sub-value.
354    #[inline]
355    pub fn try_write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> Option<MappedRwLockWriteGuard<'static, O>> {
356        let lock = self.try_write()?;
357        Some(MappedRwLockWriteGuard::map(lock, map))
358    }
359
360    /// Gets an ID for this local instance that is valid for the lifetime of the process.
361    ///
362    /// Note that comparing two `&'static LOCAL` pointers is incorrect, because in `"hot_reload"` builds the statics
363    /// can be different and still represent the same app local. This ID identifies the actual inner pointer.
364    pub fn id(&'static self) -> AppLocalId {
365        AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _)
366    }
367}
368impl<T: Send + Sync + 'static> PartialEq for AppLocal<T> {
369    fn eq(&self, other: &Self) -> bool {
370        let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
371        let b = AppLocalId((other.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
372        a == b
373    }
374}
375impl<T: Send + Sync + 'static> Eq for AppLocal<T> {}
376impl<T: Send + Sync + 'static> std::hash::Hash for AppLocal<T> {
377    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
378        let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
379        std::hash::Hash::hash(&a, state)
380    }
381}
382
383/// Identifies an [`AppLocal<T>`] instance.
384///
385/// Note that comparing two `&'static LOCAL` pointers is incorrect, because in `"hot_reload"` builds the statics
386/// can be different and still represent the same app local. This ID identifies the actual inner pointer, it is
387/// valid for the lifetime of the process.
388#[derive(PartialEq, Eq, Hash, Clone, Copy)]
389pub struct AppLocalId(pub(crate) usize);
390impl AppLocalId {
391    /// Get the underlying value.
392    pub fn get(self) -> usize {
393        // VarPtr depends on this being an actual pointer (must be unique against an `Arc<T>` raw pointer).
394        self.0 as _
395    }
396}
397impl fmt::Debug for AppLocalId {
398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399        write!(f, "AppLocalId({:#x})", self.0)
400    }
401}
402
403///<span data-del-macro-root></span> Declares new app local variable.
404///
405/// An app local is a static variable that is declared using the same syntax as [`thread_local!`], but can be
406/// accessed by any thread in the app. In apps that only run once per process it compiles down to the equivalent
407/// of a `static LOCAL: RwLock<T> = const;` or `static LOCAL: RwLock<Option<T>>` that initializes on first usage. In test
408/// builds with multiple parallel apps it compiles to a switching storage that provides a different value depending on
409/// what app is running in the current thread.
410///
411/// See [`AppLocal<T>`] for more details.
412///
413/// # Multi App
414///
415/// If the crate is compiled with the `"multi_app"` feature a different internal implementation is used that supports multiple
416/// apps, either running in parallel in different threads or one after the other. This backing implementation has some small overhead,
417/// but usually you only want multiple app instances per-process when running tests.
418///
419/// The lifetime of `"multi_app"` locals is also more limited, trying to use an app-local before starting to build an app will panic,
420/// the app-local value will be dropped when the app is dropped. Without the `"multi_app"` feature the app-locals can be used at
421/// any point before or after the app lifetime, values are not explicitly dropped, just unloaded with the process.
422///
423/// # Const
424///
425/// The initialization expression can be wrapped in a `const { .. }` block, if the `"multi_app"` feature is **not** enabled
426/// a faster implementation is used that is equivalent to a direct `static LOCAL: RwLock<T>` in terms of performance.
427///
428/// Note that this syntax is available even if the `"multi_app"` feature is enabled, the expression must be const either way,
429/// but with the feature the same dynamic implementation is used.
430///
431/// Note that `const` initialization does not automatically convert the value into the static type.
432///
433/// # Examples
434///
435/// The example below declares two app locals, note that `BAR` init value automatically converts into the app local type.
436///
437/// ```
438/// # use zng_app_context::*;
439/// app_local! {
440///     /// A public documented value.
441///     pub static FOO: u8 = const { 10u8 };
442///
443///     // A private value.
444///     static BAR: String = "Into!";
445/// }
446///
447/// let app = LocalContext::start_app(AppId::new_unique());
448///
449/// assert_eq!(10, FOO.get());
450/// ```
451///
452/// Also note that an app context is started before the first use, in `multi_app` builds trying to use an app local in
453/// a thread not owned by an app panics.
454#[macro_export]
455macro_rules! app_local {
456    ($(
457        $(#[$meta:meta])*
458        $vis:vis static $IDENT:ident : $T:ty = $(const { $init_const:expr })? $($init:expr_2021)?;
459    )+) => {$(
460        $crate::app_local_impl! {
461            $(#[$meta])*
462            $vis static $IDENT: $T = $(const { $init_const })? $($init)?;
463        }
464    )+};
465}
466
467#[doc(hidden)]
468#[macro_export]
469macro_rules! app_local_impl_single {
470    (
471        $(#[$meta:meta])*
472        $vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
473    ) => {
474        $(#[$meta])*
475        $vis static $IDENT: $crate::AppLocal<$T> = {
476            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
477                $crate::hot_static! {
478                    static IMPL: $crate::AppLocalConst<$T> = $crate::AppLocalConst::new($init);
479                }
480                $crate::hot_static_ref!(IMPL)
481            }
482            $crate::AppLocal::new(s)
483        };
484    };
485    (
486        $(#[$meta:meta])*
487        $vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
488    ) => {
489        $(#[$meta])*
490        $vis static $IDENT: $crate::AppLocal<$T> = {
491            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
492                fn init() -> $T {
493                    std::convert::Into::into($init)
494                }
495                $crate::hot_static! {
496                    static IMPL: $crate::AppLocalOption<$T> = $crate::AppLocalOption::new(init);
497                }
498                $crate::hot_static_ref!(IMPL)
499            }
500            $crate::AppLocal::new(s)
501        };
502    };
503    (
504        $(#[$meta:meta])*
505        $vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
506    ) => {
507        std::compile_error!("expected `const { $expr };` or `$expr;`")
508    };
509}
510
511#[doc(hidden)]
512#[macro_export]
513macro_rules! app_local_impl_multi {
514    (
515        $(#[$meta:meta])*
516        $vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
517    ) => {
518        $(#[$meta])*
519        $vis static $IDENT: $crate::AppLocal<$T> = {
520            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
521                const fn init() -> $T {
522                    $init
523                }
524                $crate::hot_static! {
525                    static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
526                }
527                $crate::hot_static_ref!(IMPL)
528            }
529            $crate::AppLocal::new(s)
530        };
531    };
532    (
533        $(#[$meta:meta])*
534        $vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
535    ) => {
536        $(#[$meta])*
537        $vis static $IDENT: $crate::AppLocal<$T> = {
538            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
539                fn init() -> $T {
540                    std::convert::Into::into($init)
541                }
542                $crate::hot_static! {
543                    static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
544                }
545                $crate::hot_static_ref!(IMPL)
546            }
547            $crate::AppLocal::new(s)
548        };
549    };
550    (
551        $(#[$meta:meta])*
552        $vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
553    ) => {
554        std::compile_error!("expected `const { $expr };` or `$expr;`")
555    };
556}
557
558#[cfg(feature = "multi_app")]
559#[doc(hidden)]
560pub use app_local_impl_multi as app_local_impl;
561#[cfg(not(feature = "multi_app"))]
562#[doc(hidden)]
563pub use app_local_impl_single as app_local_impl;