Introduce swap journaling crash recovery system with tests.
- Added detailed journaling system (`SwapManager`) for crash recovery, including edit recording and replay. - Integrated recovery prompts for handling swap files during file open flows. - Implemented swap file cleanup, checkpointing, and compaction mechanisms. - Added extensive unit tests for swap-related behaviors such as recovery prompts, file pruning, and corruption handling. - Updated CMake to include new test files.
This commit is contained in:
54
Swap.h
54
Swap.h
@@ -32,6 +32,18 @@ struct SwapConfig {
|
||||
// Grouping and durability knobs (stage 1 defaults)
|
||||
unsigned flush_interval_ms{200}; // group small writes
|
||||
unsigned fsync_interval_ms{1000}; // at most once per second
|
||||
|
||||
// Checkpoint/compaction knobs (stage 2 defaults)
|
||||
// A checkpoint is a full snapshot of the buffer content written as a CHKPT record.
|
||||
// Compaction rewrites the swap file to contain just the latest checkpoint.
|
||||
std::size_t checkpoint_bytes{1024 * 1024}; // request checkpoint after this many queued edit-bytes
|
||||
unsigned checkpoint_interval_ms{60000}; // request checkpoint at least this often while editing
|
||||
std::size_t compact_bytes{8 * 1024 * 1024}; // compact on checkpoint once journal grows beyond this
|
||||
|
||||
// Cleanup / retention (best-effort)
|
||||
bool prune_on_startup{true};
|
||||
unsigned prune_max_age_days{30};
|
||||
std::size_t prune_max_files{2048};
|
||||
};
|
||||
|
||||
// SwapManager manages sidecar swap files and a single background writer thread.
|
||||
@@ -45,13 +57,36 @@ public:
|
||||
void Attach(Buffer *buf);
|
||||
|
||||
// Detach and close journal.
|
||||
void Detach(Buffer *buf);
|
||||
// If remove_file is true, the swap file is deleted after closing.
|
||||
// Intended for clean shutdown/close flows.
|
||||
void Detach(Buffer *buf, bool remove_file = false);
|
||||
|
||||
// Reset (truncate-by-delete) the journal for a buffer after a clean save.
|
||||
// Best-effort: closes the current fd, deletes the swap file, and resumes recording.
|
||||
void ResetJournal(Buffer &buf);
|
||||
|
||||
// Best-effort pruning of old swap files under the swap directory.
|
||||
// Never touches non-`.swp` files.
|
||||
void PruneSwapDir();
|
||||
|
||||
// Block until all currently queued records have been written.
|
||||
// If buf is non-null, flushes all records (stage 1) but is primarily intended
|
||||
// for tests and shutdown.
|
||||
void Flush(Buffer *buf = nullptr);
|
||||
|
||||
// Request a full-content checkpoint record for one buffer (or all buffers if buf is null).
|
||||
// This is best-effort and asynchronous; call Flush() if you need it written before continuing.
|
||||
void Checkpoint(Buffer *buf = nullptr);
|
||||
|
||||
|
||||
void SetConfig(const SwapConfig &cfg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(mtx_);
|
||||
cfg_ = cfg;
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
|
||||
// Obtain a per-buffer recorder adapter that emits records for that buffer.
|
||||
// The returned pointer is owned by the SwapManager and remains valid until
|
||||
// Detach(buf) or SwapManager destruction.
|
||||
@@ -67,6 +102,10 @@ public:
|
||||
// treat this as a recovery failure and surface `err`.
|
||||
static bool ReplayFile(Buffer &buf, const std::string &swap_path, std::string &err);
|
||||
|
||||
// Compute the swap path for a file-backed buffer by filename.
|
||||
// Returns empty string if filename is empty.
|
||||
static std::string ComputeSwapPathForFilename(const std::string &filename);
|
||||
|
||||
// Test-only hook to keep swap path logic centralized.
|
||||
// (Avoid duplicating naming rules in unit tests.)
|
||||
#ifdef KTE_TESTS
|
||||
@@ -114,6 +153,10 @@ private:
|
||||
|
||||
void RecordJoin(Buffer &buf, int row);
|
||||
|
||||
void RecordCheckpoint(Buffer &buf, bool urgent_flush);
|
||||
|
||||
void maybe_request_checkpoint(Buffer &buf, std::size_t approx_edit_bytes);
|
||||
|
||||
struct JournalCtx {
|
||||
std::string path;
|
||||
int fd{-1};
|
||||
@@ -121,6 +164,9 @@ private:
|
||||
bool suspended{false};
|
||||
std::uint64_t last_flush_ns{0};
|
||||
std::uint64_t last_fsync_ns{0};
|
||||
std::uint64_t last_chkpt_ns{0};
|
||||
std::uint64_t edit_bytes_since_chkpt{0};
|
||||
std::uint64_t approx_size_bytes{0};
|
||||
};
|
||||
|
||||
struct Pending {
|
||||
@@ -134,16 +180,22 @@ private:
|
||||
// Helpers
|
||||
static std::string ComputeSidecarPath(const Buffer &buf);
|
||||
|
||||
static std::string ComputeSidecarPathForFilename(const std::string &filename);
|
||||
|
||||
static std::uint64_t now_ns();
|
||||
|
||||
static bool ensure_parent_dir(const std::string &path);
|
||||
|
||||
static std::string SwapDirRoot();
|
||||
|
||||
static bool write_header(int fd);
|
||||
|
||||
static bool open_ctx(JournalCtx &ctx, const std::string &path);
|
||||
|
||||
static void close_ctx(JournalCtx &ctx);
|
||||
|
||||
static bool compact_to_checkpoint(JournalCtx &ctx, const std::vector<std::uint8_t> &chkpt_record);
|
||||
|
||||
static std::uint32_t crc32(const std::uint8_t *data, std::size_t len, std::uint32_t seed = 0);
|
||||
|
||||
static void put_le32(std::vector<std::uint8_t> &out, std::uint32_t v);
|
||||
|
||||
Reference in New Issue
Block a user