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    pub fn with_context<R>(&mut self, f: impl FnOnce() -> R) -> R {
332        let data = mem::take(&mut self.data);
333        let prev = LOCAL.with_borrow_mut_dyn(|c| mem::replace(c, data));
334        let _tracing_restore = self.tracing.as_ref().map(tracing::dispatcher::set_default);
335        let _restore = RunOnDrop::new(|| {
336            self.data = LOCAL.with_borrow_mut_dyn(|c| mem::replace(c, prev));
337        });
338        f()
339    }
340
341    /// Calls `f` while all contextual values of `self` are set on the parent context.
342    ///
343    /// Unlike [`with_context`] this does not remove values that are only set in the parent context, the
344    /// downside is that this call is more expensive.
345    ///
346    /// If `over` is `true` all the values of `self` are set over the parent values, if `false` only
347    /// the values not already set in the parent are set.
348    ///
349    /// [`with_context`]: Self::with_context
350    pub fn with_context_blend<R>(&mut self, over: bool, f: impl FnOnce() -> R) -> R {
351        if self.data.is_empty() {
352            f()
353        } else {
354            let prev = LOCAL.with_borrow_mut_dyn(|c| {
355                let (mut base, over) = if over { (c.clone(), &self.data) } else { (self.data.clone(), &*c) };
356                for (k, v) in over {
357                    base.insert(*k, v.clone());
358                }
359
360                mem::replace(c, base)
361            });
362
363            let mut _tracing_restore = None;
364            if let Some(d) = &self.tracing {
365                if over {
366                    _tracing_restore = Some(tracing::dispatcher::set_default(d));
367                }
368            }
369
370            let _restore = RunOnDrop::new(|| {
371                LOCAL.with_borrow_mut_dyn(|c| {
372                    *c = prev;
373                });
374            });
375
376            f()
377        }
378    }
379
380    /// Blend `ctx` over `self`.
381    pub fn extend(&mut self, ctx: Self) {
382        self.data.extend(ctx.data);
383    }
384
385    fn contains(key: AppLocalId) -> bool {
386        LOCAL.with_borrow_dyn(|c| c.contains_key(&key))
387    }
388
389    fn get(key: AppLocalId) -> Option<LocalValue> {
390        LOCAL.with_borrow_dyn(|c| c.get(&key).cloned())
391    }
392
393    fn set(key: AppLocalId, value: LocalValue) -> Option<LocalValue> {
394        LOCAL.with_borrow_mut_dyn(|c| c.insert(key, value))
395    }
396    fn remove(key: AppLocalId) -> Option<LocalValue> {
397        LOCAL.with_borrow_mut_dyn(|c| c.remove(&key))
398    }
399
400    fn with_value_ctx<T: Send + Sync + 'static>(
401        key: &'static ContextLocal<T>,
402        kind: LocalValueKind,
403        value: &mut Option<Arc<T>>,
404        f: impl FnOnce(),
405    ) {
406        let key = key.id();
407        let prev = Self::set(key, (value.take().expect("no `value` to set"), kind));
408        let _restore = RunOnDrop::new(move || {
409            let back = if let Some(prev) = prev {
410                Self::set(key, prev)
411            } else {
412                Self::remove(key)
413            }
414            .unwrap();
415            *value = Some(Arc::downcast(back.0).unwrap());
416        });
417
418        f();
419    }
420
421    fn with_default_ctx<T: Send + Sync + 'static>(key: &'static ContextLocal<T>, f: impl FnOnce()) {
422        let key = key.id();
423        let prev = Self::remove(key);
424        let _restore = RunOnDrop::new(move || {
425            if let Some(prev) = prev {
426                Self::set(key, prev);
427            }
428        });
429
430        f()
431    }
432}
433thread_local! {
434    static LOCAL: RefCell<LocalData> = const { RefCell::new(new_local_data()) };
435}
436
437trait LocalKeyDyn {
438    fn with_borrow_dyn<R>(&'static self, f: impl FnOnce(&LocalData) -> R) -> R;
439    fn with_borrow_mut_dyn<R>(&'static self, f: impl FnOnce(&mut LocalData) -> R) -> R;
440}
441impl LocalKeyDyn for LocalKey<RefCell<LocalData>> {
442    fn with_borrow_dyn<R>(&'static self, f: impl FnOnce(&LocalData) -> R) -> R {
443        let mut r = None;
444        let f = |l: &LocalData| r = Some(f(l));
445
446        #[cfg(feature = "dyn_closure")]
447        let f: Box<dyn FnOnce(&LocalData)> = Box::new(f);
448
449        self.with_borrow(f);
450
451        r.unwrap()
452    }
453
454    fn with_borrow_mut_dyn<R>(&'static self, f: impl FnOnce(&mut LocalData) -> R) -> R {
455        let mut r = None;
456        let f = |l: &mut LocalData| r = Some(f(l));
457
458        #[cfg(feature = "dyn_closure")]
459        let f: Box<dyn FnOnce(&mut LocalData)> = Box::new(f);
460
461        self.with_borrow_mut(f);
462
463        r.unwrap()
464    }
465}
466
467/// Defines a [`LocalContext::capture_filtered`] filter.
468#[derive(Debug, Clone, PartialEq, Eq)]
469pub enum CaptureFilter {
470    /// Don't capture anything, equivalent of [`LocalContext::new`].
471    None,
472
473    /// Capture all [`context_local!`] values and [`TracingDispatcherContext`].
474    All,
475    /// Capture all variables not excluded, no [`context_local!`] nor [`TracingDispatcherContext`].
476    ContextVars {
477        /// Vars to not include.
478        exclude: ContextValueSet,
479    },
480    /// Capture all [`context_local!`] and [`TracingDispatcherContext`] not excluded, no context variables.
481    ContextLocals {
482        /// Locals to not include.
483        exclude: ContextValueSet,
484    },
485
486    /// Capture only this set.
487    Include(ContextValueSet),
488
489    /// Capture all except this set.
490    Exclude(ContextValueSet),
491}
492impl CaptureFilter {
493    /// Capture all variables, no [`context_local!`] nor [`TracingDispatcherContext`].
494    pub const fn context_vars() -> Self {
495        Self::ContextVars {
496            exclude: ContextValueSet::new(),
497        }
498    }
499
500    /// Capture all [`context_local!`] and [`TracingDispatcherContext`], no context variables.
501    pub const fn context_locals() -> Self {
502        Self::ContextLocals {
503            exclude: ContextValueSet::new(),
504        }
505    }
506
507    /// Only capture the [`app_local!`] and [`TracingDispatcherContext`].
508    pub fn app_only() -> Self {
509        let mut set = ContextValueSet::new();
510        set.insert_app();
511        Self::Include(set)
512    }
513}
514
515/// Provides an identifying key for a context local value.
516///
517/// Implemented by all [`ContextLocal<T>`] already, only implement this for context local thin wrappers.
518pub trait ContextLocalKeyProvider {
519    /// Gets the key.
520    fn context_local_key(&'static self) -> AppLocalId;
521}
522impl<T: Send + Sync + 'static> ContextLocalKeyProvider for ContextLocal<T> {
523    fn context_local_key(&'static self) -> AppLocalId {
524        self.id()
525    }
526}
527
528/// Represents the [`tracing::dispatcher::get_default`] dispatcher in a context value set.
529///
530/// [`tracing::dispatcher::get_default`]: https://docs.rs/tracing/latest/tracing/dispatcher/fn.get_global_default.html
531#[allow(clippy::exhaustive_structs)]
532pub struct TracingDispatcherContext;
533
534impl ContextLocalKeyProvider for TracingDispatcherContext {
535    fn context_local_key(&'static self) -> AppLocalId {
536        static ID: bool = true;
537        AppLocalId(&ID as *const bool as *const () as usize)
538    }
539}
540
541/// Identifies a selection of [`LocalContext`] values.
542#[derive(Default, Clone, PartialEq, Eq)]
543pub struct ContextValueSet(LocalSet);
544impl ContextValueSet {
545    /// New empty.
546    pub const fn new() -> Self {
547        Self(new_local_set())
548    }
549
550    /// Insert a context local.
551    pub fn insert(&mut self, value: &'static impl ContextLocalKeyProvider) -> bool {
552        self.0.insert(value.context_local_key())
553    }
554
555    /// Remove a context local.
556    pub fn remove(&mut self, value: &'static impl ContextLocalKeyProvider) -> bool {
557        self.0.remove(&value.context_local_key())
558    }
559
560    /// Checks if the context local is in the set.
561    pub fn contains(&self, value: &'static impl ContextLocalKeyProvider) -> bool {
562        self.0.contains(&value.context_local_key())
563    }
564
565    /// Number of unique values in the set.
566    pub fn len(&self) -> usize {
567        self.0.len()
568    }
569
570    /// If the set has any values.
571    pub fn is_empty(&self) -> bool {
572        self.0.is_empty()
573    }
574
575    /// Extend this set with all `other` contexts.
576    pub fn insert_all(&mut self, other: &Self) {
577        self.0.extend(other.0.iter().copied());
578    }
579
580    /// Removes all `other` contexts from this set.
581    pub fn remove_all(&mut self, other: &Self) {
582        for o in other.0.iter() {
583            self.0.remove(o);
584        }
585    }
586
587    /// Insert the [`app_local!`] ID and [`TracingDispatcherContext`].
588    pub fn insert_app(&mut self) -> bool {
589        let inserted_app = self.0.insert(AppId::local_id());
590        static TRACING: TracingDispatcherContext = TracingDispatcherContext;
591        self.insert(&TRACING) || inserted_app
592    }
593}
594impl fmt::Debug for ContextValueSet {
595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596        f.debug_struct("ContextValueSet").field("len()", &self.len()).finish()
597    }
598}