harmos_storage/manager/
status.rs1use super::*;
22
23impl<C> StorageManager<C> {
38 #[tracing::instrument(name = "storage.manager.status.wait_saved_after", level = "debug", skip_all, fields(after = cursor.sequence, saved = tracing::field::Empty, outcome = "cancelled"))]
44 pub async fn wait_saved_after(&self, cursor: &StorageCursor) -> Result<StorageCursor> {
45 let result = async {
46 self.inner.primary.history().check(&cursor.history)?;
47 self.inner
48 .wait(None, |state| {
49 if state.status.saved.sequence > cursor.sequence {
50 return crate::capacity::Attempt::Ready(state.status.saved.clone());
51 }
52 let error = if state.status.closing || state.status.closed {
53 Some(Error::Closing { domain: "storage" })
54 } else if let Some(source) = &state.status.failure {
55 Some(Error::Persistence { source: Arc::clone(source) })
56 } else if !state.ready || !state.status.running {
57 Some(Error::NotReady { domain: "storage" })
58 } else {
59 None
60 };
61 match error {
62 Some(error) => crate::capacity::Attempt::Refuse(Refusal::Failed(error)),
63 None => crate::capacity::Attempt::Wait,
64 }
65 })
66 .await
67 .map_err(Refusal::into_error)
68 }
69 .await;
70 if let Ok(saved) = &result {
71 tracing::Span::current().record("saved", saved.sequence);
72 }
73 tracing::Span::current().record("outcome", if result.is_ok() { "completed" } else { "failed" });
74 result
75 }
76
77 pub fn check_state_dependency(&self, required: &StorageCursor) -> Result<()> {
78 if !self.cursor_saved().covers(required)? {
79 return Err(Error::InvalidInput {
80 field: "state evidence dependency",
81 reason: "required primary evidence is not durable",
82 });
83 }
84 Ok(())
85 }
86
87 #[tracing::instrument(name = "storage.manager.status.retry_transient_persistence", level = "trace", skip_all)]
88 pub fn retry_transient_persistence(&self) -> bool {
89 let mut state = self.inner.state.lock().unwrap();
90 let Some(failure) = &state.status.failure else {
91 return false;
92 };
93 let mut cause = failure.as_ref();
94 while let Error::Persistence { source } = cause {
95 cause = source.as_ref();
96 }
97 if !matches!(
98 cause,
99 Error::Backend {
100 kind: crate::BackendFailure::Transient,
101 ..
102 }
103 ) {
104 return false;
105 }
106 state.status.failure = None;
107 state.retry = state.retry.wrapping_add(1);
108 drop(state);
109 self.inner.notify();
110 true
111 }
112
113 pub fn write_receipt_capacity(&self) -> usize {
114 self.inner.settings.request.max_pending_write_batches.get()
115 }
116
117 #[tracing::instrument(name = "storage.manager.status.await_durable", level = "debug", skip_all)]
118 pub async fn await_durable(&self, cursor: &StorageCursor) -> Result<()> {
119 self.inner.primary.history().check(&cursor.history)?;
120 self.flush_domain(Domain::Data, Some(cursor)).await
121 }
122
123 #[tracing::instrument(name = "storage.manager.status.release_object_history", level = "debug", skip_all)]
124 pub async fn release_object_history(&self, schema: &SchemaKey, item: &crate::ObjectKey, through: &StorageCursor) -> Result<()> {
125 self.inner.primary.history().check(&through.history)?;
126 if !self.status().saved.covers(through)? {
127 return Err(Error::InvalidInput {
128 field: "object retention",
129 reason: "release exceeds durable frontier",
130 });
131 }
132 self.inner.bounded("primary.release_object_history", self.inner.primary.release_object_history(schema, item, through)).await?;
133 self.inner.state.lock().unwrap().objects_pruned = 0;
134 self.inner.notify();
135 Ok(())
136 }
137}
138
139impl<C> StorageManager<C> {
140 pub(crate) fn position(&self, sequence: u64) -> StorageCursor {
141 StorageCursor {
142 history: self.inner.primary.history().clone(),
143 sequence,
144 }
145 }
146
147 pub(super) fn recovery_position(&self, sequence: u64) -> RecoveryPosition {
148 RecoveryPosition {
149 history: self.inner.recovery.history().clone(),
150 sequence,
151 }
152 }
153}
154
155#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::test_backend::{Data, Recovery, history, storage_settings};
165
166 fn storage() -> StorageManager {
167 StorageManager::new(
168 crate::StorageBackends {
169 primary: Arc::new(Data::<()>::new(history())),
170 state: Arc::new(Recovery::new(history())),
171 },
172 storage_settings(),
173 )
174 .unwrap()
175 }
176
177 async fn pending<F: std::future::Future + ?Sized>(mut future: std::pin::Pin<&mut F>) -> bool {
178 std::future::poll_fn(|cx| std::task::Poll::Ready(future.as_mut().poll(cx).is_pending())).await
179 }
180
181 #[tokio::test]
182 async fn saved_wait_cancels() {
183 let storage = storage();
184 {
185 let mut state = storage.inner.state.lock().unwrap();
186 state.ready = true;
187 state.status.running = true;
188 state.retry = 41;
189 }
190 let before = storage.cursor_saved();
191 let future = StorageCursor { sequence: 42, ..before.clone() };
192 let revision = *storage.inner.changed.borrow();
193 let subscribers = storage.inner.changed.receiver_count();
194 let mut waiting = Box::pin(storage.wait_saved_after(&future));
195 assert!(pending(waiting.as_mut()).await);
196 assert_eq!(storage.inner.changed.receiver_count(), subscribers + 1);
197 drop(waiting);
198 assert_eq!(storage.inner.changed.receiver_count(), subscribers);
199 assert_eq!(*storage.inner.changed.borrow(), revision);
200 {
201 let state = storage.inner.state.lock().unwrap();
202 assert_eq!(state.retry, 41);
203 assert!(state.data_request.is_none());
204 assert!(state.recovery_request.is_none());
205 assert_eq!(state.queue.len(), 0);
206 assert_eq!(state.status.saved, before);
207 assert_eq!(state.status.accepted, before);
208 }
209 let mut waiting = Box::pin(storage.wait_saved_after(&future));
210 assert!(pending(waiting.as_mut()).await);
211 storage.inner.state.lock().unwrap().status.closed = true;
212 storage.inner.notify();
213 assert!(matches!(waiting.await, Err(Error::Closing { .. })));
214 }
215 #[tokio::test]
216 async fn saved_wait_coalesces() {
217 let storage = storage();
218 {
219 let mut state = storage.inner.state.lock().unwrap();
220 state.ready = true;
221 state.status.running = true;
222 state.status.accepted.sequence = 12;
223 }
224 let before = storage.cursor_saved();
225 let mut waiting = Box::pin(storage.wait_saved_after(&before));
226 assert!(pending(waiting.as_mut()).await);
227 for sequence in [3, 9] {
228 storage.inner.state.lock().unwrap().status.saved.sequence = sequence;
229 storage.inner.notify();
230 }
231 assert_eq!(waiting.await.unwrap().sequence, 9);
232 let cursor = storage.position(9);
234 let waiting = storage.wait_saved_after(&cursor);
235 storage.inner.state.lock().unwrap().status.saved.sequence = 12;
236 storage.inner.notify();
237 assert_eq!(waiting.await.unwrap().sequence, 12);
238 }
239
240 #[tokio::test]
241 async fn saved_wait_errors() {
242 let storage = storage();
243 let cursor = storage.cursor_saved();
244 assert!(matches!(storage.wait_saved_after(&cursor).await, Err(Error::NotReady { .. })));
245 let failure = Arc::new(Error::NotReady { domain: "injected persistence" });
246 {
247 let mut state = storage.inner.state.lock().unwrap();
248 state.status.failure = Some(Arc::clone(&failure));
249 state.retry = 41;
250 }
251 let result = storage.wait_saved_after(&cursor).await;
252 assert!(matches!(result, Err(Error::Persistence { source }) if Arc::ptr_eq(&source, &failure)));
253 {
254 let mut state = storage.inner.state.lock().unwrap();
255 assert!(Arc::ptr_eq(state.status.failure.as_ref().unwrap(), &failure));
256 assert_eq!(state.retry, 41);
257 assert!(state.data_request.is_none());
258 state.status.closing = true;
259 }
260 assert!(matches!(storage.wait_saved_after(&cursor).await, Err(Error::Closing { .. })));
261 {
262 let mut state = storage.inner.state.lock().unwrap();
263 state.status.accepted.sequence = 7;
264 state.status.saved.sequence = 7;
265 }
266 assert_eq!(storage.wait_saved_after(&cursor).await.unwrap().sequence, 7);
267 let mut foreign = cursor;
268 foreign.history.store = crate::Identifier::new("foreign").unwrap();
269 assert!(matches!(storage.wait_saved_after(&foreign).await, Err(Error::Domain(harmos_domain::Error::DifferentHistory))));
270 }
271}