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:
parent
76862bd9aa
commit
7c4afbbb83
4 changed files with 559 additions and 1 deletions
|
|
@ -4925,6 +4925,159 @@ int persist_submit_job_payloads_async(persist_t* p, xlist_t* payloads,
|
|||
return first_rc;
|
||||
}
|
||||
|
||||
/* job-array-single-wal-record task 2.1: serialize a job-array submission
|
||||
* (base jobid + array size + one shared job spec) to a malloc'd Cap'n Proto
|
||||
* buffer, reusing the SAME capnp struct (JobSubmit) the client's own submit
|
||||
* request uses -- mirrors capnp_encode_job above, which mirrors encode_Job/
|
||||
* new_Job/write_Job for a single job, except here via encode_JobSubmit/
|
||||
* new_JobSubmit/write_JobSubmit. The encoded payload is O(1) in array_size:
|
||||
* arraySize is a fixed-width uint64 field, not a per-element list -- so this
|
||||
* function's output size never grows with array_size (Req 7.1). Caller must
|
||||
* free(*out) on success. */
|
||||
static int capnp_encode_job_array(const xce_job_submit_t* s, uint8_t** out,
|
||||
size_t* out_len)
|
||||
{
|
||||
struct capn c;
|
||||
struct capn_segment* cs;
|
||||
struct JobSubmit cjs;
|
||||
JobSubmit_ptr jp;
|
||||
|
||||
capn_init_malloc(&c);
|
||||
cs = capn_root(&c).seg;
|
||||
encode_JobSubmit(cs, &cjs, (xce_job_submit_t*)s);
|
||||
jp = new_JobSubmit(cs);
|
||||
write_JobSubmit(&cjs, jp);
|
||||
capn_setp(capn_root(&c), 0, jp.p);
|
||||
|
||||
*out_len = capn_size(&c);
|
||||
*out = malloc(*out_len);
|
||||
if (!*out) { capn_free(&c); return -ENOMEM; }
|
||||
capn_write_mem(&c, *out, *out_len, 0);
|
||||
capn_free(&c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* job-array-single-wal-record task 2.1: append-only, single-record
|
||||
* counterpart of append_job_wal_entry_local for a WHOLE job array. Encodes
|
||||
* ONE JobSubmit-shaped payload (base_jobid, array_size, base_spec->submit)
|
||||
* and appends it as exactly one WAL_JOB_ARRAY_SUBMIT entry via the SAME
|
||||
* primary-append primitive append_job_wal_entry_local already uses
|
||||
* (ha_primary_log_append / persist_wal_async) -- never one entry per array
|
||||
* element, regardless of array_size (Req 1.1, 1.2, 1.3). base_jobid/
|
||||
* array_size are taken as explicit parameters rather than read from
|
||||
* base_spec->jobid/base_spec->array_size, so the caller's own
|
||||
* already-range-claimed values are always what gets committed, independent
|
||||
* of whatever same-shaped fields the client's own wire request happened to
|
||||
* carry. */
|
||||
static int append_job_array_wal_entry_local(persist_t* p, uint64_t base_jobid,
|
||||
uint64_t array_size,
|
||||
const xce_job_submit_t* base_spec,
|
||||
uint64_t* out_lsn)
|
||||
{
|
||||
xce_job_submit_t wire = { .jobid = base_jobid,
|
||||
.array_size = array_size,
|
||||
.submit = base_spec->submit };
|
||||
uint8_t* buf = NULL;
|
||||
size_t sz = 0;
|
||||
int rc;
|
||||
uint64_t lsn;
|
||||
|
||||
if (capnp_encode_job_array(&wire, &buf, &sz) != 0)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Snapshot the LSN this entry will receive BEFORE the append (same
|
||||
* convention as append_job_wal_entry_local / submit_job_wal_entry_and_
|
||||
* commit: persist_wal_async / ha_primary_log_append assigns
|
||||
* hdr.lsn = current_lsn then increments). */
|
||||
pthread_mutex_lock(&p->lock);
|
||||
lsn = p->current_lsn;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (p->cluster != NULL)
|
||||
rc = ha_primary_log_append(p->cluster, WAL_JOB_ARRAY_SUBMIT, buf,
|
||||
(uint32_t)sz, NULL);
|
||||
else
|
||||
rc = persist_wal_async(p, WAL_JOB_ARRAY_SUBMIT, buf, (uint32_t)sz);
|
||||
free(buf);
|
||||
|
||||
if (rc == 0)
|
||||
*out_lsn = lsn;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* job-array-single-wal-record task 2.1 (design.md §WAL Array Submission,
|
||||
* Service Interface): encodes and appends ONE array-shaped WAL entry
|
||||
* (WAL_JOB_ARRAY_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, specialized to
|
||||
* exactly one payload. No job_t/xce_job_t object is constructed here or
|
||||
* anywhere on this call path -- array expansion into individual job
|
||||
* entries happens later, in the shared apply seam (task 3.x's
|
||||
* restore_job_array), never here.
|
||||
*
|
||||
* Mirrors persist_submit_job_payloads_async's contract exactly:
|
||||
* - a local encode/append failure resolves `fn` synchronously with that
|
||||
* failure and returns the same negative rc;
|
||||
* - the LSN-0 / engine-not-wired edge cases (documented on
|
||||
* persist_submit_job_payloads_async above) resolve `fn` inline with
|
||||
* rc=0;
|
||||
* - otherwise `fn` is registered via commit_submit_async and invoked
|
||||
* exactly once, asynchronously, once the entry's commit outcome is
|
||||
* known. */
|
||||
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)
|
||||
{
|
||||
uint64_t lsn = 0;
|
||||
int rc;
|
||||
|
||||
if (p == NULL || base_spec == NULL || base_spec->submit == NULL ||
|
||||
out_lsn == NULL || fn == NULL || array_size <= 1)
|
||||
return -EINVAL;
|
||||
|
||||
rc = append_job_array_wal_entry_local(p, base_jobid, array_size,
|
||||
base_spec, &lsn);
|
||||
if (rc != 0) {
|
||||
fn(ctx, 0, rc);
|
||||
return rc;
|
||||
}
|
||||
*out_lsn = lsn;
|
||||
|
||||
/* LSN-0 / engine-not-wired edge (see persist_submit_job_payloads_async's
|
||||
* doc comment above for the full rationale -- identical here,
|
||||
* specialized to one entry instead of a loop). */
|
||||
if (lsn == 0 || p->repl.ce == NULL) {
|
||||
fn(ctx, lsn, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
job_commit_adapter_ctx_t* a = calloc(1, sizeof(*a));
|
||||
if (a == NULL) {
|
||||
fn(ctx, lsn, -ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
a->fn = fn;
|
||||
a->ctx = ctx;
|
||||
|
||||
int reg_rc = commit_submit_async(p->repl.ce, lsn,
|
||||
job_commit_resolve_adapter, a);
|
||||
if (reg_rc != 0) {
|
||||
free(a);
|
||||
ERROR("persist_submit_job_array_async: lsn=%lu appended but "
|
||||
"commit_submit_async registration failed (rc=%d) -- the "
|
||||
"entry remains durable and will still be caught up "
|
||||
"eventually via replay, but this caller will not be told "
|
||||
"its outcome via this path",
|
||||
(unsigned long)lsn, reg_rc);
|
||||
fn(ctx, lsn, reg_rc);
|
||||
return reg_rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void persist_register_pending_apply(persist_t* p, uint64_t lsn,
|
||||
pending_apply_notify_fn on_applied,
|
||||
void* ctx)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue