fix(scheduler): re-derive worker/shared resource used-totals on snapshot load

xce_sched_core_load_snapshot() left every restored worker's resInUse
(and core->sharedInUse) at zero -- zarray_init() in the workers-
restoration block clears them, and nothing re-summed against the
restored job allocations. A caller inspecting worker/resource state
between a snapshot load and the first scheduling session (e.g. the
REPL's `info worker` before the first `step`) saw a misleadingly empty
"used" total for jobs already known to be RUNNING. Added one
sched_account_jobs(core) call after workers/jobs are restored -- a
correct idempotent-from-zero re-derivation using the same
sched_charge_account() chain the next real session would use anyway.
Must not be called again without an intervening reset (accumulates,
does not assign) -- verified no such double-call exists in this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QxaSzHRB7pmYFp4cVnwkA
This commit is contained in:
Rongsong Shen 2026-08-29 11:53:32 +00:00
parent 42fdeb6669
commit 14f95b06fe
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
5 changed files with 309 additions and 48 deletions

View file

@ -127,9 +127,12 @@
* Discriminating-power note (validated live, not merely asserted): this
* test was run against a deliberately-reverted tree (task 3's
* sim_reconcile_unaccounted_jobs() call site temporarily removed from
* sim_loop.c) and confirmed to fail -- specifically, g_round_resinuse[0]
* read back 0.0 instead of 10.0 (jobs A/C never reconciled, so
* sched_account_jobs()'s JOB_IS_RUN()+accounts!=NULL guard skipped them),
* sim_loop.c) and confirmed to fail -- specifically, the array slot for
* round 1's own resInUse snapshot (g_round_resinuse[1] as of task 4.6's
* added load-time slot at index 0; g_round_resinuse[0] at the time of this
* historical run, before that slot existed) read back 0.0 instead of 10.0
* (jobs A/C never reconciled, so sched_account_jobs()'s
* JOB_IS_RUN()+accounts!=NULL guard skipped them),
* and the direct sched_check_host_satisfy() capacity-awareness proof below
* WRONGLY granted a hypothetical job wanting JOBB_WANT (5.0) against sim's
* post-replay state -- then confirmed to pass again once the call site was
@ -1211,12 +1214,23 @@ test_contention_parity_matches_real_normal_mode_recording_round_by_round(
g_capturing = 0;
TEST_ASSERT_EQUAL_INT(0, rc);
/* 3, not 2: scheduler-snapshot-job-allocation task 4.6 added ONE extra
* sched_account_jobs() call, inside xce_sched_core_load_snapshot()
* itself (snapshot_load.c), which runs during sched_sim_driver_start()
* -- strictly before either replayed round -- so it lands at
* g_round_resinuse[0], pushing round 1/round 2's own snapshots to
* indices 1/2 (see the shifted indices below). This fixture's initial
* snapshot has no RUN-state jobs yet (A/B/C/D are all dispatched via
* recorded round decisions, never present at the replay's own starting
* snapshot), so this extra call charges nothing -- g_round_resinuse[0]
* is expected to read 0.0, not a real round observation. */
TEST_ASSERT_EQUAL_INT_MESSAGE(
2, g_round_idx,
3, g_round_idx,
"sched_account_jobs() must have been called exactly once per "
"replayed round (2 rounds in this fixture) -- if this isn't 2, "
"the round-boundary snapshot mechanism itself is miscounting, "
"independent of the parity assertions below");
"replayed round (2 rounds in this fixture) PLUS once at snapshot- "
"load time (task 4.6) -- if this isn't 3, either the round-boundary "
"snapshot mechanism itself is miscounting, or the load-time call "
"is missing/duplicated, independent of the parity assertions below");
/* ==================================================================
* Part C: the actual parity assertions
@ -1224,15 +1238,16 @@ test_contention_parity_matches_real_normal_mode_recording_round_by_round(
/* Requirement 1.4: sim mode's round-by-round resInUse must match what
* the real normal-mode recording observed at the SAME point -- not
* just at the final round. */
* just at the final round. Indices start at 1, not 0: index 0 is the
* load-time call above, not a round. */
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
normal_resinuse_after_round1, g_round_resinuse[0],
normal_resinuse_after_round1, g_round_resinuse[1],
"round 1: simulation mode's reconciled resInUse must byte-for-byte "
"match what the real normal-mode recording actually observed after "
"its own round 1 (jobs A+C dispatched, consuming 10.0 of 10.0 "
"total capacity)");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
normal_resinuse_after_round2, g_round_resinuse[1],
normal_resinuse_after_round2, g_round_resinuse[2],
"round 2: simulation mode's resInUse must still match normal "
"mode's -- the already-consumed capacity must PERSIST correctly "
"into a SECOND round, not just the first");
@ -1625,30 +1640,43 @@ test_contention_capacity_check_correct_across_multi_round_dispatch_confirm_gap(
run_multiround_contention_fixture(&sim5, 5, "_mr5");
g_capturing = 0;
/* 6, not 5: scheduler-snapshot-job-allocation task 4.6's extra
* snapshot-load-time sched_account_jobs() call (see the sibling test
* above for the full rationale) lands at g_round_resinuse[0], pushing
* every round's own snapshot to index+1 below. This fixture's initial
* snapshot also has "worker-a" registered but no RUN-state jobs yet
* (same fixture_init_seed() as the sibling test), so index 0 reads
* 0.0. */
TEST_ASSERT_EQUAL_INT_MESSAGE(
5, g_round_idx,
6, g_round_idx,
"sched_account_jobs() must have been called exactly once per "
"replayed round (5 rounds in this fixture)");
"replayed round (5 rounds in this fixture) PLUS once at "
"snapshot-load time (task 4.6)");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
0.0f, g_round_resinuse[0],
"snapshot load: no RUN-state jobs exist yet at the replay's own "
"starting snapshot -- resInUse must read 0.0 (task 4.6's own "
"load-time call, not a replayed round)");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
0.0f, g_round_resinuse[1],
"round 1: A/C dispatched but still PEND -- resInUse must read 0.0, "
"not fabricated or prematurely charged (Requirement 1.2)");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
0.0f, g_round_resinuse[1],
0.0f, g_round_resinuse[2],
"round 2: idle filler round -- the deferred-zero state from round "
"1 must persist, not drift, across an EXTRA round");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
0.0f, g_round_resinuse[2],
0.0f, g_round_resinuse[3],
"round 3: a SECOND idle filler round -- still 0.0, widening the "
"gap before A's confirmation below to genuinely several rounds");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
JOBA_WANT, g_round_resinuse[3],
JOBA_WANT, g_round_resinuse[4],
"round 4: job A's JOB_STATE_CHANGE to RUN just replayed, 3 rounds "
"after A's own round-1 DISPATCH -- resInUse must now reflect A's "
"8.0 usage (Requirement 1.3), and ONLY A's -- job C is still PEND");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
WORKER_CAPACITY, g_round_resinuse[4],
WORKER_CAPACITY, g_round_resinuse[5],
"round 5: job C's JOB_STATE_CHANGE to RUN just replayed, 4 rounds "
"after C's own round-1 DISPATCH (a DIFFERENT separation than A's 3 "
"rounds, proving this isn't a single-job coincidence) -- resInUse "