feat(scheduler-sim-eager-reconciliation): task 1.1 state-independent dispatch capture

Widens sim_reconcile_unaccounted_jobs()'s trigger gate in sim_loop.c: removes
!JOB_IS_RUN(job->state) so a job's recorded DISPATCH decision is captured as
soon as the replay reaches the round it occurred in, regardless of the job's
current state -- closing bug-1611/bug-1613's residual (a job whose RUN
confirmation lands in a later round than its dispatch decision, the realistic
case under any real dispatch-to-confirmation latency, was never reconciled).
sched_account_jobs()'s own unmodified JOB_IS_RUN charging gate continues to
defer actual resource-in-use accounting until the job genuinely transitions
to RUN -- no other line in the match/capture/report path changes.

Replaces test_reconcile_skips_non_run_state_job (which asserted the bug as
correct behavior) with test_reconcile_captures_non_run_state_job_when_dispatch_recorded,
proving the opposite for a PEND-state job. Adds the xce-test-rc-file-style
spec docs (requirements/design/research/tasks) for this feature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
This commit is contained in:
Rongsong Shen 2026-08-27 03:45:08 +00:00
parent 3891bd909a
commit e2bb98c87e
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
6 changed files with 419 additions and 35 deletions

View file

@ -498,36 +498,58 @@ static void test_reconcile_ignores_non_dispatch_entry_with_matching_jobid(
}
/* ---------------------------------------------------------------------- */
/* Test 5: the trigger only fires for a RUN-state job -- a PEND-state job */
/* with no accounts must never be touched here (the live picker re-derives */
/* PEND decisions fresh every round, per design.md's Revalidation */
/* Triggers section). */
/* Test 5 (scheduler-sim-eager-reconciliation task 1.1, replaces the old */
/* test_reconcile_skips_non_run_state_job): the trigger is now */
/* state-independent -- a job whose recorded DISPATCH decision is found */
/* this round gets its allocation captured regardless of whether it has */
/* reached RUN state yet. This is the inverse of the old (buggy) expected */
/* behavior: previously a PEND job with a matching recorded DISPATCH entry */
/* stayed uncaptured; it must now be captured just like an already-RUN */
/* job (Requirement 1.1, 3.1). The dominant real-world case (dispatch and */
/* its RUN-state confirmation recorded many rounds apart) depends on this. */
/* ---------------------------------------------------------------------- */
static void test_reconcile_skips_non_run_state_job(void)
static void
test_reconcile_captures_non_run_state_job_when_dispatch_recorded(void)
{
xce_sched_sim_driver_t sim;
xce_sched_job_t* job;
xce_job_alloc_t* jalloc;
replay_session_t* decoded;
fixture_init(&sim);
/* PEND-state, no known allocation yet -- the realistic case: this
* job's recorded DISPATCH decision has been found in this round's
* replay stream, but its RUN-state confirmation has not been replayed
* yet (may land many rounds later). */
job = add_job(&(sim.core), 7005ULL, JOB_STAT_PEND, NULL);
TEST_ASSERT_NULL(job->accounts);
TEST_ASSERT_NULL(job->allocs);
/* A matching recorded DISPATCH entry is present -- but must not be
* consulted at all, since the trigger's RUN-state gate must reject this
* job before any lookup happens. */
decoded = make_decoded_session(1000ULL, make_dispatch_entry(7005ULL));
jalloc = make_test_job_alloc_with_host(7005ULL, "worker1", "slots", 2.0f);
decoded
= make_decoded_session(1000ULL, make_dispatch_entry_from_alloc(jalloc));
sim_reconcile_unaccounted_jobs(&sim, decoded, NULL);
TEST_ASSERT_NULL_MESSAGE(
TEST_ASSERT_EQUAL_PTR_MESSAGE(
jalloc, job->allocs,
"a PEND-state job's recorded DISPATCH decision must now be "
"captured (ownership TAKEOVER, identical pointer) just like an "
"already-RUN job -- capture eligibility no longer depends on "
"job->state");
TEST_ASSERT_NOT_NULL_MESSAGE(
job->accounts,
"a non-RUN-state job must never be reconciled here, regardless of "
"what recorded data exists for it this round");
"job->accounts must be built (make_job_account()) from the "
"captured allocation even though the job has not reached RUN yet");
TEST_ASSERT_EQUAL_INT_MESSAGE(
0, decoded->n_decisions,
"the now-fully-consumed recorded entry must be removed from "
"decoded_session->decisions, exactly as for an already-RUN job");
printf("test_reconcile_skips_non_run_state_job: PASS\n");
printf("test_reconcile_captures_non_run_state_job_when_dispatch_"
"recorded: PASS\n");
}
/* ---------------------------------------------------------------------- */
@ -839,7 +861,8 @@ int main(void)
RUN_TEST(test_reconcile_is_safe_to_recheck_on_a_later_round);
RUN_TEST(test_reconcile_skips_job_that_already_has_accounts);
RUN_TEST(test_reconcile_ignores_non_dispatch_entry_with_matching_jobid);
RUN_TEST(test_reconcile_skips_non_run_state_job);
RUN_TEST(
test_reconcile_captures_non_run_state_job_when_dispatch_recorded);
RUN_TEST(
test_reconcile_reconstructs_allocs_and_accounts_from_matching_dispatch);
RUN_TEST(test_reconcile_compacts_via_shift_down_preserving_order);