forked from lei.chen/myxce
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
|
|
@ -118,12 +118,37 @@ int restore_job(cluster_t* c, uint64_t jobid_unused,
|
|||
lock(c->jobmgr, RTTI_LOCK_READ);
|
||||
int found = key_find(c->jobmgr, (void*)j.jobid, (void**)&existing);
|
||||
unlock(c->jobmgr);
|
||||
if (found > 0 && existing != NULL)
|
||||
if (found > 0 && existing != NULL) {
|
||||
/* j was fully decoded above (submit/allocs/pidinfo/pend_reasons all
|
||||
* populated by decode_Job) and create_job() is never reached on this
|
||||
* path, so ownership never moved -- every decoded pointer is still
|
||||
* ours to release here (ha-entity-snapshots task 5.2: fixes the
|
||||
* pre-existing decode leak on this early-return path, called out in
|
||||
* tasks.md's Implementation Notes). */
|
||||
free_Job(&j);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
job_t* job = create_job(c, &j);
|
||||
int saved_rc = job ? 0 : -errno;
|
||||
|
||||
/* create_job() (jobj.c job_init) takes ownership of j.allocs/j.pidinfo/
|
||||
* j.pend_reasons via MOVE_PTR (nulling them here on success) and makes
|
||||
* its OWN deduplicated copies of j.submit/j.ckptinfo (makeJobsub/
|
||||
* makeString) -- so j.submit, the one field decode_Job always populates
|
||||
* that job_init never moves, is never released by create_job and must
|
||||
* always be freed here. free_Job() null-checks each of its four fields
|
||||
* before freeing (submit/allocs/pidinfo/pend_reasons), so calling it
|
||||
* unconditionally is safe whether create_job succeeded (three of the
|
||||
* four already nulled by the MOVE_PTR above) or failed before job_init
|
||||
* ran any MOVE_PTR (none nulled, all four correctly released) -- never a
|
||||
* double free either way (ha-entity-snapshots task 5.2, pre-existing
|
||||
* leak noted in tasks.md's Implementation Notes). */
|
||||
free_Job(&j);
|
||||
|
||||
job_t* job = create_job(c, &j);
|
||||
if (!job)
|
||||
return -errno;
|
||||
return saved_rc;
|
||||
|
||||
/* ha-convergence-recovery (3.3): tag the restored job with its submit
|
||||
* entry's LSN so the egress watermark predicate can decide its commit
|
||||
* status (job_submit_committed). On a promoted primary, replayed entries
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue