Round-seven fixes: swap name room for .tmp; input and notice details

- The shortened fallback swap name was capped at exactly NAME_MAX, so
  compaction's '<journal>.tmp' failed with ENAMETOOLONG on every
  checkpoint and the journal grew by a full copy of the buffer each
  time. Cap it at NAME_MAX - 4.
- A lone UTF-8 lead byte (8-bit Meta terminals) waited a second for
  continuation bytes on every key; wait 250 ms. An invalid byte now ends
  a pending ESC/C-k prefix instead of leaving it for the next key.
- Journal notices are shown after a deferred open's own status (which
  used to overwrite them), all pending ones at once, and the lock-out
  notice is not repeated after every save while the lock is still held.

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 78d78f6f54
commit b9f91b0ed2
6 changed files with 65 additions and 20 deletions
+17 -5
View File
@@ -206,9 +206,12 @@ compute_swap_path_for_filename(const std::string &filename)
const std::string suffix = "." + hex_u64(fnv1a64(key)) + ".swp";
// A long basename made even this name exceed NAME_MAX (255), so the
// journal could never be created. Shorten only such names (the hash
// keeps them distinct), on a UTF-8 boundary.
if (base.size() + suffix.size() > 255) {
std::size_t keep = 255 - suffix.size();
// keeps them distinct), on a UTF-8 boundary, leaving room for the
// ".tmp" sibling that compaction writes (at exactly 255 compaction
// failed on every checkpoint and the journal grew without bound).
constexpr std::size_t kMaxName = 255 - 4; // strlen(".tmp")
if (base.size() + suffix.size() > kMaxName) {
std::size_t keep = kMaxName - suffix.size();
while (keep > 0 && (static_cast<unsigned char>(base[keep]) & 0xC0) == 0x80)
--keep;
base.resize(keep);
@@ -1439,8 +1442,12 @@ SwapManager::process_one(const Pending &p)
{
std::lock_guard<std::mutex> lg(mtx_);
ctxp->locked_out = true;
notify_user_locked_("File is being edited in another kte; crash recovery is off here (" +
path + ")");
if (!ctxp->lockout_notified) {
ctxp->lockout_notified = true;
notify_user_locked_(
"File is being edited in another kte; crash recovery is off here (" +
path + ")");
}
}
report_error(open_err, p.buf);
return; // not an I/O failure; later records are skipped
@@ -1453,6 +1460,11 @@ SwapManager::process_one(const Pending &p)
mark_gap();
return;
}
{
// Holding the journal now; a later lock-out is news again.
std::lock_guard<std::mutex> lg(mtx_);
ctxp->lockout_notified = false;
}
if (p.payload.size() > kMaxRecordPayload) {
// Recorders keep payloads within the limit, so this is a bug, not an
// I/O failure: do not count it against the circuit breaker.