Skip to main content

mira_core/
error.rs

1use std::path::PathBuf;
2
3#[derive(Debug, thiserror::Error)]
4pub enum Error {
5    #[error("io error on {path}: {source}")]
6    Io {
7        path: PathBuf,
8        #[source]
9        source: std::io::Error,
10    },
11
12    #[error(transparent)]
13    Arrow(#[from] arrow_schema::ArrowError),
14
15    /// A dictionary key space filled up mid-append. The caller's response is to
16    /// seal the current block and retry into a fresh one, never to fail the RPC.
17    #[error("dictionary key space exhausted for column `{0}`; seal the block")]
18    DictionaryFull(&'static str),
19
20    #[error("{path} is not an Arrow IPC file (bad ARROW1 magic)")]
21    BadMagic { path: PathBuf },
22
23    #[error("{path} failed checksum: footer says {expected:#010x}, body hashes to {actual:#010x}")]
24    BadChecksum {
25        path: PathBuf,
26        expected: u32,
27        actual: u32,
28    },
29
30    #[error("{path} has no {key} in its IPC footer metadata; not a Mira block")]
31    MissingMetadata { path: PathBuf, key: &'static str },
32
33    /// Every way arrow-rs can refuse a block body: a misaligned buffer, a
34    /// dictionary that will not decode, a ZSTD frame that will not decompress.
35    /// One variant because they arrive through one call and the `source` is
36    /// what says which — and named for the refusal rather than for alignment,
37    /// because a headline of "buffers are not aligned" over a corrupt ZSTD
38    /// frame sends the reader to the wrong half of the file at 3am.
39    ///
40    /// Misalignment is the case worth naming in the message anyway: it is the
41    /// one Mira opts into detecting, with `with_require_alignment(true)`. The
42    /// arrow-rs default is to silently memcpy the whole body out of the
43    /// mapping, turning a zero-copy read into a full allocation with no signal.
44    #[error("{path}: this block's body cannot be decoded: {source}")]
45    Undecodable {
46        path: PathBuf,
47        #[source]
48        source: arrow_schema::ArrowError,
49    },
50
51    /// The data directory is on a filesystem Mira's read path cannot survive.
52    /// See `block::check_filesystem`.
53    #[error(
54        "{path} is on {fs}, a network filesystem. Mira reads blocks through mmap, \
55         and on {fs} a server-side error surfaces as SIGBUS — a signal, not an \
56         error, with no recovery path from Rust. Point --data-dir at a local \
57         block device (in Kubernetes: a local PV, an EBS/PD volume, or an \
58         emptyDir, not an NFS/CSI network mount)."
59    )]
60    NetworkFilesystem { path: PathBuf, fs: String },
61
62    /// The data directory exists but cannot be written to. See
63    /// `block::check_writable`.
64    #[error(
65        "{path} is not writable: {source}. Mira writes nowhere else, so this is \
66         fatal at startup rather than degraded at 3am. Check that the mount is \
67         read-write and that this process owns the path (in Kubernetes: a \
68         volume mounted readOnly, or a missing fsGroup)."
69    )]
70    NotWritable {
71        path: PathBuf,
72        #[source]
73        source: std::io::Error,
74    },
75
76    /// An export bigger than the WAL will frame. Distinct from a corrupt
77    /// length on read: this one is the caller's fault and is answerable with a
78    /// 4xx, so it must not be confused with the file being damaged.
79    #[error("export of {len} bytes exceeds the {max}-byte WAL frame limit")]
80    WalFrameTooLarge { len: usize, max: u32 },
81
82    /// A WAL frame did not survive the trip. Expected exactly once, at the
83    /// tail of the last segment after a crash; anywhere else it is damage.
84    /// See `wal::Wal::replay` for why this ends a segment rather than the
85    /// process.
86    #[error("{path}: corrupt write-ahead log frame: {why}")]
87    WalCorrupt { path: PathBuf, why: &'static str },
88
89    /// A WAL segment written by a different build. Refused rather than
90    /// guessed at, for the same reason a block with an unknown format version
91    /// is: a frame layout is not self-describing enough to parse hopefully.
92    #[error("{path}: write-ahead log version {found}, this build writes {expected}")]
93    WalVersion {
94        path: PathBuf,
95        found: u16,
96        expected: u16,
97    },
98}
99
100pub type Result<T, E = Error> = std::result::Result<T, E>;
101
102pub(crate) trait IoContext<T> {
103    fn ctx(self, path: impl Into<PathBuf>) -> Result<T>;
104}
105
106impl<T> IoContext<T> for std::io::Result<T> {
107    fn ctx(self, path: impl Into<PathBuf>) -> Result<T> {
108        self.map_err(|source| Error::Io {
109            path: path.into(),
110            source,
111        })
112    }
113}