feat(scheduler-dispatch-commit): controller commits alloc+RUN transition together on successful dispatch (task 3.1)
dispatch_a_job_()'s previously-unconditional job.allocs commit is now gated on xce_startjob_req() actually succeeding (rc >= 0). In that same successful branch, under a newly-acquired lock(j, RTTI_LOCK_WRITE) (a real pre-existing locking gap this call site had -- it took no lock on the job object itself before this), the job transitions to JOB_STAT_RUN, dispatch_fail_count resets to 0, and a WAL_JOB_DISPATCH fact is appended for durability. The failure path is otherwise unchanged -- classification and resolution of the six failure sites is tasks 3.2/3.3's job, not this one's. Adds an XCE_TESTING-only dispatch_a_job_test() wrapper (dispatch_a_job_() is static) and test_dispatch_commit.c, proving: state->RUN, allocs committed correctly, counter resets, and durability survives a real persist_recover() replay with no WAL_JOB_START present. Independently reviewed and approved. Two pre-existing, out-of-boundary findings surfaced and logged for later tasks: test_commit_barrier_reply.c's test_jobdispatch_receipt_ack_no_wal_append will need updating by task 5.4 (Req 5.3 anticipates this), and bug-1429's hang was reconfirmed a 3rd time (unrelated to this change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cxa1PmqAB5PyXfJaNvj8TN
This commit is contained in:
parent
d87f4bb333
commit
d9aedc4b34
5 changed files with 699 additions and 11 deletions
|
|
@ -1035,24 +1035,71 @@ static int dispatch_a_job_(cluster_t* c, endpoint_t* ep, xce_job_alloc_t* alloc)
|
|||
|
||||
free_launchinfo(&launchinfo);
|
||||
|
||||
if (j->job.allocs != NULL) {
|
||||
free_JobAlloc_ptr(&(j->job.allocs));
|
||||
}
|
||||
j->job.allocs = (xce_job_alloc_t*)calloc(1, sizeof(xce_job_alloc_t));
|
||||
j->job.allocs->jobid = alloc->jobid;
|
||||
j->job.allocs->nhosts = nc;
|
||||
MOVE_PTR(j->job.allocs->allocs, allocs);
|
||||
alloc->allocs = NULL;
|
||||
/* scheduler-dispatch-commit task 3.1 (Req 2.1-2.4, 5.1): the allocation
|
||||
* commit -- previously unconditional regardless of xce_startjob_req()'s
|
||||
* outcome -- is now gated on the dispatch to the worker actually
|
||||
* succeeding, and the job's transition to a running state happens in
|
||||
* that same successful outcome. Protected under this component's own
|
||||
* lock(j, RTTI_LOCK_WRITE): dispatch_a_job_() previously took no lock on
|
||||
* `j` itself here (only on c->jobmgr/c->workermgr/w), a real
|
||||
* pre-existing gap this restructuring corrects for this call site
|
||||
* (design.md's Concurrency Strategy note -- not fixed anywhere else in
|
||||
* this file). On failure, state is left untouched (still PEND) and
|
||||
* nothing is committed, so there is nothing to release (Req 4.4/4.5
|
||||
* satisfied vacuously by construction for this path). The worker's own
|
||||
* later confirmation that the job actually started
|
||||
* (record_job_start_fact()/WAL_JOB_START) continues to fire exactly as
|
||||
* it does today, unaffected by this change. */
|
||||
if (rc >= 0) {
|
||||
lock(j, RTTI_LOCK_WRITE);
|
||||
|
||||
if (rc < 0) {
|
||||
if (j->job.allocs != NULL) {
|
||||
free_JobAlloc_ptr(&(j->job.allocs));
|
||||
}
|
||||
j->job.allocs = (xce_job_alloc_t*)calloc(1, sizeof(xce_job_alloc_t));
|
||||
j->job.allocs->jobid = alloc->jobid;
|
||||
j->job.allocs->nhosts = nc;
|
||||
MOVE_PTR(j->job.allocs->allocs, allocs);
|
||||
alloc->allocs = NULL;
|
||||
|
||||
j->job.state = JOB_STAT_RUN;
|
||||
|
||||
/* Reset the consecutive-retryable-failure counter (task 1.3): a
|
||||
* fresh successful dispatch clears any prior failure history, so a
|
||||
* job that later fails again is evaluated against the bound as if
|
||||
* it had only failed once (Req 4.3; the bound itself is exercised
|
||||
* end-to-end by task 5.2, not here). */
|
||||
j->dispatch_fail_count = 0;
|
||||
|
||||
/* Durable record of the dispatch-time RUN transition (task 1.2),
|
||||
* mirroring record_job_start_fact()'s persist_submit_job_fact()
|
||||
* call shape exactly -- _pad stays 0 (the "legacy: state is
|
||||
* absolute value" convention, matching every other job-state fact
|
||||
* committed synchronously here rather than via deferred apply). */
|
||||
(void)persist_submit_job_fact(&c->persist, WAL_JOB_DISPATCH,
|
||||
alloc->jobid, JOB_STAT_RUN);
|
||||
|
||||
unlock(j);
|
||||
} else {
|
||||
SET_ERROR(-rc);
|
||||
PERROR("xce_startjob_req");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/* Test-only accessor: dispatch_a_job_() is static/file-local (its only
|
||||
* caller, handle_jobdispatch(), is unmodified by this task) -- mirroring
|
||||
* xce_classify_dispatch_fail_test()'s existing XCE_TESTING-only-export
|
||||
* pattern earlier in this file (xce_controller itself is built without
|
||||
* -DXCE_TESTING, so this never ships). */
|
||||
int dispatch_a_job_test(cluster_t* c, endpoint_t* ep, xce_job_alloc_t* alloc)
|
||||
{
|
||||
return dispatch_a_job_(c, ep, alloc);
|
||||
}
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
int job_ctrl(cluster_t* c, xce_jobctrl_t* ctrinfo)
|
||||
{
|
||||
uint64_t jobid = ctrinfo->jobid;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue