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}