fix(job-array-single-wal-record): close bug-1443's bulk-insert partial-visibility gap
restore_job_array()'s bulk-insert pass could fail partway through (key_add() on realloc growth exhaustion), leaving an already-inserted prefix permanently visible in c->jobmgr but excluded from out_new_jobs -- so those elements existed and were individually addressable but never reached HOOK_DETAIL_JOB_NEW, never scheduled or pushed, not on this attempt or any later successful retry. A literal violation of Requirements 2.3 and 9.4, tracked as bug-1443. Adds kvmgr_reserve()/xzarray_reserve(), new purely-additive primitives in shared kvmgr.c/zarray.c (following the existing kvmgr_capture_keys() template -- bare extern function, no RTTI vtable slot, caller-holds- write-lock contract), that pre-grow c->jobmgr's backing store before the bulk-insert pass begins, closing the dominant N-scaling failure trigger structurally. Found a genuine off-by-one in the existing, unmodified xzarray_ensure_items(): its strict less-than sufficiency check means reserving exactly array_size and then performing array_size real stores still triggers one more growth cycle on the last store. Reserving array_size + 1 is the exact minimal correct compensation, independently re-derived and confirmed by review with concrete arithmetic. For the now-much-narrower residual case (kvtab_add()'s own small per-entry allocation, or a content collision -- both non-N-scaling, honestly documented as still theoretically possible): restore_job_array() now populates out_new_jobs with whatever prefix was genuinely inserted before a failure, and apply_committed_entry() fires HOOK_DETAIL_JOB_NEW for that non-empty partial prefix even when the overall return is negative, while still propagating the negative rc so the LSN correctly halts and the Live Apply Resource Fence still engages for the remainder. A genuinely-inserted element is never left un-pushed again. Task 7.1 of job-array-single-wal-record (Requirements 2.3, 9.4, 9.6).
This commit is contained in:
parent
575f928bbc
commit
dcf9f39c22
13 changed files with 910 additions and 36 deletions
|
|
@ -2820,16 +2820,44 @@ int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
|
|||
* and only, what the hook fire below carries — Req 3.4); -EEXIST =
|
||||
* every index in the record already existed (idempotent
|
||||
* whole-record no-op, Req 3.3) — no hook; a negative errno = a
|
||||
* genuine construction/resource-exhaustion failure (Req 9.4) — no
|
||||
* hook, propagated so persist_apply_pump_run() (task 4.1) can halt
|
||||
* and fence on it rather than silently skipping it. */
|
||||
* genuine construction/resource-exhaustion failure (Req 9.4) —
|
||||
* propagated so persist_apply_pump_run() (task 4.1) can halt and
|
||||
* fence on it rather than silently skipping it. **Task 7.1
|
||||
* (bug-1443) update**: a negative return no longer unconditionally
|
||||
* means "no hook" — restore_job_array()'s bulk-insert-phase residual
|
||||
* gap can leave a genuinely, permanently inserted prefix in
|
||||
* out_new_jobs even on failure (job.c's doc comment, "Residual
|
||||
* atomicity gap"); see below. */
|
||||
xlist_t out_new_jobs;
|
||||
memset(&out_new_jobs, 0, sizeof(out_new_jobs));
|
||||
int rc = restore_job_array(c, payload, len, lsn, &out_new_jobs);
|
||||
if (rc == -EEXIST)
|
||||
return 0; /* idempotent re-apply (replay / resync) — no hook */
|
||||
if (rc < 0)
|
||||
|
||||
if (rc < 0) {
|
||||
/* job-array-single-wal-record task 7.1 (bug-1443 partial-prefix
|
||||
* fix): out_new_jobs is untouched (count == 0, from the memset
|
||||
* above) for a reservation or scratch-build-phase failure —
|
||||
* nothing was ever inserted, so no hook fires, unchanged from
|
||||
* before this task. But for the narrower bulk-insert-phase
|
||||
* residual gap, out_new_jobs now carries a non-empty, genuinely
|
||||
* and permanently inserted prefix (job.c) that must still get
|
||||
* its one HOOK_DETAIL_JOB_NEW fire — a job that is really in
|
||||
* c->jobmgr must never be silently un-notified. The overall
|
||||
* apply is still, correctly, reported as failed via the
|
||||
* negative rc returned below, so persist_apply_pump_run() halts
|
||||
* the LSN and the Live Apply Resource Fence engages for the
|
||||
* un-constructed remainder (Req 2.3, 9.4, 9.6). */
|
||||
if (out_new_jobs.count > 0) {
|
||||
t_apply_seam_notifying = true;
|
||||
xce_run_hooks(&c->hooksys, XCE_CONTROLLER_HOOK_JOB,
|
||||
HOOK_AFTER_EVENT, HOOK_DETAIL_JOB_NEW, c,
|
||||
&out_new_jobs);
|
||||
t_apply_seam_notifying = false;
|
||||
}
|
||||
xlist_fini(&out_new_jobs);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Exactly ONE fire carrying every newly created element in one
|
||||
* list — never once per element (Req 3.4). out_new_jobs is already
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue