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:
@@ -3,9 +3,11 @@
|
||||
#include "Buffer.h"
|
||||
#include "Swap.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
static void
|
||||
@@ -37,6 +39,30 @@ buffer_bytes_via_views(const Buffer &b)
|
||||
}
|
||||
|
||||
|
||||
static std::vector<std::uint8_t>
|
||||
record_types_from_bytes(const std::string &bytes)
|
||||
{
|
||||
std::vector<std::uint8_t> types;
|
||||
if (bytes.size() < 64)
|
||||
return types;
|
||||
std::size_t off = 64;
|
||||
while (off < bytes.size()) {
|
||||
if (bytes.size() - off < 8)
|
||||
break;
|
||||
const std::uint8_t type = static_cast<std::uint8_t>(bytes[off + 0]);
|
||||
const std::uint32_t len = (std::uint32_t) static_cast<std::uint8_t>(bytes[off + 1]) |
|
||||
((std::uint32_t) static_cast<std::uint8_t>(bytes[off + 2]) << 8) |
|
||||
((std::uint32_t) static_cast<std::uint8_t>(bytes[off + 3]) << 16);
|
||||
const std::size_t crc_off = off + 4 + (std::size_t) len;
|
||||
if (crc_off + 4 > bytes.size())
|
||||
break;
|
||||
types.push_back(type);
|
||||
off = crc_off + 4;
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
|
||||
TEST (SwapReplay_RecordFlushReopenReplay_ExactBytesMatch)
|
||||
{
|
||||
const std::string path = "./.kte_ut_swap_replay_1.txt";
|
||||
@@ -111,4 +137,91 @@ TEST (SwapReplay_TruncatedLog_FailsSafely)
|
||||
std::remove(path.c_str());
|
||||
std::remove(swap_path.c_str());
|
||||
std::remove(trunc_path.c_str());
|
||||
}
|
||||
|
||||
|
||||
TEST (SwapReplay_Checkpoint_Midstream_ExactBytesMatch)
|
||||
{
|
||||
const std::string path = "./.kte_ut_swap_replay_chkpt_1.txt";
|
||||
std::remove(path.c_str());
|
||||
write_file_bytes(path, "base\nline2\n");
|
||||
|
||||
Buffer b;
|
||||
std::string err;
|
||||
ASSERT_TRUE(b.OpenFromFile(path, err));
|
||||
|
||||
kte::SwapManager sm;
|
||||
sm.Attach(&b);
|
||||
b.SetSwapRecorder(sm.RecorderFor(&b));
|
||||
|
||||
// Some edits, then an explicit checkpoint, then more edits.
|
||||
b.insert_text(0, 0, std::string("X"));
|
||||
sm.Checkpoint(&b);
|
||||
b.insert_text(1, 0, std::string("ZZ"));
|
||||
b.delete_text(0, 0, 1);
|
||||
|
||||
sm.Flush(&b);
|
||||
const std::string swap_path = kte::SwapManager::ComputeSwapPathForTests(b);
|
||||
const std::string expected = buffer_bytes_via_views(b);
|
||||
|
||||
b.SetSwapRecorder(nullptr);
|
||||
sm.Detach(&b);
|
||||
|
||||
Buffer b2;
|
||||
ASSERT_TRUE(b2.OpenFromFile(path, err));
|
||||
ASSERT_TRUE(kte::SwapManager::ReplayFile(b2, swap_path, err));
|
||||
ASSERT_EQ(buffer_bytes_via_views(b2), expected);
|
||||
|
||||
std::remove(path.c_str());
|
||||
std::remove(swap_path.c_str());
|
||||
}
|
||||
|
||||
|
||||
TEST (SwapCompaction_RewritesToSingleCheckpoint)
|
||||
{
|
||||
const std::string path = "./.kte_ut_swap_compact_1.txt";
|
||||
std::remove(path.c_str());
|
||||
write_file_bytes(path, "base\n");
|
||||
|
||||
Buffer b;
|
||||
std::string err;
|
||||
ASSERT_TRUE(b.OpenFromFile(path, err));
|
||||
|
||||
kte::SwapManager sm;
|
||||
kte::SwapConfig cfg;
|
||||
cfg.checkpoint_bytes = 0;
|
||||
cfg.checkpoint_interval_ms = 0;
|
||||
cfg.compact_bytes = 1; // force compaction on any checkpoint
|
||||
sm.SetConfig(cfg);
|
||||
|
||||
sm.Attach(&b);
|
||||
b.SetSwapRecorder(sm.RecorderFor(&b));
|
||||
|
||||
// Ensure there is at least one non-checkpoint record on disk first.
|
||||
b.insert_text(0, 0, std::string("abc"));
|
||||
sm.Flush(&b);
|
||||
|
||||
// Now emit a checkpoint; compaction should rewrite the file to just that checkpoint.
|
||||
sm.Checkpoint(&b);
|
||||
sm.Flush(&b);
|
||||
|
||||
const std::string swap_path = kte::SwapManager::ComputeSwapPathForTests(b);
|
||||
const std::string expected = buffer_bytes_via_views(b);
|
||||
|
||||
// Close journal.
|
||||
b.SetSwapRecorder(nullptr);
|
||||
sm.Detach(&b);
|
||||
|
||||
const std::string bytes = read_file_bytes(swap_path);
|
||||
const std::vector<std::uint8_t> types = record_types_from_bytes(bytes);
|
||||
ASSERT_EQ(types.size(), (std::size_t) 1);
|
||||
ASSERT_EQ(types[0], (std::uint8_t) kte::SwapRecType::CHKPT);
|
||||
|
||||
Buffer b2;
|
||||
ASSERT_TRUE(b2.OpenFromFile(path, err));
|
||||
ASSERT_TRUE(kte::SwapManager::ReplayFile(b2, swap_path, err));
|
||||
ASSERT_EQ(buffer_bytes_via_views(b2), expected);
|
||||
|
||||
std::remove(path.c_str());
|
||||
std::remove(swap_path.c_str());
|
||||
}
|
||||
Reference in New Issue
Block a user