cranpose_services/durable_save/
local_saves.rs1use std::{collections::BTreeMap, rc::Rc, sync::Arc};
6
7use super::{NEXT_SAVE, Ordering, SaveBatch};
8use crate::preferences::PreferencesRef;
9
10struct LocalSave {
11 store: Option<PreferencesRef>,
13 key: String,
14 read: Box<dyn Fn() -> Option<String>>,
15}
16
17#[derive(Default)]
18pub(crate) struct LocalSaves {
19 live: BTreeMap<u64, Rc<LocalSave>>,
20 retired: BTreeMap<String, RetiredSave>,
25 forgotten: Vec<(PreferencesRef, Arc<Forget>)>,
27}
28
29struct RetiredSave {
30 store: Option<PreferencesRef>,
31 value: String,
32}
33
34pub(super) struct Forget {
36 prefix: String,
37 keep: Vec<String>,
38}
39
40impl Forget {
41 pub(super) fn matches(&self, key: &str) -> bool {
42 key.starts_with(&self.prefix) && !self.keep.iter().any(|kept| key.starts_with(kept))
43 }
44}
45
46fn same_store(a: Option<&PreferencesRef>, b: Option<&PreferencesRef>) -> bool {
47 match (a, b) {
48 (Some(a), Some(b)) => Arc::ptr_eq(a, b),
49 (None, None) => true,
50 _ => false,
51 }
52}
53
54impl LocalSaves {
55 fn forgotten(&self, store: &PreferencesRef, key: &str) -> bool {
56 self.forgotten
57 .iter()
58 .any(|(forgotten, keys)| Arc::ptr_eq(forgotten, store) && keys.matches(key))
59 }
60}
61
62pub struct SaveableRegistration {
65 id: u64,
66 thread: std::marker::PhantomData<Rc<()>>,
67}
68
69impl Drop for SaveableRegistration {
70 fn drop(&mut self) {
71 crate::thread_state::try_with(|state| {
72 let removed = state.local_saves.borrow_mut().live.remove(&self.id);
73 let Some(save) = removed else {
74 return;
75 };
76 let Some(value) = (save.read)() else {
77 return;
78 };
79 let stored = save.store.as_ref().is_some_and(|store| {
80 !state.local_saves.borrow().forgotten(store, &save.key)
81 && store.get(&save.key).as_deref() == Some(value.as_str())
82 });
83 if !stored {
84 state.local_saves.borrow_mut().retired.insert(
85 save.key.clone(),
86 RetiredSave {
87 store: save.store.clone(),
88 value,
89 },
90 );
91 }
92 });
93 }
94}
95
96pub(crate) fn register(
99 store: Option<PreferencesRef>,
100 key: String,
101 read: impl Fn() -> Option<String> + 'static,
102) -> SaveableRegistration {
103 let id = NEXT_SAVE.fetch_add(1, Ordering::Relaxed);
104 crate::thread_state::with(|state| {
105 let mut saves = state.local_saves.borrow_mut();
106 saves.retired.remove(&key);
107 saves.live.insert(
108 id,
109 Rc::new(LocalSave {
110 store,
111 key,
112 read: Box::new(read),
113 }),
114 )
115 });
116 SaveableRegistration {
117 id,
118 thread: std::marker::PhantomData,
119 }
120}
121
122pub(crate) fn saved(store: Option<&PreferencesRef>, key: &str) -> Option<String> {
125 let (retired, forgotten) = crate::thread_state::with(|state| {
126 let saves = state.local_saves.borrow();
127 (
128 saves
129 .retired
130 .get(key)
131 .filter(|retired| same_store(retired.store.as_ref(), store))
132 .map(|retired| retired.value.clone()),
133 store.is_some_and(|store| saves.forgotten(store, key)),
134 )
135 });
136 if retired.is_some() {
137 return retired;
138 }
139 store
140 .filter(|_| !forgotten)
141 .and_then(|store| store.get(key))
142}
143
144pub(crate) fn forget(store: Option<PreferencesRef>, prefix: String, keep: Vec<String>) {
149 let keys = Arc::new(Forget { prefix, keep });
150 if let Some(store) = &store {
151 super::stop_preference_saves(store, &keys);
152 }
153 let (stopped, retired): (Vec<_>, _) = crate::thread_state::with(|state| {
154 let mut saves = state.local_saves.borrow_mut();
155 saves.retired.retain(|key, _| !keys.matches(key));
156 if let Some(store) = store {
157 saves.forgotten.push((store, Arc::clone(&keys)));
158 }
159 let retired = state.retained.borrow_mut().forget(|key| keys.matches(key));
160 let stopped = saves
161 .live
162 .extract_if(.., |_, save| keys.matches(&save.key))
163 .collect();
164 (stopped, retired)
165 });
166 drop(stopped);
167 drop(retired);
168}
169
170pub(crate) struct Sampled {
173 retired: Vec<(String, String)>,
174 forgotten: usize,
175}
176
177pub(super) fn sample(batch: &mut SaveBatch) -> Sampled {
179 let (live, retired, forgotten) = crate::thread_state::with(|state| {
180 let saves = state.local_saves.borrow();
181 (
182 saves
183 .live
184 .values()
185 .filter(|save| save.store.is_some())
186 .cloned()
187 .collect::<Vec<_>>(),
188 saves
189 .retired
190 .iter()
191 .filter_map(|(key, retired)| {
192 let store = retired.store.as_ref()?;
193 Some((Arc::clone(store), key.clone(), retired.value.clone()))
194 })
195 .collect::<Vec<_>>(),
196 saves.forgotten.clone(),
197 )
198 });
199 let sampled_forgotten = forgotten.len();
200 for (store, keys) in forgotten {
201 batch.forget(&store, keys);
202 }
203 let mut written = Vec::with_capacity(retired.len());
204 for (store, key, value) in retired {
205 batch.push(&store, key.clone(), value.clone());
206 written.push((key, value));
207 }
208 for save in live {
209 if let (Some(store), Some(value)) = (&save.store, (save.read)()) {
210 batch.push(store, save.key.clone(), value);
211 }
212 }
213 Sampled {
214 retired: written,
215 forgotten: sampled_forgotten,
216 }
217}
218
219impl Sampled {
220 pub(super) fn completed(self) {
223 crate::thread_state::with(|state| {
224 let mut saves = state.local_saves.borrow_mut();
225 let forgotten = self.forgotten.min(saves.forgotten.len());
226 saves.forgotten.drain(..forgotten);
227 for (key, value) in self.retired {
228 if saves
229 .retired
230 .get(&key)
231 .is_some_and(|retired| retired.value == value)
232 {
233 saves.retired.remove(&key);
234 }
235 }
236 });
237 }
238}