commonware_storage/journal/contiguous/
mod.rs

1//! Contiguous journals with position-based access.
2//!
3//! This module provides position-based journal implementations where items are stored
4//! contiguously and can be accessed by their position (0-indexed). Both [fixed]-size and
5//! [variable]-size item journals are supported.
6
7use super::Error;
8use futures::Stream;
9use std::num::NonZeroUsize;
10
11pub mod fixed;
12pub mod variable;
13
14#[cfg(test)]
15mod tests;
16
17/// Core trait for contiguous journals supporting sequential append operations.
18///
19/// A contiguous journal maintains a consecutively increasing position counter where each
20/// appended item receives a unique position starting from 0.
21pub trait Contiguous {
22    /// The type of items stored in the journal.
23    type Item;
24
25    /// Append a new item to the journal, returning its position.
26    ///
27    /// Positions are consecutively increasing starting from 0. The position of each item
28    /// is stable across pruning (i.e., if item X has position 5, it will always have
29    /// position 5 even if earlier items are pruned).
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if the underlying storage operation fails or if the item cannot
34    /// be encoded.
35    fn append(&mut self, item: Self::Item)
36        -> impl std::future::Future<Output = Result<u64, Error>>;
37
38    /// Return the total number of items that have been appended to the journal.
39    ///
40    /// This count is NOT affected by pruning. The next appended item will receive this
41    /// position as its value.
42    fn size(&self) -> impl std::future::Future<Output = u64>;
43
44    /// Return the position of the oldest item still retained in the journal.
45    ///
46    /// Returns `None` if the journal is empty or if all items have been pruned.
47    ///
48    /// After pruning, this returns the position of the first item that remains.
49    /// Note that due to section/blob alignment, this may be less than the `min_position`
50    /// passed to `prune()`.
51    fn oldest_retained_pos(&self) -> impl std::future::Future<Output = Result<Option<u64>, Error>>;
52
53    /// Prune items at positions strictly less than `min_position`.
54    ///
55    /// Returns `true` if any data was pruned, `false` otherwise.
56    ///
57    /// # Behavior
58    ///
59    /// - If `min_position > size()`, the prune is capped to `size()` (no error is returned)
60    /// - Some items with positions less than `min_position` may be retained due to
61    ///   section/blob alignment
62    /// - This operation is not atomic, but implementations guarantee the journal is left in a
63    ///   recoverable state if a crash occurs during pruning
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if the underlying storage operation fails.
68    fn prune(
69        &mut self,
70        min_position: u64,
71    ) -> impl std::future::Future<Output = Result<bool, Error>>;
72
73    /// Rewind the journal to the given size, discarding items from the end.
74    ///
75    /// After rewinding to size N, the journal will contain exactly N items
76    /// (positions 0 to N-1), and the next append will receive position N.
77    ///
78    /// # Behavior
79    ///
80    /// - If `size > current_size()`, returns [Error::InvalidRewind]
81    /// - If `size == current_size()`, this is a no-op
82    /// - If `size < oldest_retained_pos()`, returns [Error::InvalidRewind] (can't rewind to pruned data)
83    /// - This operation is not atomic, but implementations guarantee the journal is left in a
84    ///   recoverable state if a crash occurs during rewinding
85    ///
86    /// # Warnings
87    ///
88    /// - This operation is not guaranteed to survive restarts until `sync()` is called
89    ///
90    /// # Errors
91    ///
92    /// Returns [Error::InvalidRewind] if size is invalid (too large or points to pruned data).
93    /// Returns an error if the underlying storage operation fails.
94    fn rewind(&mut self, size: u64) -> impl std::future::Future<Output = Result<(), Error>>;
95
96    /// Return a stream of all items in the journal starting from `start_pos`.
97    ///
98    /// Each item is yielded as a tuple `(position, item)` where position is the item's
99    /// stable position in the journal.
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if `start_pos` exceeds the journal size or if any storage/decoding
104    /// errors occur during replay.
105    fn replay(
106        &self,
107        start_pos: u64,
108        buffer: NonZeroUsize,
109    ) -> impl std::future::Future<
110        Output = Result<impl Stream<Item = Result<(u64, Self::Item), Error>> + '_, Error>,
111    >;
112
113    /// Read the item at the given position.
114    ///
115    /// # Errors
116    ///
117    /// - Returns [Error::ItemPruned] if the item at `position` has been pruned.
118    /// - Returns [Error::ItemOutOfRange] if the item at `position` does not exist.
119    fn read(&self, position: u64) -> impl std::future::Future<Output = Result<Self::Item, Error>>;
120
121    /// Sync all pending writes to storage.
122    ///
123    /// This ensures all previously appended items are durably persisted.
124    fn sync(&mut self) -> impl std::future::Future<Output = Result<(), Error>>;
125
126    /// Close the journal, syncing all pending writes and releasing resources.
127    fn close(self) -> impl std::future::Future<Output = Result<(), Error>>;
128
129    /// Destroy the journal, removing all associated storage.
130    ///
131    /// This method consumes the journal and deletes all persisted data including blobs,
132    /// metadata, and any other storage artifacts. Use this for cleanup in tests or when
133    /// permanently removing a journal.
134    fn destroy(self) -> impl std::future::Future<Output = Result<(), Error>>;
135}