zng_app_context/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//! App execution context.
4//!
5//! # Crate
6//!
7#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
8#![warn(unused_extern_crates)]
9#![warn(missing_docs)]
10
11use std::{any::Any, cell::RefCell, fmt, mem, sync::Arc, thread::LocalKey};
12
13mod util;
14pub use util::*;
15
16mod app_local;
17pub use app_local::*;
18
19mod context_local;
20pub use context_local::*;
21
22use parking_lot::*;
23use zng_txt::Txt;
24use zng_unique_id::unique_id_32;
25
26#[doc(hidden)]
27pub use zng_unique_id::{hot_static, hot_static_ref};
28
29unique_id_32! {
30    /// Identifies an app instance.
31    pub struct AppId;
32}
33zng_unique_id::impl_unique_id_name!(AppId);
34zng_unique_id::impl_unique_id_fmt!(AppId);
35zng_unique_id::impl_unique_id_bytemuck!(AppId);
36
37impl serde::Serialize for AppId {
38    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39    where
40        S: serde::Serializer,
41    {
42        let name = self.name();
43        if name.is_empty() {
44            use serde::ser::Error;
45            return Err(S::Error::custom("cannot serialize unnamed `AppId`"));
46        }
47        name.serialize(serializer)
48    }
49}
50impl<'de> serde::Deserialize<'de> for AppId {
51    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
52    where
53        D: serde::Deserializer<'de>,
54    {
55        let name = Txt::deserialize(deserializer)?;
56        Ok(AppId::named(name))
57    }
58}
59
60#[derive(Clone, Copy)]
61enum LocalValueKind {
62    Local,
63    Var,
64    App,
65}
66impl LocalValueKind {
67    /// Include in local captures.
68    fn include_local(self) -> bool {
69        !matches!(self, Self::Var)
70    }
71
72    /// Include in var captures.
73    fn include_var(self) -> bool {
74        !matches!(self, Self::Local)
75    }
76}
77
78/// `(value, is_context_var)`
79type LocalValue = (Arc<dyn Any + Send + Sync>, LocalValueKind);
80// equivalent to rustc_hash::FxHashMap, but can be constructed in `const`.
81type LocalData = std::collections::HashMap<AppLocalId, LocalValue, BuildFxHasher>;
82#[derive(Clone, Default)]
83struct BuildFxHasher;
84impl std::hash::BuildHasher for BuildFxHasher {
85    type Hasher = rustc_hash::FxHasher;
86
87    fn build_hasher(&self) -> Self::Hasher {
88        rustc_hash::FxHasher::default()
89    }
90}
91const fn new_local_data() -> LocalData {
92    LocalData::with_hasher(BuildFxHasher)
93}
94
95type LocalSet = std::collections::HashSet<AppLocalId, BuildFxHasher>;
96const fn new_local_set() -> LocalSet {
97    LocalSet::with_hasher(BuildFxHasher)
98}
99
100/// Represents an app lifetime, ends the app on drop.
101///
102/// You can use [`LocalContext::start_app`] to manually create an app scope without actually running an app.
103#[must_use = "ends the app scope on drop"]
104pub struct AppScope {
105    id: AppId,
106    _same_thread: std::rc::Rc<()>,
107}
108impl Drop for AppScope {
109    fn drop(&mut self) {
110        LocalContext::end_app(self.id);
111    }
112}
113
114impl AppId {
115    fn local_id() -> AppLocalId {
116        hot_static! {
117            static ID: u8 = 0;
118        }
119        AppLocalId(hot_static_ref!(ID) as *const u8 as *const () as _)
120    }
121}
122fn cleanup_list_id() -> AppLocalId {
123    hot_static! {
124        static ID: u8 = 0;
125    }
126    AppLocalId(hot_static_ref!(ID) as *const u8 as *const () as _)
127}
128
129/// Tracks the current execution context.
130///
131/// The context tracks the current app, all or some [`context_local!`] and [`TracingDispatcherContext`].
132#[derive(Clone)]
133pub struct LocalContext {
134    data: LocalData,
135    tracing: Option<tracing::dispatcher::Dispatch>,
136}
137impl fmt::Debug for LocalContext {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        let app = self
140            .data
141            .get(&AppId::local_id())
142            .map(|(v, _)| v.downcast_ref::<AppId>().unwrap())
143            .copied();
144
145        f.debug_struct("LocalContext")
146            .field("<app>", &app)
147            .field("<entries>", &(self.data.len() - 1))
148            .finish()
149    }
150}
151impl Default for LocalContext {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156impl LocalContext {
157    /// New empty context.
158    pub const fn new() -> Self {
159        Self {
160            data: new_local_data(),
161            tracing: None,
162        }
163    }
164
165    /// Start an app scope in the current thread.
166    pub fn start_app(id: AppId) -> AppScope {
167        let valid = LOCAL.with_borrow_mut_dyn(|c| match c.entry(AppId::local_id()) {
168            std::collections::hash_map::Entry::Occupied(_) => false,
169            std::collections::hash_map::Entry::Vacant(e) => {
170                e.insert((Arc::new(id), LocalValueKind::App));
171                true
172            }
173        });
174        assert!(valid, "cannot start app, another app is already in the thread context");
175
176        AppScope {
177            id,
178            _same_thread: std::rc::Rc::new(()),
179        }
180    }
181    fn end_app(id: AppId) {
182        let valid = LOCAL.with_borrow_mut_dyn(|c| {
183            if c.get(&AppId::local_id())
184                .map(|(v, _)| v.downcast_ref::<AppId>() == Some(&id))
185                .unwrap_or(false)
186            {
187                Some(mem::take(&mut *c))
188            } else {
189                None
190            }
191        });
192
193        if let Some(data) = valid {
194            // SAFETY: app resources may leak, but we terminate the process
195            let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
196                drop(data); // deinit
197            }));
198            if let Err(p) = r {
199                tracing::error!("panic on app drop. {}", panic_str(&p));
200                eprintln!("panic on app drop. {}", panic_str(&p));
201                zng_env::exit(i32::from_le_bytes(*b"appa"));
202            }
203        } else {
204            tracing::error!("can only drop app in one of its threads");
205            eprintln!("can only drop app in one of its threads");
206            zng_env::exit(i32::from_le_bytes(*b"appa"));
207        }
208    }
209
210    /// Get the ID of the app that owns the current context.
211    pub fn current_app() -> Option<AppId> {
212        LOCAL.with_borrow_dyn(|c| c.get(&AppId::local_id()).map(|(v, _)| v.downcast_ref::<AppId>().unwrap()).copied())
213    }
214
215    /// Register to run when the app deinits and all clones of the app context are dropped.
216    pub fn register_cleanup(cleanup: impl FnOnce(AppId) + Send + 'static) {
217        let id = Self::current_app().expect("no app in context");
218        Self::register_cleanup_dyn(Box::new(move || cleanup(id)));
219    }
220    fn register_cleanup_dyn(cleanup: Box<dyn FnOnce() + Send>) {
221        let cleanup = RunOnDrop::new(cleanup);
222
223        type CleanupList = Vec<RunOnDrop<Box<dyn FnOnce() + Send>>>;
224        LOCAL.with_borrow_mut_dyn(|c| {
225            let c = c
226                .entry(cleanup_list_id())
227                .or_insert_with(|| (Arc::new(Mutex::new(CleanupList::new())), LocalValueKind::App));
228            c.0.downcast_ref::<Mutex<CleanupList>>().unwrap().lock().push(cleanup);
229        });
230    }
231
232    /// Capture a snapshot of the current context that can be restored in another thread to recreate
233    /// the current context.
234    ///
235    /// Context locals modified after this capture are not included in the capture.
236    ///
237    /// This is equivalent to [``CaptureFilter::All`].
238    pub fn capture() -> Self {
239        Self {
240            data: LOCAL.with_borrow_dyn(|c| c.clone()),
241            tracing: Some(tracing::dispatcher::get_default(|d| d.clone())),
242        }
243    }
244
245    /// Capture a snapshot of the current context that only includes `filter`.
246    pub fn capture_filtered(filter: CaptureFilter) -> Self {
247        match filter {
248            CaptureFilter::None => Self::new(),
249            CaptureFilter::All => Self::capture(),
250            CaptureFilter::ContextVars { exclude } => {
251                let mut data = new_local_data();
252                LOCAL.with_borrow_dyn(|c| {
253                    for (k, (v, kind)) in c.iter() {
254                        if kind.include_var() && !exclude.0.contains(k) {
255                            data.insert(*k, (v.clone(), *kind));
256                        }
257                    }
258                });
259                Self { data, tracing: None }
260            }
261            CaptureFilter::ContextLocals { exclude } => {
262                let mut data = new_local_data();
263                LOCAL.with_borrow_dyn(|c| {
264                    for (k, (v, kind)) in c.iter() {
265                        if kind.include_local() && !exclude.0.contains(k) {
266                            data.insert(*k, (v.clone(), *kind));
267                        }
268                    }
269                });
270                Self {
271                    data,
272                    tracing: Some(tracing::dispatcher::get_default(|d| d.clone())),
273                }
274            }
275            CaptureFilter::Include(set) => {
276                let mut data = new_local_data();
277                LOCAL.with_borrow_dyn(|c| {
278                    for (k, v) in c.iter() {
279                        if set.0.contains(k) {
280                            data.insert(*k, v.clone());
281                        }
282                    }
283                });
284                Self {
285                    data,
286                    tracing: if set.contains(&TracingDispatcherContext) {
287                        Some(tracing::dispatcher::get_default(|d| d.clone()))
288                    } else {
289                        None
290                    },
291                }
292            }
293            CaptureFilter::Exclude(set) => {
294                let mut data = new_local_data();
295                LOCAL.with_borrow_dyn(|c| {
296                    for (k, v) in c.iter() {
297                        if !set.0.contains(k) {
298                            data.insert(*k, v.clone());
299                        }
300                    }
301                });
302                Self {
303                    data,
304                    tracing: if !set.contains(&TracingDispatcherContext) {
305                        Some(tracing::dispatcher::get_default(|d| d.clone()))
306                    } else {
307                        None
308                    },
309                }
310            }
311        }
312    }
313
314    /// Collects a set of all the values in the context.
315    pub fn value_set(&self) -> ContextValueSet {
316        let mut set = ContextValueSet::new();
317        LOCAL.with_borrow_dyn(|c| {
318            for k in c.keys() {
319                set.0.insert(*k);
320            }
321        });
322        set
323    }
324
325    /// Calls `f` in the captured context.
326    ///
327    /// Note that this fully replaces the parent context for the duration of the `f` call, see [`with_context_blend`]
328    /// for a blending alternative.
329    ///
330    /// [`with_context_blend`]: Self::with_context_blend
331    #[inline(always)]
332    pub fn with_context<R>(&mut self, f: impl FnOnce() -> R) -> R {
333        struct Restore<'a> {
334            prev_data: LocalData,
335            _tracing_restore: Option<tracing::dispatcher::DefaultGuard>,
336            ctx: &'a mut LocalContext,
337        }
338        impl<'a> Restore<'a> {
339            fn new(ctx: &'a mut LocalContext) -> Self {
340                let data = mem::take(&mut ctx.data);
341                Self {
342                    prev_data: LOCAL.with_borrow_mut_dyn(|c| mem::replace(c, data)),
343                    _tracing_restore: ctx.tracing.as_ref().map(tracing::dispatcher::set_default),
344                    ctx,
345                }
346            }
347        }
348        impl<'a> Drop for Restore<'a> {
349            fn drop(&mut self) {
350                self.ctx.data = LOCAL.with_borrow_mut_dyn(|c| mem::replace(c, mem::take(&mut self.prev_data)));
351            }
352        }
353        let _restore = Restore::new(self);
354
355        f()
356    }
357
358    /// Calls `f` while all contextual values of `self` are set on the parent context.
359    ///
360    /// Unlike [`with_context`] this does not remove values that are only set in the parent context, the
361    /// downside is that this call is more expensive.
362    ///
363    /// If `over` is `true` all the values of `self` are set over the parent values, if `false` only
364    /// the values not already set in the parent are set.
365    ///
366    /// [`with_context`]: Self::with_context
367    #[inline(always)]
368    pub fn with_context_blend<R>(&mut self, over: bool, f: impl FnOnce() -> R) -> R {
369        if self.data.is_empty() {
370            f()
371        } else {
372            struct Restore {
373                prev_data: LocalData,
374                _tracing_restore: Option<tracing::dispatcher::DefaultGuard>,
375            }
376            impl Restore {
377                fn new(ctx: &mut LocalContext, over: bool) -> Self {
378                    let prev_data = LOCAL.with_borrow_mut_dyn(|c| {
379                        let mut new_data = c.clone();
380                        if over {
381                            for (k, v) in &ctx.data {
382                                new_data.insert(*k, v.clone());
383                            }
384                        } else {
385                            for (k, v) in &ctx.data {
386                                new_data.entry(*k).or_insert_with(|| v.clone());
387                            }
388                        }
389
390                        mem::replace(c, new_data)
391                    });
392
393                    let mut _tracing_restore = None;
394                    if let Some(d) = &ctx.tracing
395                        && over
396                    {
397                        _tracing_restore = Some(tracing::dispatcher::set_default(d));
398                    }
399
400                    Self {
401                        prev_data,
402                        _tracing_restore,
403                    }
404                }
405            }
406            impl Drop for Restore {
407                fn drop(&mut self) {
408                    LOCAL.with_borrow_mut_dyn(|c| {
409                        *c = mem::take(&mut self.prev_data);
410                    });
411                }
412            }
413            let _restore = Restore::new(self, over);
414
415            f()
416        }
417    }
418
419    /// Blend `ctx` over `self`.
420    pub fn extend(&mut self, ctx: Self) {
421        self.data.extend(ctx.data);
422    }
423
424    fn contains(key: AppLocalId) -> bool {
425        LOCAL.with_borrow_dyn(|c| c.contains_key(&key))
426    }
427
428    fn get(key: AppLocalId) -> Option<LocalValue> {
429        LOCAL.with_borrow_dyn(|c| c.get(&key).cloned())
430    }
431
432    fn set(key: AppLocalId, value: LocalValue) -> Option<LocalValue> {
433        LOCAL.with_borrow_mut_dyn(|c| c.insert(key, value))
434    }
435    fn remove(key: AppLocalId) -> Option<LocalValue> {
436        LOCAL.with_borrow_mut_dyn(|c| c.remove(&key))
437    }
438
439    #[inline(always)]
440    fn with_value_ctx<T: Send + Sync + 'static>(
441        key: &'static ContextLocal<T>,
442        kind: LocalValueKind,
443        value: &mut Option<Arc<T>>,
444        f: impl FnOnce(),
445    ) {
446        struct Restore<'a, T: Send + Sync + 'static> {
447            key: AppLocalId,
448            prev: Option<LocalValue>,
449            value: &'a mut Option<Arc<T>>,
450        }
451        impl<'a, T: Send + Sync + 'static> Restore<'a, T> {
452            fn new(key: &'static ContextLocal<T>, kind: LocalValueKind, value: &'a mut Option<Arc<T>>) -> Self {
453                Self {
454                    key: key.id(),
455                    prev: LocalContext::set(key.id(), (value.take().expect("no `value` to set"), kind)),
456                    value,
457                }
458            }
459        }
460        impl<'a, T: Send + Sync + 'static> Drop for Restore<'a, T> {
461            fn drop(&mut self) {
462                let back = if let Some(prev) = self.prev.take() {
463                    LocalContext::set(self.key, prev)
464                } else {
465                    LocalContext::remove(self.key)
466                }
467                .unwrap();
468                *self.value = Some(Arc::downcast(back.0).unwrap());
469            }
470        }
471        let _restore = Restore::new(key, kind, value);
472
473        f()
474    }
475
476    #[inline(always)]
477    fn with_default_ctx<T: Send + Sync + 'static>(key: &'static ContextLocal<T>, f: impl FnOnce()) {
478        struct Restore {
479            key: AppLocalId,
480            prev: Option<LocalValue>,
481        }
482        impl Drop for Restore {
483            fn drop(&mut self) {
484                if let Some(prev) = self.prev.take() {
485                    LocalContext::set(self.key, prev);
486                }
487            }
488        }
489        let _restore = Restore {
490            key: key.id(),
491            prev: Self::remove(key.id()),
492        };
493
494        f()
495    }
496}
497thread_local! {
498    static LOCAL: RefCell<LocalData> = const { RefCell::new(new_local_data()) };
499}
500
501trait LocalKeyDyn {
502    fn with_borrow_dyn<R>(&'static self, f: impl FnOnce(&LocalData) -> R) -> R;
503    fn with_borrow_mut_dyn<R>(&'static self, f: impl FnOnce(&mut LocalData) -> R) -> R;
504}
505impl LocalKeyDyn for LocalKey<RefCell<LocalData>> {
506    fn with_borrow_dyn<R>(&'static self, f: impl FnOnce(&LocalData) -> R) -> R {
507        let mut r = None;
508        let f = |l: &LocalData| r = Some(f(l));
509
510        self.with_borrow(f);
511
512        r.unwrap()
513    }
514
515    fn with_borrow_mut_dyn<R>(&'static self, f: impl FnOnce(&mut LocalData) -> R) -> R {
516        let mut r = None;
517        let f = |l: &mut LocalData| r = Some(f(l));
518
519        self.with_borrow_mut(f);
520
521        r.unwrap()
522    }
523}
524
525/// Defines a [`LocalContext::capture_filtered`] filter.
526#[derive(Debug, Clone, PartialEq, Eq)]
527pub enum CaptureFilter {
528    /// Don't capture anything, equivalent of [`LocalContext::new`].
529    None,
530
531    /// Capture all [`context_local!`] values and [`TracingDispatcherContext`].
532    All,
533    /// Capture all variables not excluded, no [`context_local!`] nor [`TracingDispatcherContext`].
534    ContextVars {
535        /// Vars to not include.
536        exclude: ContextValueSet,
537    },
538    /// Capture all [`context_local!`] and [`TracingDispatcherContext`] not excluded, no context variables.
539    ContextLocals {
540        /// Locals to not include.
541        exclude: ContextValueSet,
542    },
543
544    /// Capture only this set.
545    Include(ContextValueSet),
546
547    /// Capture all except this set.
548    Exclude(ContextValueSet),
549}
550impl CaptureFilter {
551    /// Capture all variables, no [`context_local!`] nor [`TracingDispatcherContext`].
552    pub const fn context_vars() -> Self {
553        Self::ContextVars {
554            exclude: ContextValueSet::new(),
555        }
556    }
557
558    /// Capture all [`context_local!`] and [`TracingDispatcherContext`], no context variables.
559    pub const fn context_locals() -> Self {
560        Self::ContextLocals {
561            exclude: ContextValueSet::new(),
562        }
563    }
564
565    /// Only capture the [`app_local!`] and [`TracingDispatcherContext`].
566    pub fn app_only() -> Self {
567        let mut set = ContextValueSet::new();
568        set.insert_app();
569        Self::Include(set)
570    }
571}
572
573/// Provides an identifying key for a context local value.
574///
575/// Implemented by all [`ContextLocal<T>`] already, only implement this for context local thin wrappers.
576pub trait ContextLocalKeyProvider {
577    /// Gets the key.
578    fn context_local_key(&'static self) -> AppLocalId;
579}
580impl<T: Send + Sync + 'static> ContextLocalKeyProvider for ContextLocal<T> {
581    fn context_local_key(&'static self) -> AppLocalId {
582        self.id()
583    }
584}
585
586/// Represents the [`tracing::dispatcher::get_default`] dispatcher in a context value set.
587///
588/// [`tracing::dispatcher::get_default`]: https://docs.rs/tracing/latest/tracing/dispatcher/fn.get_global_default.html
589#[allow(clippy::exhaustive_structs)]
590pub struct TracingDispatcherContext;
591
592impl ContextLocalKeyProvider for TracingDispatcherContext {
593    fn context_local_key(&'static self) -> AppLocalId {
594        static ID: bool = true;
595        AppLocalId(&ID as *const bool as *const () as usize)
596    }
597}
598
599/// Identifies a selection of [`LocalContext`] values.
600#[derive(Default, Clone, PartialEq, Eq)]
601pub struct ContextValueSet(LocalSet);
602impl ContextValueSet {
603    /// New empty.
604    pub const fn new() -> Self {
605        Self(new_local_set())
606    }
607
608    /// Insert a context local.
609    pub fn insert(&mut self, value: &'static impl ContextLocalKeyProvider) -> bool {
610        self.0.insert(value.context_local_key())
611    }
612
613    /// Remove a context local.
614    pub fn remove(&mut self, value: &'static impl ContextLocalKeyProvider) -> bool {
615        self.0.remove(&value.context_local_key())
616    }
617
618    /// Checks if the context local is in the set.
619    pub fn contains(&self, value: &'static impl ContextLocalKeyProvider) -> bool {
620        self.0.contains(&value.context_local_key())
621    }
622
623    /// Number of unique values in the set.
624    pub fn len(&self) -> usize {
625        self.0.len()
626    }
627
628    /// If the set has any values.
629    pub fn is_empty(&self) -> bool {
630        self.0.is_empty()
631    }
632
633    /// Extend this set with all `other` contexts.
634    pub fn insert_all(&mut self, other: &Self) {
635        self.0.extend(other.0.iter().copied());
636    }
637
638    /// Removes all `other` contexts from this set.
639    pub fn remove_all(&mut self, other: &Self) {
640        for o in other.0.iter() {
641            self.0.remove(o);
642        }
643    }
644
645    /// Insert the [`app_local!`] ID and [`TracingDispatcherContext`].
646    pub fn insert_app(&mut self) -> bool {
647        let inserted_app = self.0.insert(AppId::local_id());
648        static TRACING: TracingDispatcherContext = TracingDispatcherContext;
649        self.insert(&TRACING) || inserted_app
650    }
651}
652impl fmt::Debug for ContextValueSet {
653    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654        f.debug_struct("ContextValueSet").field("len()", &self.len()).finish()
655    }
656}