refactor(scheduler): read slots' job total from alloc_req instead of name-checking

The previous fix for the "slots" regression (5b18f14a) worked but was an
unneeded special case: strcmp(vres->res->name, "slots") to fall back to
nslots directly. alloc_req->host already holds exactly that value during
the global phase -- both allocators' own unconditional "add slots" step
stages it there regardless of phase, and plugin_prepare_allocation()
never resets .host during the global phase, so by the time sched_fill_
limit_requests() runs (right after, same invocation), .host contains
exactly "slots"'s job-wide total and nothing else.

Read alloc_req->host FIRST for this combination and fall back to the
rusages-based on-demand computation only when nothing is staged there
(get_resval_by_id()'s "not found" == 0.0, safe here since "slots" is
never legitimately 0 -- seqjob only runs for maxTasks==1, parjob only
for maxTasks>1). Data-driven instead of name-based, matching bug-1789's
own earlier flag-based-dispatch philosophy, and generalizes for free to
any future resource an allocator plugin similarly pre-stages outside
its normal rusages loop.

test_fill_limit_requests_global_limit_on_slots_reads_host now pre-stages
alloc_req->host exactly as the allocators do, rusages left empty,
matching production exactly.

Full xce-build.sh all asan --incremental: 94/94 Ok. Live re-verified
against both the original gitea#46 scenario and the ug1_global_mem
scenario: byte-identical scheduler traces to every prior verification
round.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLfB5uWRdmx2WiZSFC7Fm7
This commit is contained in:
Rongsong Shen 2026-09-08 11:14:55 +00:00
parent 604373b48a
commit a6d96705d9
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
2 changed files with 61 additions and 48 deletions

View file

@ -925,23 +925,28 @@ test_fill_limit_requests_global_limit_on_host_resource_reads_host(void)
* rusage entry -- seqjob_prepare_allocation()/parjob_prepare_allocation()
* derive its want directly from the job's own task count (1.0 for seqjob,
* always maxTasks==1; (float)ctx->nslots for parjob), via their own
* unconditional key_find("slots", ...) + store, entirely outside the
* normal rusages loop. The general on-demand path above (calc_wanted_job_
* total_resval(), keyed by scanning rusages for a matching entry) always
* misses for "slots" as a result -- confirmed live against a fresh deploy
* of limits.yaml's actual ug1_slots/ug1_normal_slots/limit1 (all global,
* slots-resname'd): matching worked, but every job's "wanted" silently
* stayed 0, so the caps never bound at all, reintroducing bug-1789's
* exact original symptom. sched_fill_limit_requests() (resource.c) now
* falls back to `nslots` directly (the SAME value those allocators
* already store as "slots"'s own per-host want) whenever the rusages
* lookup misses AND the resource is named "slots" -- this test proves
* that fallback with NO "slots" entry in rusages at all, matching the
* real production shape exactly (unlike this test's own first version,
* which artificially added a "slots" rusages entry and masked this exact
* gap).
* unconditional key_find("slots", ...) + store into alloc_req->host,
* regardless of phase, entirely outside the normal rusages loop. The
* general on-demand path (calc_wanted_job_total_resval(), keyed by
* scanning rusages for a matching entry) always misses for "slots" as a
* result -- confirmed live against a fresh deploy of limits.yaml's actual
* ug1_slots/ug1_normal_slots/limit1 (all global, slots-resname'd):
* matching worked, but every job's "wanted" silently stayed 0, so the caps
* never bound at all, reintroducing bug-1789's exact original symptom.
*
* sched_fill_limit_requests() (resource.c) now reads alloc_req->host FIRST
* for this combination -- since the allocator's own unconditional "add
* slots" step already puts exactly the job-wide total there, no name-based
* special case is needed, just reading the data that's already staged --
* and only falls back to the rusages-based computation when nothing is
* staged (any OTHER per-host resource). This test proves the "slots" path
* by pre-staging alloc_req->host exactly as the allocator plugins already
* do (value == the job's whole nslots, matching parjob's own (float)ctx->
* nslots store) and leaving rusages EMPTY -- the real production shape,
* unlike this test's own first version, which artificially added a
* "slots" rusages entry and masked this exact gap.
*/
static void test_fill_limit_requests_global_limit_on_slots_uses_nslots(void)
static void test_fill_limit_requests_global_limit_on_slots_reads_host(void)
{
xce_scheduler_t sched;
xce_resource_t slots_res_in = { 0 };
@ -984,6 +989,10 @@ static void test_fill_limit_requests_global_limit_on_slots_uses_nslots(void)
TEST_ASSERT_EQUAL_INT(0, rc);
rc = xvec_idval_init(&(alloc_req.host), 8);
TEST_ASSERT_EQUAL_INT(0, rc);
/* pre-staged into .host, exactly as seqjob/parjob_prepare_allocation()
* always do for "slots" regardless of phase. */
rc = xvec_idval_store(&(alloc_req.host), slots_res->idx, 4.0f);
TEST_ASSERT_TRUE(rc >= 0);
rc = sched_fill_limit_requests(&alloc_req, &limits, &rusages, 1, 4);
TEST_ASSERT_EQUAL_INT(0, rc);
@ -991,6 +1000,9 @@ static void test_fill_limit_requests_global_limit_on_slots_uses_nslots(void)
TEST_ASSERT_EQUAL_INT(1, alloc_req.shared.count);
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.shared.idxs.data)[0]);
TEST_ASSERT_EQUAL_FLOAT(4.0f, ((float*)alloc_req.shared.vals.data)[0]);
/* .host stays exactly as pre-staged -- the limit's own vres entry went
* to .shared, nothing new was written to .host. */
TEST_ASSERT_EQUAL_INT(1, alloc_req.host.count);
xlist_fini(&rusages);
xlist_fini(&limits);
@ -1144,7 +1156,7 @@ int main(void)
RUN_TEST(test_reconcile_job_resource_usage_null_args_return_einval);
RUN_TEST(test_ctx_uncommit_host_reverses_provisional_debit);
RUN_TEST(test_fill_limit_requests_global_limit_on_host_resource_reads_host);
RUN_TEST(test_fill_limit_requests_global_limit_on_slots_uses_nslots);
RUN_TEST(test_fill_limit_requests_global_limit_on_slots_reads_host);
RUN_TEST(test_fill_limit_requests_host_limit_on_host_resource_reads_host);
RUN_TEST(test_fill_limit_requests_host_limit_on_global_resource_reads_shared);
return UNITY_END();