Skip to main content

gam_runtime/warm_start/
configured.rs

1//! Explicit, lazily opened warm-start storage.
2//!
3//! A configured store never discovers a root from process-global state. The
4//! caller supplies the exact path, and clones share the one open decision and
5//! the resulting [`WarmStartStore`]. Opening is lazy so parsing or validating a
6//! fit request does not create directories.
7
8use super::{Fingerprint, Session, StoreError, StoreOptions, WarmStartStore};
9use std::path::{Path, PathBuf};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::{Arc, OnceLock};
12
13/// A best-effort warm-start store rooted at an explicit caller-owned path.
14///
15/// Persistence is an optimization, so an unavailable filesystem never makes a
16/// fit fail. The first attempted use opens the exact configured root and records
17/// either the store or its permanent absence for this fit. Every clone observes
18/// that same decision, preventing repeated directory scans and repeated
19/// diagnostics.
20#[derive(Clone, Debug)]
21pub struct ConfiguredWarmStartStore {
22    root: PathBuf,
23    options: StoreOptions,
24    opened: Arc<OnceLock<Option<WarmStartStore>>>,
25    available: Arc<AtomicBool>,
26}
27
28impl ConfiguredWarmStartStore {
29    /// Configure a store without touching the filesystem.
30    pub fn new(root: PathBuf, options: StoreOptions) -> Self {
31        Self {
32            root,
33            options,
34            opened: Arc::new(OnceLock::new()),
35            available: Arc::new(AtomicBool::new(true)),
36        }
37    }
38
39    /// The exact root supplied by the caller. No canonicalization, joining, or
40    /// environment-dependent relocation is performed.
41    pub fn root(&self) -> &Path {
42        &self.root
43    }
44
45    /// Return the shared opened store, or `None` when persistence is
46    /// unavailable.
47    ///
48    /// This is the sole owner of the open-failure decision and diagnostic.
49    /// Failures are memoized: a fit proceeds cold and never repeatedly probes a
50    /// root that already refused the configured store.
51    pub fn store(&self) -> Option<&WarmStartStore> {
52        if !self.available.load(Ordering::Relaxed) {
53            return None;
54        }
55        self.opened
56            .get_or_init(
57                || match WarmStartStore::open(self.root.clone(), self.options.clone()) {
58                    Ok(store) => {
59                        log::info!(
60                            "[warm-start-cache] opened explicit root={}",
61                            self.root.display()
62                        );
63                        Some(store)
64                    }
65                    Err(error) => {
66                        self.mark_unavailable("open", &error);
67                        None
68                    }
69                },
70            )
71            .as_ref()
72    }
73
74    /// Open a keyed session governed by this capability's shared availability
75    /// decision.
76    ///
77    /// Session reads and writes report filesystem refusal back to this owner,
78    /// so one failed outer-iterate checkpoint disables every record, artifact,
79    /// and session operation belonging to the fit.
80    pub fn open_session(&self, key: Fingerprint) -> Option<Arc<Session>> {
81        let store = self.store()?.clone();
82        Some(Arc::new(Session::open_configured(store, key, self.clone())))
83    }
84
85    pub(crate) fn is_available(&self) -> bool {
86        self.available.load(Ordering::Relaxed)
87    }
88
89    pub(crate) fn record_store_error(&self, operation: &str, error: &StoreError) {
90        match error {
91            StoreError::Io(error) => self.mark_unavailable(operation, error),
92            StoreError::Json(error) => {
93                log::warn!(
94                    "[warm-start-cache] persistence defect operation={} explicit_root={}: {}",
95                    operation,
96                    self.root.display(),
97                    error
98                );
99            }
100        }
101    }
102
103    /// Permanently disable this fit's configured persistence after the
104    /// filesystem refuses an operation.
105    ///
106    /// The first refusal owns the sole diagnostic; subsequent loads/stores and
107    /// every clone become cold no-ops. Callers should use this only for
108    /// environmental I/O failures, not serialization or contract defects.
109    pub fn mark_unavailable(&self, operation: &str, error: &dyn std::fmt::Display) {
110        if self.available.swap(false, Ordering::Relaxed) {
111            log::warn!(
112                "[warm-start-cache] persistence unavailable operation={} explicit_root={}: {}; \
113                 continuing without on-disk warm starts",
114                operation,
115                self.root.display(),
116                error
117            );
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use std::time::Duration;
126
127    fn options() -> StoreOptions {
128        StoreOptions {
129            size_budget_bytes: 1024 * 1024,
130            ttl: Duration::from_secs(60),
131        }
132    }
133
134    #[test]
135    fn explicit_configuration_is_lazy_and_uses_the_exact_root() {
136        let parent = tempfile::tempdir().expect("create isolated parent");
137        let root = parent.path().join("caller-chosen").join("warm");
138        let configured = ConfiguredWarmStartStore::new(root.clone(), options());
139
140        assert_eq!(configured.root(), root);
141        assert!(
142            !root.exists(),
143            "configuration parsing must not create the store root"
144        );
145
146        let opened = configured.store().expect("explicit root must open");
147        assert_eq!(opened.root(), root);
148        assert!(root.is_dir());
149        let cloned = configured.clone();
150        assert!(
151            std::ptr::eq(opened, cloned.store().expect("clone shares opened store")),
152            "every clone must reuse one opened store handle"
153        );
154    }
155
156    #[test]
157    fn unavailable_root_is_one_memoized_best_effort_decision() {
158        let parent = tempfile::tempdir().expect("create isolated parent");
159        let blocking_file = parent.path().join("not-a-directory");
160        std::fs::write(&blocking_file, b"x").expect("create blocking file");
161        let root = blocking_file.join("warm");
162        let configured = ConfiguredWarmStartStore::new(root, options());
163
164        assert!(configured.store().is_none());
165
166        // Make the path usable after the first refusal. A second call must not
167        // retry or mint a different per-clone decision.
168        std::fs::remove_file(&blocking_file).expect("remove blocking file");
169        std::fs::create_dir(&blocking_file).expect("replace it with a directory");
170        assert!(configured.clone().store().is_none());
171    }
172
173    #[test]
174    fn session_write_refusal_disables_the_shared_capability() {
175        let parent = tempfile::tempdir().expect("create isolated parent");
176        let root = parent.path().join("warm");
177        let configured = ConfiguredWarmStartStore::new(root.clone(), options());
178        let mut fingerprinter = crate::warm_start::Fingerprinter::new();
179        fingerprinter.absorb_str(b"test", "configured-session");
180        let session = configured
181            .open_session(fingerprinter.finalize())
182            .expect("open configured session");
183
184        std::fs::remove_dir(&root).expect("remove empty opened root");
185        std::fs::write(&root, b"not a directory").expect("block the configured root");
186        assert!(!session.finalize(b"payload", None, None));
187        assert!(
188            configured.store().is_none(),
189            "the session must report filesystem refusal to the shared owner"
190        );
191
192        std::fs::remove_file(&root).expect("remove blocking file");
193        std::fs::create_dir(&root).expect("make the root usable again");
194        assert!(
195            !session.finalize(b"payload", None, None),
196            "an unavailable capability must not retry through an existing session"
197        );
198    }
199}