Define xce_dispatch_fail_site_t/xce_dispatch_fail_class_t and xce_classify_dispatch_fail() in job.c, mapping each of dispatch_a_job_()'s six post-lookup failure points to retryable or terminal per Requirement 4.1/4.2. Standalone, not yet wired into dispatch_a_job_() (deferred to tasks 3.2/3.3). Also adds this spec's requirements/design/research/tasks docs (first commit touching this spec). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14 KiB
Requirements Document
Project Description (Input)
Who has the problem: scheduler and controller maintainers, and cluster operators who rely on accurate live resource accounting and (eventually) preemption. Surfaced while investigating bug-1611/1613-adjacent behavior and while designing the sibling scheduler-sim-eager-reconciliation spec.
Current situation: job->state's transition to RUN only happens later, via a separate worker-confirmation event — scheduler's sched_job_state_change() (scheduler.c:963-988) fires only on the controller's NotifyData_data_schedStartJob notify; controller's job.state flips to RUN only via on_job_start()/record_job_start_fact() (job.c), driven by the worker's own confirmation. Meanwhile, resource-commitment data is set much earlier and independently: scheduler's job->accounts/job->allocs are set synchronously inside sched_commit_decision()'s DISPATCH branch (scheduler.c:1441-1446), and controller's job.allocs is set synchronously inside dispatch_a_job_() (job.c:831-963) — both well before any worker confirmation.
This split (intentional and, on its own, correct — "scheduled/dispatched" and "confirmed running" are legitimately different signals) has a concrete correctness consequence: every scheduler round, xce_core_begin_session() (core_session.c:66-108) unconditionally zeroes core->sharedInUse/worker->resInUse, and sched_account_jobs() (scheduler.c:1258-1292) re-derives those totals only from jobs where JOB_IS_RUN(job->state) is true — silently skipping a job that is dispatched (accounts already set) but still PEND (not yet confirmed). Normal-mode rounds run on a 1-second cadence (main.c:453-460), so any dispatch-to-confirmation latency over ~1s (ordinary for a real worker RPC + fork/exec) wipes that job's resource charge at the very next round and does not restore it until confirmation lands. The allocator's admission check (sched_check_host_satisfy_/sched_check_shared_satisfy_, resource.c:252-370, called from seqjob_allocator.c:130) reads exactly this same resInUse/sharedInUse, with no separate reserved/committed pool (xce_sched_job_t has only state/allocs/accounts, sched/types.h:96-116) — so the resource looks free again and can be double-granted to a second job. This is a genuine live over-commit/double-booking risk in normal-mode operation, logged as bug-1624 (unfixed).
Separately, and necessary for any fix to be safe: the controller already reports dispatch-RPC failure back to the scheduler via NotifyData_data_jobDispatchFail (notify_dispatch(), job.c:1108-1125), but the scheduler's sched_handle_notify() switch (handler.c:499-544) has zero case for it — it silently falls to default: -EINVAL. So today a dispatch failure leaves job->accounts/job->allocs (scheduler) and j->job.allocs (controller) stale forever with no cleanup, logged as bug-1625 (unfixed). There is also currently no code path anywhere in job.c that ever sets a job's state back to JOB_STAT_PEND after dispatch — mark_job_fail()/apply_job_fail_transition() only ever resolves forward onto a terminal EXIT/DONE-ish state; a genuine retry-back-to-PEND path does not exist yet.
What should change:
- Redefine the RUN transition to occur synchronously at DISPATCH-decision-commit time rather than at worker-confirmation time, on BOTH scheduler (inside
sched_commit_decision()'s DISPATCH branch, alongside whereaccounts/allocsalready get set) and controller (insidedispatch_a_job_(), alongside wherejob.allocsalready gets set) — so neither side's internal state diverges from the other for the same job, andsched_account_jobs()'s existingJOB_IS_RUNgate naturally includes a dispatched job from the round it was dispatched, fixing bug-1624 with no change to that gate's own logic. - Build the dispatch-failure handling this redefinition requires to be safe: scheduler-side handling for
NotifyData_data_jobDispatchFail(fixing bug-1625), a retryable-vs-terminal failure classification (which failure codes/conditions resolve back to PEND for retry vs. forward to EXIT/FAIL permanently — no such classification exists today), and correct reversal of state/accounts/allocs/job.allocson both scheduler and controller when a dispatch is retried back to PEND. - Document (not implement delivery for) that a job holding committed-but-unconfirmed resources is a legitimate future preemption victim candidate under the new RUN definition —
scheduler-preemption(sibling spec, still at requirements-generated) currently assumes in its AC6/AC9 that every RUN victim has a live, signalable process; that assumption is worth revising before that spec proceeds to design. Actually implementing preemption delivery/victim selection remainsscheduler-preemption's own boundary.
Sequencing
The user has directed that this spec land before scheduler-allocation begins implementation. scheduler-allocation's new iterator-based try_alloc_on_host() will read the same resInUse/sharedInUse totals bug-1624 corrupts, and its planned XCE_ALLOC_PREEMPT borrowing mode interacts with what counts as committed/borrowable — building the new allocator on top of today's broken round-boundary accounting would compound rather than fix the problem.
Boundary Context
- In scope: synchronous RUN-state transition at dispatch-decision-commit time on scheduler (
sched_commit_decision()) and controller (dispatch_a_job_()); scheduler-side handling ofNotifyData_data_jobDispatchFail; retryable-vs-terminal dispatch-failure classification; PEND-reversion of state/accounts/allocs on both scheduler and controller for retryable failures; a documented note (not implementation) that a RUN-but-unconfirmed job is a valid future preemption victim candidate. - Out of scope:
scheduler-controller-msg-gap(already implementation-complete — bug-1625 is thematically identical but a new, separately-discovered gap; not a reopening of that closed spec);scheduler-sim-decision-parity(implementation-complete, do not reopen);scheduler-sim-eager-reconciliation(sibling, in-flight, explicitly simulation-mode-only and explicitly excludes normal-mode behavior changes — this spec is the normal-mode counterpart but a distinct spec, not a merge);scheduler-preemption's own preemption delivery/victim-selection mechanics (cross-referenced via a pending-revision note, not merged in);scheduler-allocation's iterator/plan-generation work itself (unrelated axis — HOW allocation plans are computed, not WHEN state/accounting transitions happen); any change to how a job's state is displayed to end users beyond what naturally falls out of the state-transition-timing change; PEND job candidacy/picker-selection logic changes beyond what the retry-to-PEND path requires. - Adjacent expectations: this spec extends
scheduler-sim-decision-parity/scheduler-sim-eager-reconciliation's andscheduler-controller-msg-gap's existing mechanisms without reopening them.scheduler-preemption's requirements.md received a cross-reference note (2026-08-23) flagging its AC6/AC9 as pending revision once this spec's design is settled. Because simulation mode replays whatever the controller actually recorded (it does not independently decide state-transition timing), a recording captured after this feature ships will naturally carry the dispatch decision and the running-state transition in the same round — narrowing, though not eliminating, the cross-round casescheduler-sim-eager-reconciliationhandles for older or otherwise-delayed recordings. This is a beneficial side effect to note, not a dependency either spec should design around.
Requirements
Requirement 1: Synchronous running-state transition on dispatch commit (Scheduler)
Objective: As a scheduler maintainer relying on accurate live resource accounting, I want a job's state to become running as soon as the Scheduler commits its dispatch decision, so that per-round resource-in-use totals never lose track of a job's committed resources solely because worker confirmation has not yet arrived.
Acceptance Criteria
- When the Scheduler commits a dispatch decision for a job, the Scheduler shall transition that job to a running state in the same operation that records the job's resource allocation.
- While a job's dispatch decision has been committed and no dispatch failure has since been reported for it, the Scheduler shall include that job's resource consumption in worker and shared resource-in-use totals from the round it was dispatched onward, regardless of how many rounds elapse before the worker confirms the job has started.
- The Scheduler shall not change the existing rule that only jobs in a running state contribute to a round's resource-in-use totals; this feature changes only when a job reaches that state, not how totals are computed from it.
Requirement 2: Synchronous running-state transition on dispatch receipt (Controller), consistent with Scheduler
Objective: As a maintainer relying on scheduler-side and controller-side bookkeeping staying consistent for the same job, I want the Controller to mark a job running at the same moment it processes the Scheduler's dispatch decision, so the two components never disagree about whether a job has begun consuming its allocated resources.
Acceptance Criteria
- When the Controller's attempt to notify the assigned worker of a dispatch decision succeeds, the Controller shall transition that job to a running state in the same operation that commits the job's resource allocation. The transition and the allocation commit shall not depend on any later worker confirmation of actual process start.
- The Controller shall continue to record the worker's confirmation that a job has actually started, including the job's actual start time, as a separate durable fact, independent of and in addition to the state transition performed under Criterion 1.
- When a status query or job listing is served after a job's dispatch decision has been processed, the Controller shall report that job's state as running, whether or not the worker has yet confirmed the job started.
- The Controller shall apply the state transition in Criterion 1 through the same durability and failover-consistency discipline it already applies to a job's other state transitions, so the transition is neither lost nor left divergent across a controller failover.
Requirement 3: Scheduler-side dispatch-failure handling
Objective: As a maintainer relying on the Scheduler not silently losing track of failed dispatches, I want the Scheduler to actually process the Controller's existing dispatch-failure notification, so a dispatch that never reaches a worker does not leave stale committed-resource bookkeeping behind indefinitely.
Acceptance Criteria
- When the Scheduler receives a dispatch-failure notification for a job from the Controller, the Scheduler shall process that notification rather than discard it as an unrecognized event.
- When the Scheduler processes a dispatch-failure notification for a job, the Scheduler shall resolve that job according to the retryable-vs-terminal classification defined in Requirement 4.
Requirement 4: Retryable-vs-terminal dispatch-failure resolution
Objective: As a cluster operator, I want a dispatch failure caused by a transient or environmental condition to be retried automatically, and a dispatch failure caused by a locally-invalid decision to fail immediately without endless retrying, so failed dispatches resolve correctly in both directions.
Acceptance Criteria
- When a dispatch failure is caused by a transient or environmental condition — for example the assigned worker being unreachable or not in a ready state, or the dispatch request itself failing to be delivered — the Scheduler and Controller shall return the job to a pending state, making it eligible for the Scheduler to attempt dispatch again in a subsequent round.
- When a dispatch failure is caused by a locally-invalid decision — for example the job or its allocation no longer being resolvable by the Controller — the Scheduler and Controller shall resolve the job to a terminal failed state without returning it to a pending state.
- While a job has accumulated consecutive transient/environmental dispatch failures with no intervening successful dispatch, when the number of such failures reaches a bounded limit, the Scheduler and Controller shall resolve the job to a terminal failed state rather than returning it to pending again. The exact limit is a design-phase decision; this requirement only constrains it to be bounded, never infinite.
- When a job is returned to a pending state following a dispatch failure, the Scheduler and Controller shall each release the resource-allocation and commitment data associated with the failed dispatch attempt, so a subsequent dispatch decision for that job is not blocked by stale data from the failed attempt.
- When a job is resolved to a terminal failed state following a dispatch failure, the Scheduler and Controller shall each release the resource-allocation and commitment data associated with the failed dispatch attempt.
Requirement 5: No regression to existing guarantees
Objective: As a maintainer, I want this feature to preserve existing dispatch, accounting, and lifecycle guarantees outside the specific gap it closes.
Acceptance Criteria
- When a job's dispatch succeeds and the worker later confirms the job has started, the Controller shall continue to record the confirmed-start fact exactly as it does today, in addition to (not instead of) the earlier state transition performed under Requirement 2.
- The Scheduler shall not apply this feature's dispatch-time state transition when operating in simulation/replay mode; simulation mode's own recorded-event replay path is unaffected by this feature.
- Existing scheduler and controller job-lifecycle test suites shall continue to pass, except for assertions that directly depended on the state transition occurring only at worker-confirmation time, which shall be updated to reflect the new timing.
- A full HA soak (sequential and chaotic
/xce-test-haregression) shall show no new job loss, state divergence, or liveness stall attributable to this feature.