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