fix(scheduler): admission check reads uncommitted amounts by resource index (bug-1892)

unsatisfy_() compared positional total/inuse/wanted arrays (entry i is
resource wanted->idxs[i]) with uncomitted[i], but the allocation
context's uncommitted vectors are dense and keyed by resource index.
From the second primary of a COMPOUND/ALTER job on, the check ignored
earlier primaries' claim on the same resource (over-admit) or charged
another resource's (wrong pend), with the matching wrong shortfall in
the pend reason.

unsatisfy_() now takes the vector and reads entry wanted->idxs[i]; an
index past its end (sim_loop.c's short all-zero vectors) or a NULL
vector counts as nothing uncommitted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-01 04:00:42 +00:00
parent da5c3065ed
commit e822b88886
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
2 changed files with 179 additions and 8 deletions

View file

@ -1285,6 +1285,153 @@ test_fill_limit_requests_host_limit_on_global_resource_reads_shared(void)
xlist_fini(&limits);
}
/*
* bug-1892: sched_check_host_satisfy()/sched_check_shared_satisfy() get the
* context's uncommitted vector dense, keyed by resource index (1024 floats,
* filled by xvec_idval(), schedobj.c), while total/inuse/wanted are
* positional (entry i is resource wanted->idxs[i]). unsatisfy_() used to
* read uncomitted[i] instead of uncomitted[wanted->idxs[i]].
*
* Fixture: resource idx 5 has total 10, nothing in use; the request wants
* 4 of it.
*/
#define B1892_RESIDX 5
static void b1892_dense_uncomit(xvec_t* v, int residx, float val)
{
TEST_ASSERT_EQUAL_INT(1024, xvec_init(v, 1024, sizeof(float)));
((float*)v->data)[residx] = val;
}
static void b1892_want(xvec_idval_t* wanted, float val)
{
TEST_ASSERT_TRUE(xvec_idval_init(wanted, 1) >= 0);
xvec_idval_store(wanted, B1892_RESIDX, val);
}
static xce_sched_worker_t* b1892_host(xce_sched_core_t* core)
{
xce_sched_worker_t* w;
int idx[1] = { B1892_RESIDX };
float val[1] = { 10.0f };
setup_core_with_worker(core, "hostA");
w = sched_get_worker(core, "hostA");
TEST_ASSERT_NOT_NULL(w);
TEST_ASSERT_TRUE(put_resvals(&(w->resvals), 1, idx, val) >= 0);
return w;
}
/* An earlier primary's uncommitted claim of 8 on the same resource leaves
* 2: the request for 4 must not fit (it used to read uncomitted[0] = 0). */
static void test_host_satisfy_counts_uncommitted_of_the_wanted_resource(void)
{
xce_sched_core_t core;
xce_sched_worker_t* w;
xvec_t uncomit;
xvec_idval_t wanted;
xvec_idval_t unmeet;
int rc;
w = b1892_host(&core);
b1892_dense_uncomit(&uncomit, B1892_RESIDX, 8.0f);
b1892_want(&wanted, 4.0f);
memset(&unmeet, 0, sizeof(unmeet));
rc = sched_check_host_satisfy(&core, w, &uncomit, &wanted, &unmeet);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "8 uncommitted + 4 wanted > 10");
TEST_ASSERT_EQUAL_INT(1, unmeet.count);
TEST_ASSERT_EQUAL_INT(B1892_RESIDX, ((int*)unmeet.idxs.data)[0]);
TEST_ASSERT_EQUAL_FLOAT(2.0f, ((float*)unmeet.vals.data)[0]);
xvec_idval_fini(&unmeet);
xvec_idval_fini(&wanted);
xvec_fini(&uncomit);
}
/* An uncommitted claim on a different resource (idx 0) must not be charged
* against resource 5: the request fits. */
static void test_host_satisfy_ignores_uncommitted_of_another_resource(void)
{
xce_sched_core_t core;
xce_sched_worker_t* w;
xvec_t uncomit;
xvec_idval_t wanted;
xvec_idval_t unmeet;
int rc;
w = b1892_host(&core);
b1892_dense_uncomit(&uncomit, 0, 8.0f);
b1892_want(&wanted, 4.0f);
memset(&unmeet, 0, sizeof(unmeet));
rc = sched_check_host_satisfy(&core, w, &uncomit, &wanted, &unmeet);
TEST_ASSERT_EQUAL_INT_MESSAGE(1, rc,
"resource 0's uncommitted amount is not "
"resource 5's");
xvec_idval_fini(&unmeet);
xvec_idval_fini(&wanted);
xvec_fini(&uncomit);
}
/* Same as the host case, against the shared pool. */
static void test_shared_satisfy_counts_uncommitted_of_the_wanted_resource(void)
{
xce_sched_core_t core;
xvec_t uncomit;
xvec_idval_t wanted;
xvec_idval_t unmeet;
int idx[1] = { B1892_RESIDX };
float val[1] = { 10.0f };
int rc;
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&core));
TEST_ASSERT_TRUE(put_resvals(&(core.sharedInst), 1, idx, val) >= 0);
b1892_dense_uncomit(&uncomit, B1892_RESIDX, 8.0f);
b1892_want(&wanted, 4.0f);
memset(&unmeet, 0, sizeof(unmeet));
rc = sched_check_shared_satisfy(&core, &uncomit, &wanted, &unmeet);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "8 uncommitted + 4 wanted > 10");
TEST_ASSERT_EQUAL_INT(1, unmeet.count);
TEST_ASSERT_EQUAL_FLOAT(2.0f, ((float*)unmeet.vals.data)[0]);
xvec_idval_fini(&unmeet);
xvec_idval_fini(&wanted);
xvec_fini(&uncomit);
}
/* sim_loop.c's reconcile checks pass an all-zero uncommitted vector sized
* to the request (here 1 entry) -- shorter than the wanted resource index.
* An index past its end counts as 0, never an out-of-bounds read. */
static void test_host_satisfy_short_uncommitted_vector_counts_as_zero(void)
{
xce_sched_core_t core;
xce_sched_worker_t* w;
xvec_t uncomit;
xvec_idval_t wanted;
xvec_idval_t unmeet;
int rc;
w = b1892_host(&core);
TEST_ASSERT_EQUAL_INT(1, xvec_init(&uncomit, 1, sizeof(float)));
b1892_want(&wanted, 4.0f);
memset(&unmeet, 0, sizeof(unmeet));
rc = sched_check_host_satisfy(&core, w, &uncomit, &wanted, &unmeet);
TEST_ASSERT_EQUAL_INT(1, rc);
xvec_idval_fini(&unmeet);
xvec_idval_fini(&wanted);
xvec_fini(&uncomit);
}
int main(void)
{
UNITY_BEGIN();
@ -1310,5 +1457,9 @@ int main(void)
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);
RUN_TEST(test_host_satisfy_counts_uncommitted_of_the_wanted_resource);
RUN_TEST(test_host_satisfy_ignores_uncommitted_of_another_resource);
RUN_TEST(test_shared_satisfy_counts_uncommitted_of_the_wanted_resource);
RUN_TEST(test_host_satisfy_short_uncommitted_vector_counts_as_zero);
return UNITY_END();
}