Skip to main content

weir_core/
durability.rs

1//! The [`Durability`] tier — the per-record durability guarantee a producer
2//! requests in the frame header.
3
4/// Durability tier requested by the producer for a given record.
5/// Wire values are fixed and must not change without a WIRE_VERSION bump.
6///
7/// `Sync` and `Batched` carry the **same durability guarantee today**: the
8/// record is written and `fdatasync`ed — as part of one batch-boundary group
9/// fsync — *before* the ACK is sent, so an ack means the bytes are on stable
10/// storage and replay after a crash. They remain distinct wire values for
11/// historical and forward-compatibility reasons (originally `Sync` fsynced once
12/// per record and `Batched` once per batch; both now share the batch-boundary
13/// group fsync — see `docs/architecture.md`). `Buffered` trades durability for
14/// latency: it acks before any fsync.
15#[repr(u8)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Durability {
18    /// Durable before ACK: written and `fdatasync`ed (via the batch-boundary
19    /// group fsync) before the ACK — on stable storage, replays after a crash.
20    Sync = 0x01,
21    /// Durable before ACK — the same guarantee as [`Durability::Sync`] today:
22    /// written and `fdatasync`ed at the batch boundary before the ACK.
23    /// (Historically `Batched` deferred fsync to the batch boundary while `Sync`
24    /// fsynced per record; both now share the batch-boundary group fsync.)
25    Batched = 0x02,
26    /// Memory write only. ACK is sent after the record enters the in-memory queue.
27    Buffered = 0x03,
28}
29
30impl From<Durability> for u8 {
31    /// The wire byte for this tier. Inverse of [`Durability::try_from`].
32    fn from(d: Durability) -> u8 {
33        d as u8
34    }
35}
36
37impl std::fmt::Display for Durability {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        let s = match self {
40            Durability::Sync => "sync",
41            Durability::Batched => "batched",
42            Durability::Buffered => "buffered",
43        };
44        write!(f, "{s}")
45    }
46}
47
48/// Error returned when a `u8` does not map to a known `Durability` variant.
49/// Preserves the raw byte for logging by the caller.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct UnknownDurability(pub u8);
52
53impl std::fmt::Display for UnknownDurability {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "unknown durability byte: {:#04x}", self.0)
56    }
57}
58
59impl std::error::Error for UnknownDurability {}
60
61impl TryFrom<u8> for Durability {
62    type Error = UnknownDurability;
63
64    fn try_from(value: u8) -> Result<Self, Self::Error> {
65        match value {
66            0x01 => Ok(Durability::Sync),
67            0x02 => Ok(Durability::Batched),
68            0x03 => Ok(Durability::Buffered),
69            v => Err(UnknownDurability(v)),
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn try_from_accepts_known_values() {
80        assert_eq!(Durability::try_from(0x01).unwrap(), Durability::Sync);
81        assert_eq!(Durability::try_from(0x02).unwrap(), Durability::Batched);
82        assert_eq!(Durability::try_from(0x03).unwrap(), Durability::Buffered);
83    }
84
85    #[test]
86    fn from_for_u8_round_trips_and_display() {
87        for d in [Durability::Sync, Durability::Batched, Durability::Buffered] {
88            assert_eq!(Durability::try_from(u8::from(d)).unwrap(), d);
89        }
90        assert_eq!(Durability::Sync.to_string(), "sync");
91        assert_eq!(Durability::Buffered.to_string(), "buffered");
92    }
93
94    #[test]
95    fn try_from_rejects_unknown_values() {
96        assert!(Durability::try_from(0x00).is_err());
97        assert!(Durability::try_from(0x04).is_err());
98        assert!(Durability::try_from(0xff).is_err());
99    }
100
101    #[test]
102    fn unknown_durability_preserves_byte() {
103        let err = Durability::try_from(0xab).unwrap_err();
104        assert_eq!(err.0, 0xab);
105    }
106
107    #[test]
108    fn repr_values_match_wire() {
109        assert_eq!(Durability::Sync as u8, 0x01);
110        assert_eq!(Durability::Batched as u8, 0x02);
111        assert_eq!(Durability::Buffered as u8, 0x03);
112    }
113}