Skip to main content

sim_lib_journal/
lib.rs

1//! Domain-free atomic journal behavior over content-addressed objects.
2//!
3//! A journal publishes immutable objects before making entries visible and
4//! advances its head through one fenced compare-and-swap transition. Backends
5//! implement [`JournalBackend::admit`]; callers use [`Journal`] so the gapless,
6//! closure, redelivery, and replay laws are enforced once.
7
8mod backend;
9mod entry;
10mod head;
11mod host;
12mod lease;
13mod memory;
14mod object;
15mod projection;
16mod replay;
17mod verify;
18
19pub use backend::{Admission, JournalBackend, StoredState};
20pub use entry::JournalEntry;
21pub use head::JournalHead;
22pub use host::{BackendCapabilities, Failpoint, HostDirJournalBackend};
23pub use lease::Lease;
24pub use memory::MemoryBackend;
25pub use object::JournalObject;
26pub use projection::{DirProjection, ProjectionRow, TableProjection};
27pub use replay::{Replay, replay};
28pub use verify::{JournalError, Verification};
29
30use sim_kernel::{ContentId, Symbol};
31
32/// The single state-machine implementation shared by every backend.
33pub struct Journal<B> {
34    backend: B,
35}
36
37impl<B: JournalBackend> Journal<B> {
38    /// Wraps a backend. Durable production backends must report write safety.
39    pub fn new(backend: B) -> Self {
40        Self { backend }
41    }
42
43    /// Obtains a new fencing generation, invalidating every older lease.
44    pub fn acquire_lease(&self) -> Result<Lease, JournalError> {
45        self.backend.acquire_lease()
46    }
47
48    /// Returns the current verified head.
49    pub fn head(&self) -> Result<Option<JournalHead>, JournalError> {
50        let state = self.backend.read_state()?;
51        verify::verify_state(&state).map(|v| v.head)
52    }
53
54    /// Atomically publishes objects and an ordered entry batch under one fence.
55    ///
56    /// Exact redelivery is a no-op. Any conflicting delivery, missing/corrupt
57    /// payload, stale fence, sequence gap, or wrong predecessor is rejected.
58    pub fn publish(
59        &self,
60        lease: &Lease,
61        expected: Option<&JournalHead>,
62        objects: Vec<JournalObject>,
63        entries: Vec<JournalEntry>,
64    ) -> Result<JournalHead, JournalError> {
65        if entries.is_empty() {
66            return Err(JournalError::EmptyBatch);
67        }
68        let before = self.backend.read_state()?;
69        verify::verify_state(&before)?;
70        verify::verify_batch(&before, expected, &objects, &entries)?;
71        let head = self.backend.admit(Admission {
72            fence: lease.fence,
73            expected: expected.cloned(),
74            objects,
75            entries: entries.clone(),
76        })?;
77        Ok(head)
78    }
79
80    /// Reads and verifies the complete journal closure.
81    pub fn verify(&self) -> Result<Verification, JournalError> {
82        verify::verify_state(&self.backend.read_state()?)
83    }
84
85    /// Replays verified entries in sequence order.
86    pub fn replay(&self) -> Result<Replay, JournalError> {
87        replay(self.backend.read_state()?)
88    }
89
90    /// Creates a detached, read-only table projection.
91    pub fn table_projection(&self) -> Result<TableProjection, JournalError> {
92        Ok(TableProjection::from_verification(self.verify()?))
93    }
94
95    /// Creates a detached, read-only directory projection.
96    pub fn dir_projection(&self) -> Result<DirProjection, JournalError> {
97        Ok(DirProjection::from_verification(self.verify()?))
98    }
99
100    /// Convenience constructor for an entry with an open kind symbol.
101    pub fn entry(
102        sequence: u64,
103        previous: Option<ContentId>,
104        kind: Symbol,
105        payloads: Vec<ContentId>,
106    ) -> JournalEntry {
107        JournalEntry::new(sequence, previous, kind, payloads)
108    }
109}
110
111#[cfg(test)]
112mod tests;