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

@ -127,8 +127,25 @@ int put_resvals(zarray_t* array, int nres, int* idxs, float* vals)
return nres;
}
/* uncomit_at_ - the uncommitted amount of resource index `idx`.
*
* bug-1892: the context's uncommitted vectors are dense, keyed by resource
* index (schedobj.c fills them with xvec_idval()), unlike the positional
* total/inuse/wanted arrays unsatisfy_() compares them with. An index past
* the vector's end (sim_loop.c passes a short all-zero one) or a NULL
* vector means nothing is uncommitted. */
static float uncomit_at_(const xvec_t* uncomitted, int idx)
{
if ((uncomitted == NULL) || (uncomitted->data == NULL) || (idx < 0)
|| (idx >= uncomitted->count)) {
return 0.0f;
}
return ((const float*)uncomitted->data)[idx];
}
static int unsatisfy_(xce_sched_core_t* core, int nres, int* idxs, float* total,
float* inuse, float* uncomitted, float* wanted,
float* inuse, const xvec_t* uncomitted, float* wanted,
int* unmeet_idxs, float* unmeet_vals)
{
int i, j;
@ -136,10 +153,11 @@ static int unsatisfy_(xce_sched_core_t* core, int nres, int* idxs, float* total,
count = 0;
for (i = 0; i < nres; i++) {
if (total[i] < inuse[i] + uncomitted[i] + wanted[i]) {
float uncomit = uncomit_at_(uncomitted, idxs[i]);
if (total[i] < inuse[i] + uncomit + wanted[i]) {
TRACE("%s resource %d total %f inuse %f uncomit %f wanted %f",
__func__, idxs[i], total[i], inuse[i], uncomitted[i],
wanted[i]);
__func__, idxs[i], total[i], inuse[i], uncomit, wanted[i]);
count++;
}
@ -151,9 +169,11 @@ static int unsatisfy_(xce_sched_core_t* core, int nres, int* idxs, float* total,
j = 0;
for (i = 0; i < nres; i++) {
if (total[i] < inuse[i] + uncomitted[i] + wanted[i]) {
float uncomit = uncomit_at_(uncomitted, idxs[i]);
if (total[i] < inuse[i] + uncomit + wanted[i]) {
xce_resource_t* r = NULL;
float need = inuse[i] + uncomitted[i] + wanted[i] - total[i];
float need = inuse[i] + uncomit + wanted[i] - total[i];
vget(core->resmgr, idxs[i], (void**)&r);
TRACE("%s unmet resource %d (%s) shortfall %f", __func__, idxs[i],
@ -299,7 +319,7 @@ static int sched_check_shared_satisfy_(xce_sched_core_t* core, xvec_t* total,
}
rc = unsatisfy_(core, nres, (int*)wanted->idxs.data, (float*)total->data,
(float*)inuse->data, (float*)uncomitted->data,
(float*)inuse->data, uncomitted,
(float*)wanted->vals.data, (int*)unmeet->idxs.data,
(float*)unmeet->vals.data);
if (rc < 0) {
@ -374,7 +394,7 @@ static int sched_check_host_satisfy_(xce_sched_core_t* core,
}
rc = unsatisfy_(core, nres, (int*)wanted->idxs.data, (float*)total->data,
(float*)inuse->data, (float*)uncomitted->data,
(float*)inuse->data, uncomitted,
(float*)wanted->vals.data, (int*)unmeet->idxs.data,
(float*)unmeet->vals.data);
if (rc < 0) {

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();
}