Skip to main content

nodedb_wal/double_write/
status.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Observable torn-write protection state.
4//!
5//! A DWB problem never fails the WAL append that provoked it — the WAL write
6//! itself is still correct and still acknowledged. What it does change is
7//! whether a torn record at the tail of a segment can be reconstructed, and
8//! that has to be legible to the caller instead of living in a log line.
9
10/// Why a writer's double-write buffer stopped protecting it.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum DwbDegradation {
13    /// The DWB file could not be opened when the writer was constructed.
14    OpenFailed,
15    /// Mirroring a record into the DWB failed; the buffer was detached.
16    WriteFailed,
17    /// The batch fsync of the DWB failed; the buffer was detached.
18    FlushFailed,
19}
20
21impl std::fmt::Display for DwbDegradation {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            Self::OpenFailed => f.write_str("DWB could not be opened"),
25            Self::WriteFailed => f.write_str("DWB record write failed"),
26            Self::FlushFailed => f.write_str("DWB batch fsync failed"),
27        }
28    }
29}
30
31/// Torn-write protection standing of a WAL writer.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum DwbProtection {
34    /// Every record that fits a slot is being mirrored.
35    Active,
36    /// No DWB was requested for this writer (`DwbMode::Off`). Intentional, so
37    /// not a degradation.
38    Off,
39    /// A DWB was requested but is not mirroring.
40    Degraded(DwbDegradation),
41}
42
43impl DwbProtection {
44    /// Whether records are currently being mirrored.
45    pub fn is_active(&self) -> bool {
46        matches!(self, Self::Active)
47    }
48
49    /// Whether protection was requested and then lost.
50    pub fn is_degraded(&self) -> bool {
51        matches!(self, Self::Degraded(_))
52    }
53}
54
55/// Why a record could not be mirrored even though the buffer is healthy.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum DwbSkipReason {
58    /// The record is larger than one slot. Multi-slot spanning would make a
59    /// slot's own CRC insufficient to validate it, which is the property the
60    /// whole recovery path rests on.
61    RecordTooLarge { size: usize, max: usize },
62}
63
64impl std::fmt::Display for DwbSkipReason {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            Self::RecordTooLarge { size, max } => {
68                write!(f, "record of {size} bytes exceeds the {max}-byte DWB slot")
69            }
70        }
71    }
72}
73
74/// Outcome of mirroring one record into the double-write buffer.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76#[must_use]
77pub enum DwbMirror {
78    /// The record is in the buffer (durable once `flush` runs).
79    Mirrored,
80    /// The record was not mirrored and is therefore unprotected.
81    Skipped(DwbSkipReason),
82}