zng_app_context/
app_local.rs

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