feat(resreq-maxtasks-wiring): wire snapshot-restore ingest to real task count

snapshot_load.c's job-restore path now calls apply_job_task_count()
with the job's already-restored mintasks/maxtasks, right after its
resreq parse succeeds and before snapshot_build_schedobj() reads the
resreq's task count for its fingerprint. This is the simulation/replay
driver's only live call site (xce_sched_core_load_snapshot(), reached
via --sim), independent of and parallel to task 2.1's fresh-ingest
wiring in core_events.c.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
This commit is contained in:
Rongsong Shen 2026-09-06 04:28:48 +00:00
parent 0aadb49493
commit 6577e04196
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
3 changed files with 88 additions and 1 deletions

View file

@ -16,7 +16,7 @@
- _Requirements: 1.1_
- _Boundary: core_events.c (core_build_job)_
- [ ] 2.2 (P) Wire simulation/replay snapshot-restore ingest to apply the same real task count
- [x] 2.2 (P) Wire simulation/replay snapshot-restore ingest to apply the same real task count
- Call the new helper immediately after the snapshot-restore path's existing resource-request parse, using the job's already-restored minimum/maximum task count
- Observable: a job reconstructed from a saved snapshot via the simulation/replay driver has its dispatched resource request reflect the same real task count fresh ingest would produce
- _Requirements: 1.3_

View file

@ -444,6 +444,22 @@ int xce_sched_core_load_snapshot(xce_sched_core_t* core,
errmsg ? errmsg : "(null)");
FREEUP(errmsg);
FREEUP(job->resreq);
} else {
/* resreq-maxtasks-wiring task 2.2 (bug-1770, Req 1.3):
* overwrite the just-parsed resreq's standalone
* primary minTasks/maxTasks with the job's real
* restored task count -- job->mintasks/maxtasks are
* already correctly restored from wj->submit->
* mintasks/maxtasks above, BEFORE this parse (unlike
* core_build_job()'s ordering in core_events.c, task
* 2.1). Must run strictly before
* snapshot_build_schedobj() below reads the resreq's
* task count for its own fingerprint computation. No-op
* for a COMPOUND/ALTER resreq (apply_job_task_count()'s
* own tag gate) -- out of this task's boundary per
* design.md. */
apply_job_task_count(job->resreq, job->mintasks,
job->maxtasks);
}
}

View file

@ -1175,6 +1175,76 @@ test_snapshot_load_builds_schedobj_for_primary_compound_and_alter_resreq(void)
restored_alter->schedobj->project);
}
/*
* resreq-maxtasks-wiring task 2.2 (bug-1770, Req 1.3): a job restored from a
* saved snapshot -- live only via the simulation/replay driver
* (design.md's "Existing Architecture Analysis": xce_sched_core_load_
* snapshot()'s only real caller is sim.c's sim_locate_and_load_snapshot(),
* reachable only through main.c's --sim FILE offline-replay flag) -- must
* derive the SAME standalone primary resreq task count fresh ingest would
* (core_events.c's core_build_job(), task 2.1). job->mintasks/maxtasks are
* already correctly restored from wj->submit->mintasks/maxtasks earlier in
* this same function (before the resreq parse, unlike core_build_job()'s
* ordering) -- but before this fix, nothing applied them onto the just-
* parsed resreq's standalone primary, which parse_resreq_primary() always
* leaves at minTasks==maxTasks==1 regardless of the job's real requested
* count. */
static void
test_snapshot_load_wires_real_task_count_into_resreq_primary(void)
{
xce_sched_core_t core;
sched_snapshot_data_t in;
xce_jobs_data_t jobs_data;
xce_queue_t wire_queue;
xce_queue_t* queues[1];
xce_job_t* job;
xce_job_t* jobs[1];
xce_sched_job_t* restored;
int rc;
memset(&g_stub_state_a, 0, sizeof(g_stub_state_a));
fixture_init(&core, &g_stub_plugin_a);
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;
job = make_job_with_resreq(601, JOB_STAT_PEND, "normal", "alice", "proj1",
"type == any");
job->submit->mintasks = 4;
job->submit->maxtasks = 6;
jobs[0] = job;
memset(&jobs_data, 0, sizeof(jobs_data));
jobs_data.count = 1;
jobs_data.jobs = jobs;
memset(&in, 0, sizeof(in));
in.jobs = &jobs_data;
in.queues = queues;
in.n_queues = 1;
rc = xce_sched_core_load_snapshot(&core, &in);
TEST_ASSERT_EQUAL_INT(0, rc);
restored = sched_get_job(&core, 601);
TEST_ASSERT_NOT_NULL(restored);
TEST_ASSERT_EQUAL_UINT32(4, restored->mintasks);
TEST_ASSERT_EQUAL_UINT32(6, restored->maxtasks);
TEST_ASSERT_NOT_NULL(restored->resreq);
TEST_ASSERT_EQUAL_INT(XCE_RESREQ_PRIMARY, restored->resreq->tag);
TEST_ASSERT_NOT_NULL(restored->resreq->u.primary);
/* The fix under test: the standalone primary's minTasks/maxTasks must
* reflect the job's real restored task count, not parse_resreq_
* primary()'s always-1 default. */
TEST_ASSERT_EQUAL_INT(4, restored->resreq->u.primary->minTasks);
TEST_ASSERT_EQUAL_INT(6, restored->resreq->u.primary->maxTasks);
}
/* Regression-safety test (NOT a new feature): a job whose resreq fails to
* parse on load is a pre-existing, out-of-boundary gap (design.md "Known
* limitation") that task 1.2 must NOT try to fix -- it must only make sure
@ -2473,6 +2543,7 @@ int main(void)
test_snapshot_save_then_load_produces_behaviorally_equivalent_core);
RUN_TEST(
test_snapshot_load_builds_schedobj_for_primary_compound_and_alter_resreq);
RUN_TEST(test_snapshot_load_wires_real_task_count_into_resreq_primary);
RUN_TEST(
test_snapshot_load_leaves_schedobj_null_when_resreq_fails_to_parse);
RUN_TEST(