Skip to main content

harmos_storage/manager/
status.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17
18// Third Party Imports
19
20// Local Imports
21use super::*;
22
23/* === Constants === */
24
25/* === Macros === */
26
27/* === Type Aliases === */
28
29/* === Structs === */
30
31/* === Enums === */
32
33/* === Traits === */
34
35/* === Implementations === */
36
37impl<C> StorageManager<C> {
38    /// Waits passively for a strictly newer durable primary frontier.
39    ///
40    /// Intermediate frontiers may coalesce. This does not request persistence or
41    /// retry failed work. Cancellation only removes the notification waiter.
42    /// A newer saved cursor is returned before checking terminal storage state.
43    #[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 { field: "state evidence dependency", reason: "required primary evidence is not durable" });
80        }
81        Ok(())
82    }
83
84    #[tracing::instrument(name = "storage.manager.status.retry_transient_persistence", level = "trace", skip_all)]
85    pub fn retry_transient_persistence(&self) -> bool {
86        let mut state = self.inner.state.lock().unwrap();
87        let Some(failure) = &state.status.failure else {
88            return false;
89        };
90        let mut cause = failure.as_ref();
91        while let Error::Persistence { source } = cause {
92            cause = source.as_ref();
93        }
94        if !matches!(cause, Error::Backend { kind: crate::BackendFailure::Transient, .. }) {
95            return false;
96        }
97        state.status.failure = None;
98        state.retry = state.retry.wrapping_add(1);
99        drop(state);
100        self.inner.notify();
101        true
102    }
103
104    #[tracing::instrument(name = "storage.manager.status.reserve_arrow_memory", level = "trace", skip_all, fields(bytes = bytes, outcome = "cancelled"))]
105    pub fn reserve_arrow_memory(&self, bytes: usize) -> Result<RetainedMemory> {
106        let result: Result<RetainedMemory> =
107            Reservation::try_new(&self.inner, Pool::Read, &Held::default(), bytes).map(Reservation::retain).map_err(Refusal::into_error);
108        tracing::Span::current().record("outcome", if result.is_ok() { "completed" } else { "failed" });
109        result
110    }
111
112    pub fn write_receipt_capacity(&self) -> usize {
113        self.inner.settings.request.max_pending_write_batches.get()
114    }
115
116    #[tracing::instrument(name = "storage.manager.status.await_durable", level = "debug", skip_all)]
117    pub async fn await_durable(&self, cursor: &StorageCursor) -> Result<()> {
118        self.inner.primary.history().check(&cursor.history)?;
119        self.flush_domain(Domain::Data, Some(cursor)).await
120    }
121
122    #[tracing::instrument(name = "storage.manager.status.release_object_history", level = "debug", skip_all)]
123    pub async fn release_object_history(&self, schema: &SchemaKey, item: &crate::ObjectKey, through: &StorageCursor) -> Result<()> {
124        self.inner.primary.history().check(&through.history)?;
125        if !self.status().saved.covers(through)? {
126            return Err(Error::InvalidInput { field: "object retention", reason: "release exceeds durable frontier" });
127        }
128        self.inner.bounded("primary.release_object_history", self.inner.primary.release_object_history(schema, item, through)).await?;
129        self.inner.state.lock().unwrap().objects_pruned = 0;
130        self.inner.notify();
131        Ok(())
132    }
133}
134
135/* === Public Functions === */
136
137/* === Private Functions === */
138
139/* === Tests === */
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::test_backend::{Data, Recovery, history, storage_settings};
145
146    fn storage() -> StorageManager {
147        StorageManager::new(
148            crate::StorageBackends { primary: Arc::new(Data::<()>::new(history())), state: Arc::new(Recovery::new(history())) },
149            storage_settings(),
150        )
151        .unwrap()
152    }
153
154    async fn pending<F: std::future::Future + ?Sized>(mut future: std::pin::Pin<&mut F>) -> bool {
155        std::future::poll_fn(|cx| std::task::Poll::Ready(future.as_mut().poll(cx).is_pending())).await
156    }
157
158    #[tokio::test]
159    async fn saved_wait_cancels() {
160        let storage = storage();
161        {
162            let mut state = storage.inner.state.lock().unwrap();
163            state.ready = true;
164            state.status.running = true;
165            state.retry = 41;
166        }
167        let before = storage.cursor_saved();
168        let future = StorageCursor { sequence: 42, ..before.clone() };
169        let revision = *storage.inner.changed.borrow();
170        let subscribers = storage.inner.changed.receiver_count();
171        let mut waiting = Box::pin(storage.wait_saved_after(&future));
172        assert!(pending(waiting.as_mut()).await);
173        assert_eq!(storage.inner.changed.receiver_count(), subscribers + 1);
174        drop(waiting);
175        assert_eq!(storage.inner.changed.receiver_count(), subscribers);
176        assert_eq!(*storage.inner.changed.borrow(), revision);
177        {
178            let state = storage.inner.state.lock().unwrap();
179            assert_eq!(state.retry, 41);
180            assert!(state.data_request.is_none());
181            assert!(state.recovery_request.is_none());
182            assert_eq!(state.queue.len(), 0);
183            assert_eq!(state.status.saved, before);
184            assert_eq!(state.status.accepted, before);
185        }
186        let mut waiting = Box::pin(storage.wait_saved_after(&future));
187        assert!(pending(waiting.as_mut()).await);
188        storage.inner.state.lock().unwrap().status.closed = true;
189        storage.inner.notify();
190        assert!(matches!(waiting.await, Err(Error::Closing { .. })));
191    }
192    #[tokio::test]
193    async fn saved_wait_coalesces() {
194        let storage = storage();
195        {
196            let mut state = storage.inner.state.lock().unwrap();
197            state.ready = true;
198            state.status.running = true;
199            state.status.accepted.sequence = 12;
200        }
201        let before = storage.cursor_saved();
202        let mut waiting = Box::pin(storage.wait_saved_after(&before));
203        assert!(pending(waiting.as_mut()).await);
204        for sequence in [3, 9] {
205            storage.inner.state.lock().unwrap().status.saved.sequence = sequence;
206            storage.inner.notify();
207        }
208        assert_eq!(waiting.await.unwrap().sequence, 9);
209        // Persistence Can Advance Between Taking A Snapshot And Polling The Wait.
210        let cursor = storage.position(9);
211        let waiting = storage.wait_saved_after(&cursor);
212        storage.inner.state.lock().unwrap().status.saved.sequence = 12;
213        storage.inner.notify();
214        assert_eq!(waiting.await.unwrap().sequence, 12);
215    }
216
217    #[tokio::test]
218    async fn saved_wait_errors() {
219        let storage = storage();
220        let cursor = storage.cursor_saved();
221        assert!(matches!(storage.wait_saved_after(&cursor).await, Err(Error::NotReady { .. })));
222        let failure = Arc::new(Error::NotReady { domain: "injected persistence" });
223        {
224            let mut state = storage.inner.state.lock().unwrap();
225            state.status.failure = Some(Arc::clone(&failure));
226            state.retry = 41;
227        }
228        let result = storage.wait_saved_after(&cursor).await;
229        assert!(matches!(result, Err(Error::Persistence { source }) if Arc::ptr_eq(&source, &failure)));
230        {
231            let mut state = storage.inner.state.lock().unwrap();
232            assert!(Arc::ptr_eq(state.status.failure.as_ref().unwrap(), &failure));
233            assert_eq!(state.retry, 41);
234            assert!(state.data_request.is_none());
235            state.status.closing = true;
236        }
237        assert!(matches!(storage.wait_saved_after(&cursor).await, Err(Error::Closing { .. })));
238        {
239            let mut state = storage.inner.state.lock().unwrap();
240            state.status.accepted.sequence = 7;
241            state.status.saved.sequence = 7;
242        }
243        assert_eq!(storage.wait_saved_after(&cursor).await.unwrap().sequence, 7);
244        let mut foreign = cursor;
245        foreign.history.store = crate::Identifier::new("foreign").unwrap();
246        assert!(matches!(storage.wait_saved_after(&foreign).await, Err(Error::Domain(harmos_domain::Error::DifferentHistory))));
247    }
248}