feat(job-array-single-wal-record): implement single-record array append/commit

Adds persist_submit_job_array_async(): encodes one JobSubmit-shaped
payload (base jobid, array size, one shared spec), appends exactly
one WAL_JOB_ARRAY_SUBMIT entry, and registers exactly one commit
continuation regardless of array size -- reuses the existing
job_commit_adapter_ctx_t/job_commit_resolve_adapter machinery, no
per-element loop. Not yet wired into handle_jobsubmit (task 2.2).

Also fixes a test-helper bug (WAL offset back-computed from the
post-append cursor, only valid for zero-payload entries) that
corrupted state across tests when exercising a nonzero-payload
array-submit entry.

Task 2.1 of job-array-single-wal-record.
This commit is contained in:
Rongsong Shen 2026-07-14 09:50:00 +00:00
parent 76862bd9aa
commit 7c4afbbb83
4 changed files with 559 additions and 1 deletions

View file

@ -10,6 +10,13 @@
#include "wal_types.h" /* wal_entry_hdr_t, wal_entry_type — P2-4: shared with repl.h */
#include "repl.h" /* replicator_t — P2-4: embedded in persist_t */
#include "xlist.h" /* xlist_t — persist_submit_wal_and_commit job batch */
#include "xce/types.h" /* xce_job_submit_t — job-array-single-wal-record task 2.1:
* persist_submit_job_array_async's base_spec parameter.
* Header-guarded (_XCE_TYPES_H_), no circular dependency
* on persist.h — safe to pull in this early; the later,
* pre-existing #include "xce/types.h" further down this
* file (GC retention predicates) is now a harmless
* no-op re-inclusion. */
/* Forward declaration */
typedef struct cluster_s cluster_t;
@ -1077,6 +1084,41 @@ int persist_submit_job_payloads_async(persist_t* p, xlist_t* payloads,
persist_job_commit_notify_fn fn,
void* ctx);
/* job-array-single-wal-record task 2.1 (design.md §WAL Array Submission,
* Service Interface): encodes and appends the WHOLE array as ONE
* WAL_JOB_ARRAY_SUBMIT entry (base_jobid, array_size, one shared
* base_spec->submit) and registers ONE commit continuation for it,
* regardless of array_size -- the single-record sibling of
* persist_submit_job_payloads_async's per-payload loop. No job_t/xce_job_t
* object is constructed here or anywhere on this call path (mirrors the
* "nothing exists until apply" invariant persist_submit_job_payloads_async
* already documents) -- array expansion into individual job entries happens
* later, in the shared apply seam (job-array-single-wal-record task 3.x).
*
* Preconditions: array_size > 1 (array_size <= 1 returns -EINVAL; a
* single-job submission stays on the unmodified persist_submit_wal_and_
* commit / persist_submit_job_payloads_async path); base_spec != NULL and
* base_spec->submit != NULL; the caller has already range-claimed
* [base_jobid, base_jobid + array_size) (job.c's inflight_range_try_add) --
* not re-validated here, same division of responsibility as the payload-
* loop sibling above.
*
* Postconditions: on success, exactly one WAL entry is durably appended
* locally (*out_lsn set to its LSN) and exactly one continuation is
* registered; `fn` is invoked exactly once with the outcome -- either
* synchronously, right here (a local encode/append failure, the once-per-
* cluster-lifetime LSN-0 edge, or an engine-not-wired context), or later,
* asynchronously, once the entry's commit outcome is known. Returns 0 once
* the entry is durably appended locally and its outcome resolution path is
* established (sync or async); a negative rc otherwise -- `fn` has already
* been invoked (or is already guaranteed to be invoked later) by the time
* this function returns, regardless of its return value. */
int persist_submit_job_array_async(persist_t* p, uint64_t base_jobid,
uint64_t array_size,
const xce_job_submit_t* base_spec,
uint64_t* out_lsn,
persist_job_commit_notify_fn fn, void* ctx);
/* raft-apply-decoupling task 3.2: producer-side hand-off for a command
* whose commit_submit_async() continuation resolved COMMITTED. Per task
* 3.2's own contract, a COMMITTED outcome must NOT apply state or reply to