fix(scheduler): never elect a job without a schedobj (bug-1897)

A job whose snapshot resreq fails to parse (any replay captured before
the bug-1888 fix) is kept by xce_sched_core_load_snapshot() with no
schedobj, but was still made a picker candidate -- by the loader and, in
simulation mode, by sim_bootstrap_picker()'s picker rebuild. The
allocator thread then dereferenced the NULL schedobj in
limit_job_election() and xce_scheduler --sim crashed.

Both push sites now skip such a job (it stays in the core so a restored
allocation is still charged), and sched_allocator() skips any picked job
without a schedobj as a backstop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-01 03:45:51 +00:00
parent 3d3f37e0fd
commit 0fe27d4be3
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
7 changed files with 239 additions and 15 deletions

View file

@ -1313,11 +1313,11 @@ test_snapshot_load_computes_resreq_fingerprint_for_restored_primary(void)
0, restored->schedobj->u.primary->resreq_fp.select_node_id);
}
/* 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
* schedobj construction is skipped rather than crashing on a NULL resreq.
* Proves job->schedobj stays NULL, identical to today's behavior. */
/* Regression-safety test: a job whose resreq fails to parse on load keeps
* job->resreq and job->schedobj NULL (schedobj construction is skipped
* rather than crashing on a NULL resreq, task 1.2), stays in the core, and
* -- bug-1897 -- is never pushed to the picker, so the allocator thread
* never elects a job with no schedobj. */
static void
test_snapshot_load_leaves_schedobj_null_when_resreq_fails_to_parse(void)
{
@ -1364,6 +1364,12 @@ test_snapshot_load_leaves_schedobj_null_when_resreq_fails_to_parse(void)
/* THIS feature's guard: schedobj construction is skipped, not crashed
* into, when resreq is NULL. */
TEST_ASSERT_NULL(restored->schedobj);
/* bug-1897: a job with no schedobj must never become a picker
* candidate -- the allocator thread runs plugin_job_election() on
* job->schedobj (limit_job_election() dereferenced it as NULL). */
TEST_ASSERT_EQUAL_INT_MESSAGE(
0, g_stub_state_a.ncandidates,
"a job whose resreq failed to parse must not be pushed to the picker");
}
/* ---------------------------------------------------------------------- */