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
|
|
@ -41,4 +41,30 @@ int kvmgr_capture_keys(var kvmgr,
|
|||
slot_key_t** keys_out,
|
||||
size_t* n_keys_out);
|
||||
|
||||
/*
|
||||
* kvmgr_reserve — job-array-single-wal-record task 7.1 (bug-1443).
|
||||
*
|
||||
* Pre-grows kvmgr's backing value store (the internal xzarray_type
|
||||
* instance) for `n` additional entries, via the new xzarray_reserve()
|
||||
* primitive (zarray.h/zarray.c) -- zero changes to any existing function in
|
||||
* either file. Closes the DOMINANT, N-scaling failure mode for a bulk
|
||||
* key_add() loop (e.g. restore_job_array()'s bulk-insert pass, job.c): the
|
||||
* large realloc() reachable through key_add -> xkvmgr_add -> vstore ->
|
||||
* xzarray_ensure_items when the value store must grow.
|
||||
*
|
||||
* This narrows, but does NOT eliminate, that failure surface: key_add()'s
|
||||
* own kvtab_add() call (kv.c) still has its own separate, tiny per-entry
|
||||
* hash-chain-node allocation, and a content-based -EEXIST collision remains
|
||||
* theoretically possible -- both are far rarer and non-N-scaling compared
|
||||
* to the growth cycle this call structurally avoids.
|
||||
*
|
||||
* Caller MUST hold the kvmgr write lock for the full duration of this call
|
||||
* -- identical contract to kvmgr_capture_keys() above.
|
||||
*
|
||||
* Returns 1 on success (capacity already sufficient, or successfully
|
||||
* grown). Returns -EINVAL for a NULL kvmgr or negative n. Returns -ENOMEM
|
||||
* if the underlying growth allocation fails.
|
||||
*/
|
||||
int kvmgr_reserve(var kvmgr, int n);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -43,4 +43,62 @@ typedef struct {
|
|||
|
||||
extern typeinfo_t xzarray_type;
|
||||
|
||||
/* job-array-single-wal-record task 7.1 (bug-1443): pre-grows an xzarray_type
|
||||
* instance's backing value store for `n` additional entries, by calling the
|
||||
* existing, unmodified xzarray_ensure_items(a, a->count + n) internally --
|
||||
* zero changes to xzarray_ensure_items or any other existing function in
|
||||
* this file. This is the primitive kvmgr_reserve() (kvmgr.c/kvmgr.h) uses to
|
||||
* pre-grow c->jobmgr's backing xzarray_type instance (kvm->data) before a
|
||||
* bulk-insert pass, closing the DOMINANT, N-scaling failure mode for that
|
||||
* pass (the large realloc() reachable through vstore ->
|
||||
* xzarray_ensure_items). It does NOT make every subsequent vstore() call
|
||||
* unconditionally infallible: this only covers the value-store side --
|
||||
* xkvmgr_type's separate kvtab key->index table (kv.c) has its own,
|
||||
* independent, much smaller, non-N-scaling per-entry allocation that this
|
||||
* function does not and cannot pre-reserve.
|
||||
*
|
||||
* Caller MUST hold the same write lock the subsequent bulk vstore()/
|
||||
* key_add() calls will be made under, for the full duration of this call --
|
||||
* identical contract to kvmgr_capture_keys() (kvmgr.h).
|
||||
*
|
||||
* Returns 1 on success (capacity already sufficient, or successfully
|
||||
* grown). Returns -EINVAL for a NULL array or negative n. Returns -ENOMEM if
|
||||
* the underlying growth allocation fails. */
|
||||
extern int xzarray_reserve(var array, int n);
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/* Test-only fault injection for xzarray_reserve() (job-array-single-wal-
|
||||
* record task 7.1 / bug-1443). When engaged, EVERY subsequent
|
||||
* xzarray_reserve() call -- across any xzarray_type instance, including one
|
||||
* wrapped inside an xkvmgr_type via kvmgr_reserve() -- reports a simulated
|
||||
* allocation failure (-ENOMEM) without touching the real array. File-scope,
|
||||
* not per-instance: mirrors job.c's own
|
||||
* job_array_apply_test_set_bulk_insert_force_fail_at() convention (this
|
||||
* feature's own established #ifdef XCE_TESTING test-only-symbol style).
|
||||
*
|
||||
* NOTE on why these declarations, unlike job.c's own #ifdef XCE_TESTING
|
||||
* seams, are guarded here but their DEFINITIONS (zarray.c) are NOT: zarray.c
|
||||
* is compiled exactly once, as part of the shared `libxce` static_library()
|
||||
* target (src/lib/meson.build), without -DXCE_TESTING -- unlike job.c, which
|
||||
* every consumer (including test binaries) recompiles from source with its
|
||||
* own c_args. Guarding the .c definitions the same way would silently drop
|
||||
* them from libxce.a in every build, including test builds that need them
|
||||
* (confirmed empirically). So the underlying symbols are unconditionally
|
||||
* present in the compiled library; only these DECLARATIONS are gated, which
|
||||
* keeps them un-prototyped (hence uncallable without a compiler error) from
|
||||
* any translation unit that does not itself define XCE_TESTING -- i.e. every
|
||||
* production source file. */
|
||||
extern void xzarray_reserve_test_set_force_fail(void);
|
||||
extern void xzarray_reserve_test_clear_force_fail(void);
|
||||
|
||||
/* Test-only introspection: returns the CURRENT backing-store capacity (item
|
||||
* slots, not bytes) of an xzarray_type instance. xzarray_t is fully opaque
|
||||
* outside zarray.c, so this is the only way a test can observe whether a
|
||||
* reservation actually grew capacity ahead of need, as opposed to deferring
|
||||
* growth to the next vstore() call. Returns -EINVAL for a NULL array. See
|
||||
* the NOTE above -- declaration-gated, not definition-gated, for the same
|
||||
* shared-library build reason. */
|
||||
extern int xzarray_test_get_caps(var array);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue