Skip to main content

dialcache/
scope.rs

1//! Enabled scopes and request memoization.
2
3use std::collections::HashMap;
4use std::fmt;
5use std::sync::Arc;
6
7use parking_lot::Mutex;
8
9use crate::flight::Flight;
10use crate::local::StoredValue;
11
12pub(crate) struct OwnerState {
13    pub(crate) live: bool,
14    pub(crate) memo: HashMap<String, StoredValue>,
15    pub(crate) flights: HashMap<String, Arc<Flight>>,
16}
17
18/// The outermost enabled scope's request state: memo and registered request flights.
19pub(crate) struct Owner {
20    pub(crate) state: Mutex<OwnerState>,
21}
22
23impl fmt::Debug for Owner {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        f.debug_struct("Owner")
26            .field("live", &self.is_live())
27            .finish()
28    }
29}
30
31impl Owner {
32    pub(crate) fn new() -> Arc<Owner> {
33        Arc::new(Owner {
34            state: Mutex::new(OwnerState {
35                live: true,
36                memo: HashMap::new(),
37                flights: HashMap::new(),
38            }),
39        })
40    }
41
42    pub(crate) fn is_live(&self) -> bool {
43        self.state.lock().live
44    }
45
46    pub(crate) fn close(&self) {
47        let retired = {
48            let mut state = self.state.lock();
49            state.live = false;
50            (
51                std::mem::take(&mut state.memo),
52                std::mem::take(&mut state.flights),
53            )
54        };
55        // Memoized values drop here, outside the lock: a value's destructor
56        // may call back into the cache.
57        drop(retired);
58    }
59}
60
61/// A caching scope handle.
62///
63/// Caching is disabled by default. [`DialCache::enable`](crate::DialCache::enable)
64/// opens the outermost enabled scope and hands its `Scope` to the callback;
65/// pass it to every cached call made on behalf of that request. Nested
66/// [`DialCache::enable_in`](crate::DialCache::enable_in) and
67/// [`DialCache::disable_in`](crate::DialCache::disable_in) derive child
68/// scopes that share the outer request memo. When the outermost callback
69/// completes its scope closes: retained clones no longer enable caching and
70/// late work cannot publish into the request memo.
71///
72/// [`Scope::outside`] is the pass-through scope of code that runs on behalf of
73/// no request: calls made with it invoke their source directly.
74#[derive(Clone)]
75pub struct Scope {
76    pub(crate) cache_id: u64,
77    pub(crate) owner: Option<Arc<Owner>>,
78    pub(crate) enabled: bool,
79}
80
81impl fmt::Debug for Scope {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        f.debug_struct("Scope")
84            .field("cache_id", &self.cache_id)
85            .field("enabled", &self.is_enabled())
86            .field("has_owner", &self.owner.is_some())
87            .finish()
88    }
89}
90
91impl Scope {
92    /// The scope of work that runs on behalf of no request. Calls pass
93    /// straight through to their source.
94    pub fn outside() -> Scope {
95        Scope {
96            cache_id: 0,
97            owner: None,
98            enabled: false,
99        }
100    }
101
102    /// Whether caching is enabled for calls made with this scope: it was
103    /// derived from an enabled scope and its outermost scope is still open.
104    pub fn is_enabled(&self) -> bool {
105        self.enabled && self.owner.as_ref().is_some_and(|owner| owner.is_live())
106    }
107
108    pub(crate) fn live_owner(&self) -> Option<Arc<Owner>> {
109        self.owner.as_ref().filter(|owner| owner.is_live()).cloned()
110    }
111
112    pub(crate) fn disabled_view(&self) -> Scope {
113        Scope {
114            cache_id: self.cache_id,
115            owner: self.owner.clone(),
116            enabled: false,
117        }
118    }
119}