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        loop {
141            if read.is_some() {
142                return RwLockReadGuard::map(read, |o| o.as_ref().unwrap());
143            }
144            drop(read);
145            let mut write = lock.write();
146            if write.is_none() {
147                *write = Some((self.init)());
148                LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
149            }
150            drop(write);
151            read = lock.read();
152        }
153    }
154
155    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
156        let lock = self.app_lock();
157        let mut read = lock.try_read()?;
158        loop {
159            if read.is_some() {
160                return Some(RwLockReadGuard::map(read, |o| o.as_ref().unwrap()));
161            }
162            drop(read);
163            let mut write = lock.try_write()?;
164            if write.is_none() {
165                *write = Some((self.init)());
166                LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
167            }
168            drop(write);
169            read = lock.read();
170        }
171    }
172
173    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
174        let lock = self.app_lock();
175        let mut write = lock.write();
176        if write.is_none() {
177            *write = Some((self.init)());
178            LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
179        }
180        RwLockWriteGuard::map(write, |o| o.as_mut().unwrap())
181    }
182
183    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
184        let lock = self.app_lock();
185        let mut write = lock.try_write()?;
186        if write.is_none() {
187            *write = Some((self.init)());
188            LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
189        }
190        Some(RwLockWriteGuard::map(write, |o| o.as_mut().unwrap()))
191    }
192}
193impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalOption<T> {
194    fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
195        self.read_impl(self.value.read_recursive())
196    }
197
198    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
199        Some(self.read_impl(self.value.try_read_recursive()?))
200    }
201
202    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
203        self.write_impl(self.value.write())
204    }
205
206    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
207        Some(self.write_impl(self.value.try_write()?))
208    }
209}
210impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalConst<T> {
211    fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
212        RwLockReadGuard::map(self.value.read(), |l| l)
213    }
214
215    fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
216        Some(RwLockReadGuard::map(self.value.try_read()?, |l| l))
217    }
218
219    fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
220        RwLockWriteGuard::map(self.value.write(), |l| l)
221    }
222
223    fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
224        Some(RwLockWriteGuard::map(self.value.try_write()?, |l| l))
225    }
226}
227
228/// An app local storage.
229///
230/// This is similar to [`std::thread::LocalKey`], but works across all threads of the app.
231///
232/// Use the [`app_local!`] macro to declare a static variable in the same style as [`thread_local!`].
233///
234/// Note that in `"multi_app"` builds the app local can only be used if an app is running in the thread,
235/// if no app is running read and write **will panic**.
236///
237/// [`app_local!`]: crate::app_local!
238pub struct AppLocal<T: Send + Sync + 'static> {
239    inner: fn() -> &'static dyn AppLocalImpl<T>,
240}
241impl<T: Send + Sync + 'static> AppLocal<T> {
242    #[doc(hidden)]
243    pub const fn new(inner: fn() -> &'static dyn AppLocalImpl<T>) -> Self {
244        AppLocal { inner }
245    }
246
247    /// Read lock the value associated with the current app.
248    ///
249    /// Initializes the default value for the app if this is the first value access.
250    ///
251    /// # Panics
252    ///
253    /// Panics if no app is running in `"multi_app"` builds.
254    #[inline]
255    pub fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
256        (self.inner)().read()
257    }
258
259    /// Try read lock the value associated with the current app.
260    ///
261    /// Initializes the default value for the app if this is the first value access.
262    ///
263    /// Returns `None` if can’t acquire a read lock.
264    ///
265    /// # Panics
266    ///
267    /// Panics if no app is running in `"multi_app"` builds.
268    #[inline]
269    pub fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
270        (self.inner)().try_read()
271    }
272
273    /// Write lock the value associated with the current app.
274    ///
275    /// Initializes the default value for the app if this is the first value access.
276    ///
277    /// # Panics
278    ///
279    /// Panics if no app is running in `"multi_app"` builds.
280    #[inline]
281    pub fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
282        (self.inner)().write()
283    }
284
285    /// Try to write lock the value associated with the current app.
286    ///
287    /// Initializes the default value for the app if this is the first value access.
288    ///
289    /// Returns `None` if can’t acquire a write lock.
290    ///
291    /// # Panics
292    ///
293    /// Panics if no app is running in `"multi_app"` builds.
294    pub fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
295        (self.inner)().try_write()
296    }
297
298    /// Get a clone of the value.
299    #[inline]
300    pub fn get(&'static self) -> T
301    where
302        T: Clone,
303    {
304        self.read().clone()
305    }
306
307    /// Set the value.
308    #[inline]
309    pub fn set(&'static self, value: T) {
310        *self.write() = value;
311    }
312
313    /// Try to get a clone of the value.
314    ///
315    /// Returns `None` if can't acquire a read lock.
316    #[inline]
317    pub fn try_get(&'static self) -> Option<T>
318    where
319        T: Clone,
320    {
321        self.try_read().map(|l| l.clone())
322    }
323
324    /// Try to set the value.
325    ///
326    /// Returns `Err(value)` if can't acquire a write lock.
327    #[inline]
328    pub fn try_set(&'static self, value: T) -> Result<(), T> {
329        match self.try_write() {
330            Some(mut l) => {
331                *l = value;
332                Ok(())
333            }
334            None => Err(value),
335        }
336    }
337
338    /// Create a read lock and `map` it to a sub-value.
339    #[inline]
340    pub fn read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> MappedRwLockReadGuard<'static, O> {
341        MappedRwLockReadGuard::map(self.read(), map)
342    }
343
344    /// Try to create a read lock and `map` it to a sub-value.
345    #[inline]
346    pub fn try_read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> Option<MappedRwLockReadGuard<'static, O>> {
347        let lock = self.try_read()?;
348        Some(MappedRwLockReadGuard::map(lock, map))
349    }
350
351    /// Create a write lock and `map` it to a sub-value.
352    #[inline]
353    pub fn write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> MappedRwLockWriteGuard<'static, O> {
354        MappedRwLockWriteGuard::map(self.write(), map)
355    }
356
357    /// Try to create a write lock and `map` it to a sub-value.
358    #[inline]
359    pub fn try_write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> Option<MappedRwLockWriteGuard<'static, O>> {
360        let lock = self.try_write()?;
361        Some(MappedRwLockWriteGuard::map(lock, map))
362    }
363
364    /// Gets an ID for this local instance that is valid for the lifetime of the process.
365    ///
366    /// Note that comparing two `&'static LOCAL` pointers is incorrect, because in `"hot_reload"` builds the statics
367    /// can be different and still represent the same app local. This ID identifies the actual inner pointer.
368    pub fn id(&'static self) -> AppLocalId {
369        AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _)
370    }
371}
372impl<T: Send + Sync + 'static> PartialEq for AppLocal<T> {
373    fn eq(&self, other: &Self) -> bool {
374        let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
375        let b = AppLocalId((other.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
376        a == b
377    }
378}
379impl<T: Send + Sync + 'static> Eq for AppLocal<T> {}
380impl<T: Send + Sync + 'static> std::hash::Hash for AppLocal<T> {
381    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
382        let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
383        std::hash::Hash::hash(&a, state)
384    }
385}
386
387/// Identifies an [`AppLocal<T>`] instance.
388///
389/// Note that comparing two `&'static LOCAL` pointers is incorrect, because in `"hot_reload"` builds the statics
390/// can be different and still represent the same app local. This ID identifies the actual inner pointer, it is
391/// valid for the lifetime of the process.
392#[derive(PartialEq, Eq, Hash, Clone, Copy)]
393pub struct AppLocalId(pub(crate) usize);
394impl AppLocalId {
395    /// Get the underlying value.
396    pub fn get(self) -> usize {
397        // VarPtr depends on this being an actual pointer (must be unique against an `Arc<T>` raw pointer).
398        self.0 as _
399    }
400}
401impl fmt::Debug for AppLocalId {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        write!(f, "AppLocalId({:#x})", self.0)
404    }
405}
406
407///<span data-del-macro-root></span> Declares new app local variable.
408///
409/// An app local is a static variable that is declared using the same syntax as [`thread_local!`], but can be
410/// accessed by any thread in the app. In apps that only run once per process it compiles down to the equivalent
411/// of a `static LOCAL: RwLock<T> = const;` or `static LOCAL: RwLock<Option<T>>` that initializes on first usage. In test
412/// builds with multiple parallel apps it compiles to a switching storage that provides a different value depending on
413/// what app is running in the current thread.
414///
415/// See [`AppLocal<T>`] for more details.
416///
417/// # Multi App
418///
419/// If the crate is compiled with the `"multi_app"` feature a different internal implementation is used that supports multiple
420/// apps, either running in parallel in different threads or one after the other. This backing implementation has some small overhead,
421/// but usually you only want multiple app instances per-process when running tests.
422///
423/// The lifetime of `"multi_app"` locals is also more limited, trying to use an app-local before starting to build an app will panic,
424/// the app-local value will be dropped when the app is dropped. Without the `"multi_app"` feature the app-locals can be used at
425/// any point before or after the app lifetime, values are not explicitly dropped, just unloaded with the process.
426///
427/// # Const
428///
429/// The initialization expression can be wrapped in a `const { .. }` block, if the `"multi_app"` feature is **not** enabled
430/// a faster implementation is used that is equivalent to a direct `static LOCAL: RwLock<T>` in terms of performance.
431///
432/// Note that this syntax is available even if the `"multi_app"` feature is enabled, the expression must be const either way,
433/// but with the feature the same dynamic implementation is used.
434///
435/// Note that `const` initialization does not automatically convert the value into the static type.
436///
437/// # Examples
438///
439/// The example below declares two app locals, note that `BAR` init value automatically converts into the app local type.
440///
441/// ```
442/// # use zng_app_context::*;
443/// app_local! {
444///     /// A public documented value.
445///     pub static FOO: u8 = const { 10u8 };
446///
447///     // A private value.
448///     static BAR: String = "Into!";
449/// }
450///
451/// let app = LocalContext::start_app(AppId::new_unique());
452///
453/// assert_eq!(10, FOO.get());
454/// ```
455///
456/// Also note that an app context is started before the first use, in `multi_app` builds trying to use an app local in
457/// a thread not owned by an app panics.
458#[macro_export]
459macro_rules! app_local {
460    ($(
461        $(#[$meta:meta])*
462        $vis:vis static $IDENT:ident : $T:ty = $(const { $init_const:expr })? $($init:expr_2021)?;
463    )+) => {$(
464        $crate::app_local_impl! {
465            $(#[$meta])*
466            $vis static $IDENT: $T = $(const { $init_const })? $($init)?;
467        }
468    )+};
469}
470
471#[doc(hidden)]
472#[macro_export]
473macro_rules! app_local_impl_single {
474    (
475        $(#[$meta:meta])*
476        $vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
477    ) => {
478        $(#[$meta])*
479        $vis static $IDENT: $crate::AppLocal<$T> = {
480            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
481                $crate::hot_static! {
482                    static IMPL: $crate::AppLocalConst<$T> = $crate::AppLocalConst::new($init);
483                }
484                $crate::hot_static_ref!(IMPL)
485            }
486            $crate::AppLocal::new(s)
487        };
488    };
489    (
490        $(#[$meta:meta])*
491        $vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
492    ) => {
493        $(#[$meta])*
494        $vis static $IDENT: $crate::AppLocal<$T> = {
495            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
496                fn init() -> $T {
497                    std::convert::Into::into($init)
498                }
499                $crate::hot_static! {
500                    static IMPL: $crate::AppLocalOption<$T> = $crate::AppLocalOption::new(init);
501                }
502                $crate::hot_static_ref!(IMPL)
503            }
504            $crate::AppLocal::new(s)
505        };
506    };
507    (
508        $(#[$meta:meta])*
509        $vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
510    ) => {
511        std::compile_error!("expected `const { $expr };` or `$expr;`")
512    };
513}
514
515#[doc(hidden)]
516#[macro_export]
517macro_rules! app_local_impl_multi {
518    (
519        $(#[$meta:meta])*
520        $vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
521    ) => {
522        $(#[$meta])*
523        $vis static $IDENT: $crate::AppLocal<$T> = {
524            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
525                const fn init() -> $T {
526                    $init
527                }
528                $crate::hot_static! {
529                    static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
530                }
531                $crate::hot_static_ref!(IMPL)
532            }
533            $crate::AppLocal::new(s)
534        };
535    };
536    (
537        $(#[$meta:meta])*
538        $vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
539    ) => {
540        $(#[$meta])*
541        $vis static $IDENT: $crate::AppLocal<$T> = {
542            fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
543                fn init() -> $T {
544                    std::convert::Into::into($init)
545                }
546                $crate::hot_static! {
547                    static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
548                }
549                $crate::hot_static_ref!(IMPL)
550            }
551            $crate::AppLocal::new(s)
552        };
553    };
554    (
555        $(#[$meta:meta])*
556        $vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
557    ) => {
558        std::compile_error!("expected `const { $expr };` or `$expr;`")
559    };
560}
561
562#[cfg(feature = "multi_app")]
563#[doc(hidden)]
564pub use app_local_impl_multi as app_local_impl;
565#[cfg(not(feature = "multi_app"))]
566#[doc(hidden)]
567pub use app_local_impl_single as app_local_impl;