- Introduced `SwapManager` for buffering and writing incremental edits to sidecar `.kte.swp` files. - Implemented basic operations: insertion, deletion, split, join, and checkpointing. - Added recovery design doc (`docs/plans/swap-files.md`). - Updated editor initialization to integrate `SwapManager` instance for crash recovery across buffers.
5.5 KiB
5.5 KiB
Swap files for kte — design plan
Goals
- Preserve user work across crashes, power failures, and OS kills.
- Keep the editor responsive; avoid blocking the UI on disk I/O.
- Bound recovery time and swap size.
- Favor simple, robust primitives that work well on POSIX and macOS; keep Windows feasibility in mind.
Model overview
Per open buffer, maintain a sidecar swap journal next to the file:
- Path:
.<basename>.kte.swpin the same directory as the file (for unnamed/unsaved buffers, use a per‑session temp dir like$TMPDIR/kte/with a random UUID). - Format: append‑only journal of editing operations with periodic checkpoints.
- Crash safety: only append, fsync as per policy; checkpoint via write‑to‑temp + fsync + atomic rename.
File format (v1)
Header (fixed 64 bytes):
- Magic:
KTE_SWP\0(8 bytes) - Version: 1 (u32)
- Flags: bitset (u32) — e.g., compression, checksums, endian.
- Created time (u64)
- Host info hash (u64) — optional, for telemetry/debug.
- File identity: hash of canonical path (u64) and original file size+mtime (u64+u64) at start.
- Reserved/padding.
Records (stream after header):
- Each record: [type u8][len u24][payload][crc32 u32]
- Types:
CHKPT— full snapshot checkpoint of entire buffer content and minimal metadata (cursor pos, filetype). Payload optionally compressed. Written occasionally to cap replay time.INS— insert at (row, col) text bytes (text may contain newlines). Encoded with varints.DEL— delete length at (row, col). If spanning lines, semantics defined as in Buffer::delete_text.SPLIT,JOIN— explicit structural ops (optional; can be expressed via INS/DEL).META— update metadata (e.g., filetype, encoding hints).
Durability policy
Configurable knobs (sane defaults in parentheses):
- Time‑based flush: group edits and flush every 150–300 ms (200 ms).
- Operation count flush: after N ops (200).
- Idle flush: on 500 ms idle lull, flush immediately.
- Checkpoint cadence: after M KB of journal (512–2048 KB) or T seconds ( 30–120 s), whichever first.
- fsync policy:
always: fsync every flush (safest, slowest).grouped(default): fsync at most every 1–2 s or on idle/blur/quit.never: rely on OS flush (fastest, riskier).- On POSIX, prefer
fdatasyncwhen available; fall back tofsync.
Performance & threading
- Background writer thread per editor instance (shared) with a bounded MPSC queue of per‑buffer records.
- Each Buffer has a small in‑memory journal buffer; UI thread enqueues ops (non‑blocking) and may coalesce adjacent inserts/deletes.
- Writer batch‑writes records to the swap file, computes CRCs, and decides checkpoint boundaries.
- Backpressure: if the queue grows beyond a high watermark, signal the
UI to start coalescing more aggressively and slow enqueue (never block
hard editing path; at worst drop optional
META).
Recovery flow
On opening a file:
- Detect swap sidecar
.<basename>.kte.swp. - Validate header, iterate records verifying CRCs.
- Compare recorded original file identity against actual file; if mismatch, warn user but allow recovery (content wins).
- Reconstruct buffer: start from the last good
CHKPT(if any), then replay subsequent ops. If trailing partial record encountered (EOF mid‑record), truncate at last good offset. - Present a choice: Recover (load recovered buffer; keep the swap file until user saves) or Discard (delete swap file and open clean file).
Stability & corruption mitigation
- Append‑only with per‑record CRC32 guards against torn writes.
- Atomic checkpoint rotation: write
.<basename>.kte.swp.tmp, fsync, then rename over old.swp. - Size caps: rotate or compact when
.swpexceeds a threshold (e.g., 64–128 MB). Compaction creates a fresh file with a single checkpoint. - Low‑disk‑space behavior: on write failures, surface a non‑modal warning and temporarily fall back to in‑memory only; retry opportunistically.
Security considerations
- Swap files mirror buffer content, which may be sensitive. Options:
- Configurable location (same dir vs.
$XDG_STATE_HOME/kte/swap). - Optional per‑file encryption (future work) using OS keychain.
- Ensure permissions are 0600.
- Configurable location (same dir vs.
Interoperability & UX
- Use a distinctive extension
.kte.swpto avoid conflicts with other editors. - Status bar indicator when swap is active; commands to purge/compact.
- On save: do not delete swap immediately; keep until the buffer is clean and idle for a short grace period (allows undo of accidental external changes).
Implementation plan (staged)
- Minimal journal writer (append‑only INS/DEL) with grouped fsync; single per‑editor writer thread.
- Reader/recovery path with CRC validation and replay.
- Checkpoints + atomic rotation; compaction path.
- Config surface and UI prompts; telemetry counters.
- Optional compression and advanced coalescing.
Defaults balancing performance and stability
- Grouped flush with fsync every ~1 s or on idle/quit.
- Checkpoint every 1 MB or 60 s.
- Bounded queue and batch writes to minimize syscalls.
- Immediate flush on critical events (buffer close, app quit, power source change on laptops if detectable).