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

@ -490,6 +490,26 @@ int xce_sched_core_load_snapshot(xce_sched_core_t* core,
}
}
/* worker/shared resource "used" totals (Req 1.2, 1.3): every restored
* worker's resInUse (and core->sharedInUse) was left freshly zeroed by
* the zarray_init() calls in the workers-restoration block above, and
* job->accounts was just rebuilt from the restored job->allocs -- so
* charging them here, ONCE, is a correct, idempotent-from-zero
* re-derivation, not a fabrication (same source data and same
* sched_charge_account() chain the next real session's own
* sched_account_jobs() call would use). Without this, a caller
* inspecting worker/resource state between a snapshot load and the
* first scheduling session (e.g. the interactive REPL's `info worker`
* before the first `step`) would see a misleadingly empty "used" total
* for jobs already known to be RUNNING, even though no scheduling
* decision is ever actually made against that stale state (every
* driver's own round-boundary sequence resets-then-re-sums before
* unblocking the picker regardless, core.h's xce_core_begin_session()/
* xce_core_signal_go() doc comment). Must not be called again without
* an intervening resInUse/sharedInUse reset -- sched_charge_account()
* accumulates (+=), not assigns. */
sched_account_jobs(core);
/* prng_seed -- see snapshot.c header "PRNG seed resolution". Genuine
* gap found while writing this task's own cross-core equivalence test
* (test_snapshot_save_load.c): save_snapshot() already writes

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 "

View file

@ -644,11 +644,27 @@ test_sim_replay_calls_account_jobs_once_per_round_within_session_window(
/* THE central regression proof (task 2.1's own scope, Req 1.3/1.4):
* before this task, this count is 0 (sim_loop.c never called
* sched_account_jobs() at all, bug-1613) -- after, it is 2, one per
* replayed round, not merely once ever. */
TEST_ASSERT_EQUAL_INT(2, atomic_load(&g_spy_calls));
* sched_account_jobs() at all, bug-1613) -- after, it is 2 per-round
* calls plus ONE load-time call (task 4.6:
* xce_sched_core_load_snapshot(), called from sched_sim_driver_start()
* at the very top of sched_sim_driver_run(), now also charges
* sched_account_jobs() once for whatever RUN-state jobs the loaded
* snapshot already has -- see snapshot_load.c's own doc comment on
* that call). Total 3, not merely once ever and not merely once per
* round. */
TEST_ASSERT_EQUAL_INT(3, atomic_load(&g_spy_calls));
for (i = 0; i < 2; i++) {
/* Index 0 is task 4.6's load-time call -- by design it runs BEFORE any
* session begins (core->session_active == 0 at that point is correct,
* not a violation of the round-boundary contract below, which only
* governs calls made ONCE A ROUND IS UNDERWAY). */
TEST_ASSERT_FALSE_MESSAGE(
g_spy_session_active_ok[0],
"the load-time sched_account_jobs() call (task 4.6) runs before "
"any session begins -- core->session_active must be 0 here, not "
"the round-boundary contract's session_active==1");
for (i = 1; i < 3; i++) {
TEST_ASSERT_TRUE_MESSAGE(
g_spy_session_active_ok[i],
"sched_account_jobs() must run with core->session_active==1 -- "
@ -737,8 +753,12 @@ static void test_sim_replay_batch_mode_uses_the_same_round_boundary(void)
rc = sched_sim_driver_run(&sim, &sim_args);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(1, atomic_load(&g_spy_calls));
TEST_ASSERT_TRUE(g_spy_session_active_ok[0]);
/* 2, not 1: task 4.6's load-time call (index 0, session_active==0 by
* design -- see the two-round test's own comment above) plus this
* batch run's one replayed round (index 1, session_active==1). */
TEST_ASSERT_EQUAL_INT(2, atomic_load(&g_spy_calls));
TEST_ASSERT_FALSE(g_spy_session_active_ok[0]);
TEST_ASSERT_TRUE(g_spy_session_active_ok[1]);
TEST_ASSERT_EQUAL_INT(1, atomic_load(&g_begin_session_spy_calls));
TEST_ASSERT_TRUE(g_begin_session_spy_active_ok[0]);

View file

@ -81,6 +81,20 @@ void kill_sched_resval(xce_val_t* val) { (void)val; }
* xce_sched_core_init()'s kvmgr datakiller. */
void plugin_unregister(void* arg) { (void)arg; }
/* xce_sched_core_load_snapshot()'s (snapshot_load.c) linker dependency as
* of scheduler-snapshot-job-allocation task 4.6 -- scheduler.c is
* deliberately not linked here (this file never drives a real scheduling
* session, see this file's own header comment), so a trivial no-op
* satisfies the link. Behaviorally exact, not just link-satisfying: every
* fixture in this file (write_valid_fixture()) snapshots a scheduler with
* zero jobs ever pushed, so the real sched_account_jobs() would iterate
* zero RUN-state jobs and charge nothing here regardless. */
int sched_account_jobs(xce_sched_core_t* core)
{
(void)core;
return 0;
}
/* Real reimplementation of schedobj.c's sched_resolve_job_picker -- same
* rationale/algorithm as test_record_lifecycle.c's own copy (schedobj.c is
* not linked). Both the FIXTURE core (built via a real xce_scheduler_t,

View file

@ -1666,8 +1666,20 @@ test_snapshot_load_leaves_allocs_and_accounts_null_for_job_without_allocation(
* #include scheduler.c itself). Calls the REAL sched_charge_account()
* (resource.c, #include'd above), so this genuinely exercises the same
* accounting chain live dispatch and sim_reconcile_unaccounted_jobs() both
* rely on downstream. */
static int test_sched_account_jobs(xce_sched_core_t* core)
* rely on downstream.
*
* Named (and exported, not `static`) as the REAL symbol -- not
* `test_sched_account_jobs` -- because xce_sched_core_load_snapshot()
* (snapshot_load.c, #include'd above) now calls sched_account_jobs()
* itself (scheduler-snapshot-job-allocation task 4.6, Req 1.2/1.3: worker/
* shared resource "used" totals correct immediately on load, not only
* after a caller manually re-derives them). This TU's own white-box
* #include of snapshot_load.c means that call resolves to THIS
* definition, exactly like every other real/stub function this file
* already provides for a function snapshot_load.c or core.c reference
* (kill_sched_*, sched_resolve_job_picker, etc.) -- same convention, not a
* new one. */
int sched_account_jobs(xce_sched_core_t* core)
{
iterator_t it;
int rc = 0;
@ -1914,12 +1926,16 @@ test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independen
/* ===================================================================
* Phase 3: the actual proof point (Req 1.2/1.3) -- call the REAL
* sched_account_jobs() chain (test_sched_account_jobs() above, which
* calls the REAL sched_charge_account(), resource.c) on core_a BEFORE
* saving, capturing what "resInUse/sharedInUse, reported the normal
* way" looks like for the source instance.
* sched_account_jobs() chain (this file's own real reimplementation
* above, which calls the REAL sched_charge_account(), resource.c) on
* core_a BEFORE saving, capturing what "resInUse/sharedInUse, reported
* the normal way" looks like for the source instance. Manual here
* because core_a is the SOURCE core, built via direct field pokes
* (never loaded from a snapshot), so nothing calls this for it
* automatically the way xce_sched_core_load_snapshot() now does for
* core_b below (Phase 5/8).
* =================================================================== */
rc = test_sched_account_jobs(&core_a);
rc = sched_account_jobs(&core_a);
TEST_ASSERT_EQUAL_INT(0, rc);
worker_a_a = sched_get_worker(&core_a, "hostA");
@ -2030,16 +2046,19 @@ test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independen
TEST_ASSERT_TRUE(restored_running->allocs != snap.jobs->jobs[0]->allocs);
/* ===================================================================
* Phase 8: the actual proof (Req 1.2/1.3) -- call the SAME real
* accounting chain on core_b (the RESTORED instance) and confirm its
* resInUse/sharedInUse totals EXACTLY match what the identical chain
* produced on core_a (Phase 3) before saving -- proving the restored
* allocation is "included in in-use totals immediately" and "reported
* identically to ... normal dispatch" (Req 1.2, 1.3), not merely that
* job->accounts' own fields happen to look right.
* Phase 8: the actual proof (Req 1.2/1.3) -- confirm core_b's
* resInUse/sharedInUse totals EXACTLY match what the identical
* accounting chain produced on core_a (Phase 3) before saving --
* proving the restored allocation is "included in in-use totals
* immediately" and "reported identically to ... normal dispatch" (Req
* 1.2, 1.3), not merely that job->accounts' own fields happen to look
* right. NO manual sched_account_jobs(&core_b) call here (task 4.6,
* unlike this test's pre-4.6 form): xce_sched_core_load_snapshot()
* itself (Phase 5 above) now calls it internally, exactly once, right
* after restoring job->accounts -- calling it again here would
* double-charge (sched_charge_account() accumulates, it does not
* assign) and silently corrupt every assertion below.
* =================================================================== */
rc = test_sched_account_jobs(&core_b);
TEST_ASSERT_EQUAL_INT(0, rc);
worker_a_b = sched_get_worker(&core_b, "hostA");
worker_b_b = sched_get_worker(&core_b, "hostB");
@ -2124,6 +2143,160 @@ test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independen
TEST_ASSERT_EQUAL_FLOAT(1.0f, restored_shared_lic);
}
/* ---------------------------------------------------------------------- */
/* scheduler-snapshot-job-allocation task 4.6 (Req 1.2/1.3, "correct */
/* immediately on load"): the direct, minimal proof that */
/* xce_sched_core_load_snapshot() itself -- with NO further call from the */
/* caller -- leaves a restored RUNNING job's worker resInUse already */
/* correct. Found live (2026-08-28): an operator running */
/* `xce_scheduler -i -s <replay>` saw `info worker all` report `used=0.00` */
/* for a worker with jobs already RUNNING in the loaded snapshot, correct */
/* only after the first `step` -- because, before this task, ONLY the */
/* round-boundary sched_account_jobs() call (scheduler-sim-decision-parity */
/* spec, bug-1613's fix, sim_loop.c) ever re-derived resInUse; nothing ran */
/* at load time itself. The round-trip test above already covers this */
/* incidentally (its Phase 8 no longer needs a manual sched_account_jobs() */
/* call and still passes) -- this test names the specific fix directly, */
/* with the leanest fixture that can prove it (one resource, one worker, */
/* one running job), rather than relying on a much larger test's side */
/* effect. */
/* ---------------------------------------------------------------------- */
static void
test_snapshot_load_populates_worker_resinuse_immediately_without_a_session(
void)
{
xce_sched_core_t core;
sched_snapshot_data_t snap;
xce_core_event_t events[3];
xce_conf4sched_t config;
xce_resource_t res_slots;
xce_resource_t* resources[1];
xce_queue_t wire_queue;
xce_queue_t* queues[1];
xce_worker_t wire_worker;
xce_worker_t* workers[1];
xce_val_t worker_slots_val;
xce_name_val_t worker_slots_nv;
xce_name_val_t* worker_resvals[1];
xce_job_t* wire_job;
xce_job_t* jobs[1];
xce_sched_job_t* job;
xce_alloc_t* a_slots;
xce_host_alloc_t* h0;
xce_host_alloc_t** hallocs;
xce_job_alloc_t* jalloc;
xce_sched_worker_t* worker;
xce_resource_t* slots_res;
float resinuse_slots = -1.0f;
int rc;
memset(&g_stub_state_a, 0, sizeof(g_stub_state_a));
fixture_init(&core, &g_stub_plugin_a);
memset(&res_slots, 0, sizeof(res_slots));
res_slots.name = "slots";
resources[0] = &res_slots;
memset(&wire_queue, 0, sizeof(wire_queue));
wire_queue.queue = "normal";
wire_queue.priority = 1;
wire_queue.status = QUEUE_STAT_OPEN;
queues[0] = &wire_queue;
memset(&config, 0, sizeof(config));
config.n_resources = 1;
config.resources = resources;
config.n_queues = 1;
config.queues = queues;
memset(&worker_slots_val, 0, sizeof(worker_slots_val));
worker_slots_val.which = XCE_VAL_NUMERIC;
worker_slots_val.u.ivalue32 = 16;
memset(&worker_slots_nv, 0, sizeof(worker_slots_nv));
worker_slots_nv.name = "slots";
worker_slots_nv.val = &worker_slots_val;
worker_resvals[0] = &worker_slots_nv;
memset(&wire_worker, 0, sizeof(wire_worker));
wire_worker.name = "hostA";
wire_worker.status = 0; /* available */
wire_worker.nresvals_to_sched = 1;
wire_worker.resvals_to_sched = worker_resvals;
workers[0] = &wire_worker;
wire_job = make_job(901, JOB_STAT_PEND, "normal", "alice", "proj1");
jobs[0] = wire_job;
memset(events, 0, sizeof(events));
events[0].type = XCE_CORE_EVENT_CONFIG_PUSH;
events[0].u.config_push.config = &config;
events[1].type = XCE_CORE_EVENT_WORKERS_PUSH;
events[1].u.workers_push.nworkers = 1;
events[1].u.workers_push.workers = workers;
events[2].type = XCE_CORE_EVENT_JOBS_PUSH;
events[2].u.jobs_push.njobs = 1;
events[2].u.jobs_push.jobs = jobs;
rc = xce_core_push_events(&core, events, 3);
TEST_ASSERT_EQUAL_INT(0, rc);
job = sched_get_job(&core, 901);
TEST_ASSERT_NOT_NULL(job);
a_slots = make_alloc((char*)"slots", NULL, 0, 4.0f, 0);
TEST_ASSERT_NOT_NULL(a_slots);
h0 = make_host_alloc((char*)"hostA", 1, make_allocs(1, a_slots));
TEST_ASSERT_NOT_NULL(h0);
hallocs = make_host_allocs(1, h0);
TEST_ASSERT_NOT_NULL(hallocs);
jalloc = make_job_alloc(901, 1, hallocs);
TEST_ASSERT_NOT_NULL(jalloc);
job->state = JOB_STAT_RUN;
job->allocs = jalloc;
job->accounts = make_job_account(job->allocs);
TEST_ASSERT_NOT_NULL(job->accounts);
memset(&snap, 0, sizeof(snap));
rc = xce_sched_core_save_snapshot(&core, &snap);
TEST_ASSERT_EQUAL_INT(0, rc);
/* Reload into the SAME core -- xce_sched_core_load_snapshot() only
* ADDS state (sched_add_job/sched_add_worker on fresh kvmgr entries),
* it does not clear what is already there first, so a second load onto
* an already-populated core would double the very jobs/workers this
* test needs to stay singular; a fresh core (matching every other test
* in this file's own "load into a separate instance" convention) keeps
* the fixture unambiguous. */
{
xce_sched_core_t core_b;
memset(&g_stub_state_b, 0, sizeof(g_stub_state_b));
fixture_init(&core_b, &g_stub_plugin_b);
rc = xce_sched_core_load_snapshot(&core_b, &snap);
TEST_ASSERT_EQUAL_INT(0, rc);
/* The actual proof: NO sched_account_jobs()/manual accounting call
* anywhere in this test -- xce_sched_core_load_snapshot() alone
* must already have charged this restored RUNNING job's
* allocation onto its worker's resInUse. */
worker = sched_get_worker(&core_b, "hostA");
TEST_ASSERT_NOT_NULL(worker);
rc = key_find(core_b.resmgr, (void*)"slots", (void**)&slots_res);
TEST_ASSERT_GREATER_THAN_INT(0, rc);
TEST_ASSERT_NOT_NULL(slots_res);
get_resvals(&(worker->resInUse), 1, &(slots_res->idx),
&resinuse_slots);
TEST_ASSERT_EQUAL_FLOAT(4.0f, resinuse_slots);
}
}
/* ---------------------------------------------------------------------- */
/* scheduler-snapshot-job-allocation task 3.2 (Req 3.5): the backward- */
/* compatibility case made explicit for the scenario Req 3.5's own text and */
@ -2156,8 +2329,9 @@ test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independen
/* (d) job->allocs and job->accounts are both NULL -- clean degrade, not */
/* a fabricated allocation or a crash; */
/* (e) with a REAL worker present, the REAL sched_account_jobs() chain */
/* (test_sched_account_jobs() above, task 3.1's own real */
/* reimplementation) correctly contributes ZERO from this job to the */
/* (this file's own reimplementation above, task 3.1, now called */
/* automatically by xce_sched_core_load_snapshot() itself per task */
/* 4.6) correctly contributes ZERO from this job to the */
/* worker's resInUse -- the concrete "used=0, identical to today's */
/* behavior" claim Req 3.5 and the originally-reported "info worker" */
/* symptom are both actually about, not merely that job->accounts is */
@ -2266,11 +2440,17 @@ test_snapshot_load_leaves_running_job_without_allocation_at_zero_resinuse(void)
TEST_ASSERT_NULL(restored->allocs);
TEST_ASSERT_NULL(restored->accounts);
/* (e): the REAL accounting chain, run against the restored core, charges
* NOTHING for this job (job->accounts == NULL skips it, same guard live
* dispatch's own sched_account_jobs() uses) -- the worker's resInUse for
* "slots" reads back as exactly 0, matching today's pre-feature
* behavior, not an uninitialized/undefined value. */
/* (e): the REAL accounting chain -- xce_sched_core_load_snapshot()
* itself already ran it once above (task 4.6) -- charges NOTHING for
* this job (job->accounts == NULL skips it, same guard live dispatch's
* own sched_account_jobs() uses) -- the worker's resInUse for "slots"
* reads back as exactly 0, matching today's pre-feature behavior, not
* an uninitialized/undefined value. No second manual call here: with
* nothing charged either way for this NULL-accounts job, a redundant
* call would be harmless for THIS assertion specifically, but is
* omitted anyway to match this file's one-call-per-load convention
* (see Phase 8 of the round-trip test above, where a second call would
* NOT be harmless). */
worker = sched_get_worker(&core, "hostA");
TEST_ASSERT_NOT_NULL(worker);
@ -2278,9 +2458,6 @@ test_snapshot_load_leaves_running_job_without_allocation_at_zero_resinuse(void)
TEST_ASSERT_GREATER_THAN_INT(0, rc);
TEST_ASSERT_NOT_NULL(slots_res);
rc = test_sched_account_jobs(&core);
TEST_ASSERT_EQUAL_INT(0, rc);
get_resvals(&(worker->resInUse), 1, &(slots_res->idx), &resinuse_slots);
TEST_ASSERT_EQUAL_FLOAT(0.0f, resinuse_slots);
}
@ -2307,6 +2484,8 @@ int main(void)
test_snapshot_load_leaves_allocs_and_accounts_null_for_job_without_allocation);
RUN_TEST(
test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independence);
RUN_TEST(
test_snapshot_load_populates_worker_resinuse_immediately_without_a_session);
RUN_TEST(
test_snapshot_load_leaves_running_job_without_allocation_at_zero_resinuse);
return UNITY_END();