Add CRC32C checksums to WAL record format (v2), implement crash recovery with automatic truncation of corrupt records, add feature-gated group commit buffer for batched fsync under concurrent load, and implement log rotation via segment files with global offset addressing. Key changes: - Record format v2: [len:u32][crc32c:u32][blake3:32][payload:N] - recover_file() scans and truncates corrupt tail records - GroupCommitBuffer batches fsync via MPSC channel (tokio feature gate) - SegmentManager with binary search resolution and cursor-based cleanup - Journal::read() auto-refreshes segments on miss for writer/reader split - Split recovery.rs and key_codec.rs into directory modules for 500-line max Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
78 lines
2.2 KiB
Rust
78 lines
2.2 KiB
Rust
use std::io;
|
|
use std::path::PathBuf;
|
|
use thiserror::Error;
|
|
|
|
/// Result type for WAL operations.
|
|
pub type Result<T> = std::result::Result<T, QuarantineError>;
|
|
|
|
/// Errors that can occur during WAL operations.
|
|
#[derive(Error, Debug)]
|
|
pub enum QuarantineError {
|
|
/// IO error at a specific path.
|
|
#[error("IO error at {path:?}: {source}")]
|
|
Io {
|
|
/// The path where the error occurred.
|
|
path: PathBuf,
|
|
/// The underlying IO error.
|
|
source: io::Error,
|
|
},
|
|
|
|
/// Failed to fsync a file.
|
|
#[error("Failed to fsync {path:?}: {source}")]
|
|
FsyncFailed {
|
|
/// The path of the file.
|
|
path: PathBuf,
|
|
/// The underlying IO error.
|
|
source: io::Error,
|
|
},
|
|
|
|
/// File is locked by another process.
|
|
#[error("File is locked: {path:?}")]
|
|
FileLocked {
|
|
/// The path of the locked file.
|
|
path: PathBuf,
|
|
},
|
|
|
|
/// CRC32C checksum mismatch (fast integrity check, detects torn writes).
|
|
#[error(
|
|
"CRC32C mismatch at offset {offset}: expected {expected:#010x}, actual {actual:#010x}"
|
|
)]
|
|
Crc32cMismatch {
|
|
/// File offset where the corrupt record starts.
|
|
offset: u64,
|
|
/// Expected CRC32C value from the record header.
|
|
expected: u32,
|
|
/// Actual CRC32C computed from the data.
|
|
actual: u32,
|
|
},
|
|
|
|
/// Record length field is invalid (zero or exceeds MAX_RECORD_SIZE).
|
|
#[error("Invalid record length at offset {offset}: {length} bytes")]
|
|
InvalidRecordLength {
|
|
/// File offset where the record starts.
|
|
offset: u64,
|
|
/// The invalid length value read.
|
|
length: u32,
|
|
},
|
|
|
|
/// Generic record corruption with a descriptive reason.
|
|
#[error("Corrupt record at offset {offset}: {reason}")]
|
|
CorruptRecord {
|
|
/// File offset where corruption was detected.
|
|
offset: u64,
|
|
/// Human-readable description of the corruption.
|
|
reason: String,
|
|
},
|
|
|
|
/// Generic IO error.
|
|
#[error(transparent)]
|
|
IoGeneric(#[from] io::Error),
|
|
}
|
|
|
|
impl QuarantineError {
|
|
/// Helper to create an `Io` error variant.
|
|
pub fn io(path: impl Into<PathBuf>, source: io::Error) -> Self {
|
|
Self::Io { path: path.into(), source }
|
|
}
|
|
}
|