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}