Skip to main content

mongreldb_log/
commit_log.rs

1//! Commit-log abstraction (spec sections 6.2 and 9.4, FND-004).
2//!
3//! [`CommitLog`] is the single authority through which commands become
4//! committed. Standalone mode has one implementation wrapping the shared WAL
5//! group commit (in `mongreldb-core`); replicated mode implements the same
6//! contract over Raft (Stage 2). The storage apply path receives only
7//! committed commands.
8
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::Arc;
11use std::time::Instant;
12
13use mongreldb_types::hlc::HlcTimestamp;
14use mongreldb_types::ids::TransactionId;
15
16use crate::envelope::{CommandEnvelope, EnvelopeError};
17
18/// A position in one commit log. `term` is zero for the standalone log.
19#[derive(
20    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
21)]
22pub struct LogPosition {
23    /// Consensus term; zero in standalone mode.
24    pub term: u64,
25    /// Monotonic log index (the standalone epoch).
26    pub index: u64,
27}
28
29impl LogPosition {
30    /// The position before any entry.
31    pub const ZERO: Self = Self { term: 0, index: 0 };
32}
33
34/// One committed log entry returned by [`CommitLog::read_committed`].
35#[derive(Debug, Clone)]
36pub struct CommittedEntry {
37    /// Position of this entry in the log.
38    pub position: LogPosition,
39    /// Commit timestamp assigned by the log authority.
40    pub commit_ts: HlcTimestamp,
41    /// The committed command.
42    pub envelope: CommandEnvelope,
43}
44
45/// A point-in-time image of applied state at a log boundary.
46#[derive(Debug, Clone)]
47pub struct LogSnapshot {
48    /// Last log position included in the snapshot.
49    pub position: LogPosition,
50    /// Commit timestamp of `position`.
51    pub commit_ts: HlcTimestamp,
52    /// Opaque snapshot payload defined by the implementation.
53    pub data: Vec<u8>,
54}
55
56/// Durability guarantee attached to a committed command.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
58pub enum DurabilityLevel {
59    /// Standalone shared-WAL group commit (fsync before acknowledge).
60    GroupCommit,
61    /// Replicated: leader-local disk only (optional lower guarantee).
62    LeaderDisk,
63    /// Replicated: persisted by a quorum (the replicated default, RPO 0).
64    Quorum,
65}
66
67/// Proof that a command crossed its durable commit fence (spec S1B-004).
68///
69/// Once a receipt exists, the caller is never told the write rolled back.
70#[derive(Debug, Clone)]
71pub struct CommitReceipt {
72    /// Transaction this command committed.
73    pub transaction_id: TransactionId,
74    /// Commit timestamp assigned by the log authority.
75    pub commit_ts: HlcTimestamp,
76    /// Position of the committed entry.
77    pub log_position: LogPosition,
78    /// Durability level that was satisfied.
79    pub durability: DurabilityLevel,
80}
81
82/// Deadline and cancellation for log operations.
83///
84/// This is a deliberately minimal mirror of `mongreldb_core`'s
85/// `execution::ExecutionControl`; the core type cannot move below the runtime
86/// crate in the dependency graph, so the core adapter converts (see
87/// `docs/architecture/adr/0002`).
88#[derive(Debug, Clone, Default)]
89pub struct ExecutionControl {
90    /// Optional absolute deadline; queue wait counts toward it.
91    pub deadline: Option<Instant>,
92    /// Cooperative cancellation flag.
93    pub cancellation: Option<Arc<AtomicBool>>,
94}
95
96impl ExecutionControl {
97    /// Returns an error if the operation is cancelled or past its deadline.
98    pub fn check(&self) -> Result<(), LogError> {
99        if let Some(flag) = &self.cancellation {
100            if flag.load(Ordering::Relaxed) {
101                return Err(LogError::Cancelled);
102            }
103        }
104        if let Some(deadline) = self.deadline {
105            if Instant::now() >= deadline {
106                return Err(LogError::DeadlineExceeded);
107            }
108        }
109        Ok(())
110    }
111}
112
113/// Errors produced by a [`CommitLog`].
114#[derive(Debug, thiserror::Error)]
115pub enum LogError {
116    /// The command envelope was malformed or unverifiable.
117    #[error(transparent)]
118    Envelope(#[from] EnvelopeError),
119    /// The operation was cancelled.
120    #[error("operation cancelled")]
121    Cancelled,
122    /// The operation's deadline expired.
123    #[error("deadline exceeded")]
124    DeadlineExceeded,
125    /// The log is closed and rejects new proposals.
126    #[error("commit log is closed")]
127    Closed,
128    /// The receiving replica is not the leader for the consensus group
129    /// (spec section 11.7). Retryable with the returned leader hint.
130    ///
131    /// Category mapping: this variant carries
132    /// `mongreldb_types::errors::ErrorCategory::NotLeader` semantics.
133    /// No `LogError` → `ErrorCategory` bridge exists yet (the engine
134    /// commit path does not convert `LogError` today); the bridge lands
135    /// with the Stage 2G gateway/routing wave, which consumes the hint.
136    #[error("not the leader (current leader: {leader_hint:?})")]
137    NotLeader {
138        /// The group's current leader hint when known (text form of the
139        /// leader identity, e.g. its raft node id); `None` when the group
140        /// has no known leader.
141        leader_hint: Option<String>,
142    },
143    /// The implementation does not provide this operation.
144    #[error("unsupported operation: {0}")]
145    Unsupported(&'static str),
146    /// Any other log failure.
147    #[error("commit log failure: {0}")]
148    Internal(String),
149}
150
151/// The single authority through which commands become committed.
152pub trait CommitLog: Send + Sync {
153    /// Proposes one command and waits until its durability policy is
154    /// satisfied. A returned [`CommitReceipt`] is irrevocable.
155    fn propose(
156        &self,
157        command: CommandEnvelope,
158        control: &ExecutionControl,
159    ) -> Result<CommitReceipt, LogError>;
160
161    /// Reads committed entries strictly after `after`, in log order.
162    fn read_committed(
163        &self,
164        after: LogPosition,
165        limit: usize,
166    ) -> Result<Vec<CommittedEntry>, LogError>;
167
168    /// The highest position the local state machine has applied.
169    fn applied_position(&self) -> LogPosition;
170
171    /// Captures applied state through the current applied position.
172    fn create_snapshot(&self) -> Result<LogSnapshot, LogError>;
173
174    /// Replaces applied state with a snapshot taken at a log boundary.
175    fn install_snapshot(&self, snapshot: LogSnapshot) -> Result<(), LogError>;
176}