Swap: retry a journal's recovery checkpoint while idle

After a lost record the journal drops position-based records until a
full checkpoint. That checkpoint was only requested from later edits,
rate-limited to once a second; when the writer handled the failing
record before its edit asked for a checkpoint, the checkpoint failed as
well and consumed the slot, so edits made within the next second were
not recovered and, if the user stopped typing, the journal stayed behind
indefinitely. SwapManager::RetryGapCheckpoints(), called once per frame
from Editor::ProcessPendingOpens, requests it once the interval has
passed. This also made SwapReplay_LostRecord_ResyncsWithCheckpoint flaky
under load; the test now exercises the retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 09:16:04 -07:00
committed by Kyle Isom
parent b1b0b3de57
commit 1e312fcd0b
5 changed files with 46 additions and 0 deletions
+8
View File
@@ -6,7 +6,9 @@
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <chrono>
#include <filesystem>
#include <thread>
#include <string>
#include <vector>
@@ -313,6 +315,12 @@ TEST(SwapReplay_LostRecord_ResyncsWithCheckpoint)
sm.Flush(&b);
b.insert_text(0, 3, std::string("D"));
sm.Flush(&b);
// If the writer handled B before its edit asked for a checkpoint, that
// checkpoint failed too and the next attempt is rate-limited; the
// per-frame retry then brings the journal back in step without an edit.
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
sm.RetryGapCheckpoints();
sm.Flush(&b);
const std::string expected = buffer_bytes_via_views(b);
ASSERT_EQ(expected, std::string("ABCDbase\n"));
// Replay the journal as it stands mid-session (as after a crash); Detach