Swap: keep the journal replayable across failures and crashes
- Replay recovers every complete record before a torn final record (the normal result of a crash mid-append) and reports it through err, instead of rejecting the whole journal. SwapReplay_TruncatedLog is updated to the new behaviour. - A record that cannot be written (open/write failure, breaker open) marks the journal as having a gap. Later position-based records are dropped until a checkpoint re-establishes the content; recorders request one on the next edit (at most once per second). - A failed append is truncated back off the file, and a failed header write closes the file so the next attempt starts fresh. - Inserts over the 24-bit record limit are split into consecutive records; buffers too large to checkpoint are no longer snapshotted and rejected on every checkpoint. - Declining to delete an unreadable swap moves it to .corrupt so new edits are not appended behind the bad data. - NotifyFilenameChanged no longer writes a full checkpoint into the journal it is about to delete. Audit findings D7, P5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
@@ -386,8 +386,16 @@ Editor::ResolveRecoveryPrompt(const bool yes)
|
||||
return true;
|
||||
}
|
||||
if (kind == RecoveryPromptKind::DeleteCorruptSwap) {
|
||||
std::string kept_as;
|
||||
if (yes) {
|
||||
(void) std::remove(swp.c_str());
|
||||
} else {
|
||||
// Keep the unreadable journal for inspection, but out of the way:
|
||||
// left in place, the new session's records would be appended
|
||||
// behind the corrupt data and could never be replayed.
|
||||
kept_as = swp + ".corrupt";
|
||||
if (std::rename(swp.c_str(), kept_as.c_str()) != 0)
|
||||
kept_as.clear();
|
||||
}
|
||||
if (!OpenFile(req.path, err)) {
|
||||
SetStatus(err);
|
||||
@@ -395,7 +403,9 @@ Editor::ResolveRecoveryPrompt(const bool yes)
|
||||
}
|
||||
apply_pending_line(*this, req.line1);
|
||||
// Include a short hint that the swap was corrupt.
|
||||
if (!rerr_s.empty()) {
|
||||
if (!kept_as.empty()) {
|
||||
SetStatus("Opened " + req.path + " (unreadable swap kept as " + kept_as + ")");
|
||||
} else if (!rerr_s.empty()) {
|
||||
SetStatus("Opened " + req.path + " (swap unreadable)");
|
||||
} else {
|
||||
SetStatus("Opened " + req.path);
|
||||
|
||||
Reference in New Issue
Block a user