fix(lib,scheduler): allocation constructors and allocators report OOM instead of building NULL-name records (bug-1882)

make_alloc() and make_host_alloc() assigned strdup() results unchecked,
so under memory exhaustion they returned a record whose resname/reserve/
host was NULL; merge_job_alloc()'s same_alloc_slot() (bug-1863) then
strcmp()'d it (ASAN SEGV at xcealloc.c:337). make_alloc() also passed a
NULL resname straight to strdup().
- xcealloc.c: make_alloc()/make_host_alloc() reject a NULL name (EINVAL)
  and check every strdup(), freeing what they built and returning NULL
  (ENOMEM); on failure make_host_alloc()'s caller keeps `allocs`.
  dup_host_alloc() frees its partial copy on failure instead of leaking.
- scheduler.c sched_make_allocs(): a record that cannot be built fails
  the whole array (NULL, ENOMEM) instead of leaving a NULL entry.
- seqjob/parjob my_alloc(): check every constructor; on failure free
  what was built (parjob also rolls back the remaining claims) and
  return -ENOMEM with ctx->alloc NULL -- the caller moves ctx.alloc onto
  the job whatever the rc.

Tests: new xcealloc_oom_test (8, --wrap=strdup) and
sched_make_allocs_oom_test (real scheduler_srcs + --wrap=strdup); a new
-ENOMEM test in the seqjob and parjob allocator suites. All RED before
(SEGV / "Expected NULL" / "Expected -12 Was 1"), GREEN after.
xce-build.sh all asan: 115/115 suites OK, Go tests OK.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-28 08:49:51 +00:00
parent c02d8e5cf6
commit f9146864a3
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
10 changed files with 563 additions and 12 deletions

View file

@ -134,6 +134,10 @@ int plugin_job_rusage_update(void* s, xce_sched_job_t* j, xce_job_rusages_t* r)
* how many resource entries it carries, not the internal xce_alloc_t
* field contents make_alloc() (xcealloc.c, unmodified, real) would fill
* in for a live cluster. */
/* bug-1882: set to make the stub fail like the real sched_make_allocs()
* does when make_alloc() cannot build a record. */
static int g_make_allocs_fail = 0;
xce_alloc_t** sched_make_allocs(xce_scheduler_t* sched, char* reserve,
int nres, int* idxs, float* vals,
int* duration)
@ -147,6 +151,11 @@ xce_alloc_t** sched_make_allocs(xce_scheduler_t* sched, char* reserve,
(void)vals;
(void)duration;
if (g_make_allocs_fail) {
errno = ENOMEM;
return NULL;
}
if (nres <= 0) {
return NULL;
}
@ -163,8 +172,8 @@ xce_alloc_t** sched_make_allocs(xce_scheduler_t* sched, char* reserve,
return allocs;
}
void setUp(void) { return; }
void tearDown(void) { return; }
void setUp(void) { g_make_allocs_fail = 0; }
void tearDown(void) { g_make_allocs_fail = 0; }
/* ---------------------------------------------------------------------- */
/* Fixture helpers */
@ -379,9 +388,56 @@ test_seqjob_prepare_allocation_guards_against_maxtasks_gt_1_fanout(void)
sched_ctx_fini(&ctx);
}
/*
* bug-1882: when the allocation records cannot be built (sched_make_allocs()
* returns NULL), my_alloc() must report -ENOMEM and leave ctx->alloc NULL
* -- sched_make_primary_allocation() moves ctx.alloc onto the job whatever
* the rc, so a half-built allocation there would be dispatched.
*/
static void test_my_alloc_reports_enomem_when_allocs_cannot_be_built(void)
{
xce_scheduler_t sched;
xce_resource_t* slots_res;
xce_sched_obj_t obj;
xce_primary_schedobj_t primary_obj;
xce_primary_resreq_t primary;
xce_sched_ctx_t ctx;
xce_sched_plugin_t plugin;
int rc;
memset(&sched, 0, sizeof(sched));
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
slots_res = register_slots_resource(sched.core.resmgr);
TEST_ASSERT_NOT_NULL(slots_res);
add_worker_with_slots(&(sched.core), WORKER_NAME, slots_res->idx, 4.0f);
memset(&primary, 0, sizeof(primary));
primary.minTasks = 1;
primary.maxTasks = 1;
primary.select = NULL;
init_primary_obj(&obj, &primary_obj, &primary);
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &(sched.core), &obj));
sched_ctx_set_primary(&ctx, &primary);
memset(&plugin, 0, sizeof(plugin));
plugin.sched = &sched;
plugin.core = &(sched.core);
g_make_allocs_fail = 1;
rc = my_alloc(&plugin, &ctx);
TEST_ASSERT_EQUAL_INT(-ENOMEM, rc);
TEST_ASSERT_NULL(ctx.alloc);
sched_ctx_fini(&ctx);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_my_alloc_reports_enomem_when_allocs_cannot_be_built);
RUN_TEST(
test_my_alloc_allocates_single_matching_host_through_shared_iterators);
RUN_TEST(test_seqjob_prepare_allocation_guards_against_maxtasks_gt_1_fanout);