gam_runtime/warm_start/
configured.rs1use super::{Fingerprint, Session, StoreError, StoreOptions, WarmStartStore};
9use std::path::{Path, PathBuf};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::{Arc, OnceLock};
12
13#[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 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 pub fn root(&self) -> &Path {
42 &self.root
43 }
44
45 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 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 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 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}