feat(ha-entity-snapshots): restore-before-advance load_snapshot implementation (task 5.2)
Replaces the naive task-5.1 stopgap with the real restore contract: detects legacy vs entity-bearing checkpoints by remaining file size; restores every job then every queue status via restore_job/ restore_queue_state, in file order, with bounds-checked length-prefixed reads; only after every entity restores successfully does current_lsn/checkpoint_lsn commit to the checkpoint's position. Every failure path (bounds check, decode, restore error, unrecognized format_version) returns before that single commit point -- position is provably untouched on failure, never partially advanced. Also fixes the pre-existing restore_job decode leak (job.c): job_init deep-copies submit (never nulled) and MOVE_PTRs allocs/pidinfo/ pend_reasons (nulled on the one path that runs) -- free_Job(&j) is safe unconditionally on every reachable return path (EEXIST early-return, create_job success, create_job failure), verified against the exact generated free_*_ptr null-check guards. Two pre-existing test_config_repl.c wire-protocol tests needed their synthetic garbage snapshot bodies updated to a well-formed empty-entity tail, since load_snapshot now genuinely parses past the legacy header (previously ignored); their actual assertions (chunk-reassembly correctness) are unchanged. This is the exact function/contract that caused task 2.2's rejection (position advancing before restore validated). Independent review traced the memory-safety fix through the generated capnp code field-by- field, confirmed the test_config_repl.c change doesn't weaken any assertion, and confirmed restore_queue_state's ENOENT-aborts-the-whole- checkpoint behavior is faithful to design.md's unconditional contract (flagged as a watch-item for task 7.1's soak, not a defect). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDYFbrgyDHbvNYcxApKhrX
This commit is contained in:
parent
a8e300d96a
commit
9fdb6778d4
4 changed files with 297 additions and 56 deletions
|
|
@ -69,7 +69,7 @@
|
|||
- Observable: the RED test from 4.2 passes
|
||||
- _Requirements: 3.1, 3.2, 3.3_
|
||||
|
||||
- [ ] 5. Core: entity restoration on load
|
||||
- [x] 5. Core: entity restoration on load
|
||||
- [x] 5.1 RED: restore-before-advance, failure-safety, and legacy-format tests
|
||||
- Assert loading a checkpoint with N jobs + M queues restores all of them before the member's
|
||||
position advances
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
- _Requirements: 2.1, 2.2, 2.3, 2.4, 5.1, 5.2_
|
||||
- _Boundary: Persistence Layer (load_snapshot)_
|
||||
- _Depends: 2.2, 3.2_
|
||||
- [ ] 5.2 Restore entities before advancing the member's position, with legacy-format detection
|
||||
- [x] 5.2 Restore entities before advancing the member's position, with legacy-format detection
|
||||
- Detect whether a checkpoint carries entity content or is a legacy position-only file
|
||||
- Restore every job then every queue status via the existing restore functions, in file order;
|
||||
on any failure, stop and leave position unchanged
|
||||
|
|
@ -134,3 +134,5 @@
|
|||
- (4.1) `snapshot_inflight_lsn[peer_idx]` clears to 0 BEFORE `snapshot_inflight[peer_idx]` clears to 0 in every path (completion + all 3 failure sites in `ha_snapshot_send_dispatch`). Task 4.3's `rotate_snapshots` consultation must therefore treat `snapshot_inflight_lsn[i] != 0` as the "protected" signal directly (checkpoint LSNs are never 0), not "check flag==1 first, then trust LSN" -- there's a narrow window where the flag is still 1 but the LSN has already cleared.
|
||||
- (execution note, after 4.1) Remaining tasks split by risk given a mid-session spend-limit hit: low-risk/small/isolated tasks (4.2, 4.3, 7.1, 8.1) executed directly in the main context with self-review (build + test + boundary/TBD/secrets grep), no subagent dispatch. High-risk tasks (5.1/5.2 `load_snapshot` restore -- the exact function that caused task 2.2's rejection; 6.1 wiring into the live `ha_handle_snapshot_chunk` production path) keep the full implementer + independent-reviewer subagent cycle, since that adversarial review already caught a real live-hazard bug once this spec (task 2.2) and the remaining risk is concentrated there, not spread evenly.
|
||||
- (5.1) `persist_load_snapshot`'s wrapper currently maps `*out_entities_restored = (rc==0)` -- a deliberately naive stopgap so the 3 RED tests fail for the right reasons. `load_snapshot`'s real body is completely untouched (still watermark-only, byte-identical to before this task). Task 5.2 must replace the wrapper's naive mapping wholesale with real format-detection + restore-then-gate-position logic; it owns turning `load_snapshot` from a no-op-on-entities function into the real restore path.
|
||||
- (5.2) `restore_queue_state`'s `-ENOENT` ("queue not yet built from config") aborts the ENTIRE checkpoint restore, including already-successfully-restored jobs earlier in the same file -- this is design-compliant (design.md/Req 2.3 are unconditional: any restore failure leaves position unchanged) and not a defect, but it is a real residual availability risk: a benign config-lag on the destination member (a queue the source references not yet locally provisioned) would make catch-up fail outright rather than skip-and-continue. WATCH-ITEM for task 7.1's chaotic soak: if a catch-up round gets stuck/never completes, this exact code path (queue.c's restore_queue_state ENOENT -> persist.c's load_snapshot_entities abort) is the first place to look.
|
||||
- (5.2) Test-only, non-blocking: the destination fixture's `snapent_teardown` does not release job_t objects added via `restore_job` (only the ones seeded at fixture creation) -- a pre-existing category of test-fixture-teardown gap (same class as tasks.md's (3.2) note), now slightly enlarged since load-path tests exercise restore_job against a destination fixture. Not production code; extend snapent_teardown if this file keeps growing.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue