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 {
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/* === Public Functions === */
156
157/* === Private Functions === */
158
159/* === Tests === */
160
161#[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        // Persistence Can Advance Between Taking A Snapshot And Polling The Wait.
233        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}