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:
parent
c02d8e5cf6
commit
f9146864a3
10 changed files with 563 additions and 12 deletions
|
|
@ -182,6 +182,11 @@ int plugin_job_rusage_update(void* s, xce_sched_job_t* j, xce_job_rusages_t* r)
|
|||
* assert_uncommitted_host_debit() (real ctx->uncomitted state, untouched
|
||||
* by this stub) rather than these fake xce_alloc_t's fields, exactly like
|
||||
* the seqjob equivalence tests already do. */
|
||||
/* bug-1882: arm to make the stub's Nth call (0-based) fail like the real
|
||||
* sched_make_allocs() does when make_alloc() cannot build a record. */
|
||||
static int g_make_allocs_fail_at = -1; /* -1 == disabled */
|
||||
static int g_make_allocs_calls = 0;
|
||||
|
||||
xce_alloc_t** sched_make_allocs(xce_scheduler_t* sched, char* reserve, int nres,
|
||||
int* idxs, float* vals, int* duration)
|
||||
{
|
||||
|
|
@ -194,6 +199,12 @@ xce_alloc_t** sched_make_allocs(xce_scheduler_t* sched, char* reserve, int nres,
|
|||
(void)vals;
|
||||
(void)duration;
|
||||
|
||||
if ((g_make_allocs_fail_at >= 0)
|
||||
&& (g_make_allocs_calls++ == g_make_allocs_fail_at)) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (nres <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -730,6 +741,53 @@ static void test_multi_bucket_plan_succeeds_through_real_my_alloc(void)
|
|||
sched_ctx_fini(&ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* bug-1882: a two-host plan whose allocation records cannot be built --
|
||||
* first for host 0, then for host 1 after host 0's record already exists --
|
||||
* must report -ENOMEM with ctx->alloc left NULL (the caller moves ctx->alloc
|
||||
* onto the job whatever the rc). ASAN catches a double free or use after
|
||||
* free on the cleanup path.
|
||||
*/
|
||||
static void test_my_alloc_reports_enomem_when_allocs_cannot_be_built(void)
|
||||
{
|
||||
int fail_at;
|
||||
|
||||
for (fail_at = 0; fail_at < 2; fail_at++) {
|
||||
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;
|
||||
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), "host-a", slots_res->idx, 2.0f);
|
||||
add_worker_with_slots(&(sched.core), "host-b", slots_res->idx, 1.0f);
|
||||
|
||||
memset(&primary, 0, sizeof(primary));
|
||||
primary.minTasks = 1;
|
||||
primary.maxTasks = 3;
|
||||
primary.select = NULL;
|
||||
|
||||
init_primary_obj(&obj, &primary_obj, &primary);
|
||||
|
||||
g_make_allocs_calls = 0;
|
||||
g_make_allocs_fail_at = fail_at;
|
||||
rc = drive_my_alloc(&sched, &obj, &primary, &ctx);
|
||||
g_make_allocs_fail_at = -1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-ENOMEM, rc);
|
||||
TEST_ASSERT_NULL(ctx.alloc);
|
||||
|
||||
sched_ctx_fini(&ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Req 4 AC 2: the exact descending-vs-ascending worked example from
|
||||
* requirements.md's Composition Algorithm. Two hosts, capacities 1 and 2;
|
||||
|
|
@ -1157,7 +1215,11 @@ static void test_shared_resource_debited_once_regardless_of_host_count(void)
|
|||
sched_ctx_fini(&ctx);
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void setUp(void)
|
||||
{
|
||||
g_make_allocs_fail_at = -1;
|
||||
g_make_allocs_calls = 0;
|
||||
}
|
||||
|
||||
void tearDown(void) { return; }
|
||||
|
||||
|
|
@ -1171,6 +1233,7 @@ int main(void)
|
|||
RUN_TEST(test_init_rejects_invalid_input);
|
||||
RUN_TEST(test_next_has_a_real_cancellation_point);
|
||||
RUN_TEST(test_multi_bucket_plan_succeeds_through_real_my_alloc);
|
||||
RUN_TEST(test_my_alloc_reports_enomem_when_allocs_cannot_be_built);
|
||||
RUN_TEST(test_descending_bucket_order_succeeds_where_ascending_fails);
|
||||
RUN_TEST(
|
||||
test_mid_plan_bucket_failure_rolls_back_before_later_plan_succeeds);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue