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:
parent
42fdeb6669
commit
14f95b06fe
5 changed files with 309 additions and 48 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue