fix(record): replay past a crash-truncated trailing record - #2836
Conversation
`RecordingReader::next_entry` already treats a torn *length prefix* as a
clean end-of-stream (`UnexpectedEof` -> `Ok(None)`), matching the crate's
crash-survivability goal (see `truncated_file_no_footer`). But a torn
record *body* was mapped to a hard error via `.wrap_err("truncated
record")?`.
A crash (SIGKILL / Ctrl-C) can flush a record's 4-byte length prefix but
only part of the body that follows, since `RecordingWriter` buffers
through a `BufWriter`. In that case replay of an otherwise fully
recoverable recording failed outright, discarding every complete record
before the torn tail — the boundary was inconsistent: 1-3 stray prefix
bytes returned `Ok(None)`, a complete prefix plus a partial body errored.
Map `UnexpectedEof` on the body read to `Ok(None)` too, so every
fully-written record still replays and iteration stops gracefully at the
torn tail. Corruption *within* a complete record is unaffected: it is
still rejected by the existing bounds checks in `read_array` /
`read_slice` (covered by `corrupt_node_id_len_returns_err_not_panic` and
`corrupt_event_bytes_len_returns_err_not_panic`).
Adds a regression test `truncated_record_body_stops_gracefully`.
This change is machine-generated by Claude (an AI assistant) as part of an
automated code-review run, and reviewed against current origin/main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMv79fayzGF9mDV7vPMQN7
|
😎 Merged successfully - details. |
Automated code-review run — summaryThis is the umbrella summary for a machine-generated review run (branched off current PRs opened, by categoryCorrectness
Optimization
Documentation
Simplification
Each PR was verified with Findings considered but rejected (one-line reasons)
Generated by Claude Code |
|
🤖 Automated review by Claude — fully automated, no human in the loop. Reviewed the diff in Mapping The regression test Generated by Claude Code |
Issue
RecordingReader::next_entryis designed for crash survivability: a torn length prefix is already treated as clean end-of-stream (UnexpectedEof→Ok(None)), which is exactly what thetruncated_file_no_footertest exercises. But a torn record body was mapped to a hard error via.wrap_err("truncated record")?.A crash (SIGKILL / Ctrl-C) can flush a record's 4-byte length prefix but only part of the body that follows, because
RecordingWriterbuffers through aBufWriter. In that case, replay of an otherwise fully recoverable recording failed outright — discarding every complete record before the torn tail. The boundary was inconsistent:Ok(None)(graceful)Err(whole replay lost)Fix
Map
UnexpectedEofon the body read toOk(None)as well, so every fully-written record still replays and iteration stops gracefully at the torn tail — matching the length-prefix handling directly above it.Corruption within a complete record is unaffected: it is still rejected by the existing bounds checks in
read_array/read_slice, covered bycorrupt_node_id_len_returns_err_not_panicandcorrupt_event_bytes_len_returns_err_not_panic.Validation
truncated_record_body_stops_gracefully(writes a complete record, then a length prefix followed by a partial body; asserts the complete record replays and the torn tail yieldsOk(None)).cargo test -p dora-recording --lib→ 15 passed.cargo clippy -p dora-recording --all-targets -- -D warningsclean.origin/main. Please review carefully before merging.🤖 Generated with Claude Code
https://claude.ai/code/session_01TMv79fayzGF9mDV7vPMQN7
Generated by Claude Code