fix(scheduler): reset the per-host want at each primary's job-level prepare pass (bug-1883)
A COMPOUND/ALTER job's primaries share one ctx. The job-level prepare pass reset only alloc_req.shared, and the allocators append their slots want into alloc_req.host, so a later primary's host pre-filter (and a global limit on slots) read the previous primary's last per-host want: a 1-task primary after a 4-task one was filtered on slots:4 and pended although a host with its one free slot existed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
61f948e601
commit
c10f32ccd8
2 changed files with 58 additions and 0 deletions
|
|
@ -615,8 +615,15 @@ int plugin_prepare_allocation(xce_sched_core_t* core, xce_sched_ctx_t* ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->worker == NULL) {
|
if (ctx->worker == NULL) {
|
||||||
|
/* bug-1883: the job-level pass also resets .host. The allocators'
|
||||||
|
* hooks store this primary's job-level slots want there (the host
|
||||||
|
* pre-filter and a global limit on slots read it); a COMPOUND's
|
||||||
|
* previous primary left its last per-host want in .host, which the
|
||||||
|
* append-only store would otherwise extend, not replace. */
|
||||||
xvec_idval_fini(&(ctx->alloc_req.shared));
|
xvec_idval_fini(&(ctx->alloc_req.shared));
|
||||||
xvec_idval_init(&(ctx->alloc_req.shared), 8);
|
xvec_idval_init(&(ctx->alloc_req.shared), 8);
|
||||||
|
xvec_idval_fini(&(ctx->alloc_req.host));
|
||||||
|
xvec_idval_init(&(ctx->alloc_req.host), 8);
|
||||||
} else {
|
} else {
|
||||||
xvec_idval_fini(&(ctx->alloc_req.host));
|
xvec_idval_fini(&(ctx->alloc_req.host));
|
||||||
xvec_idval_init(&(ctx->alloc_req.host), 8);
|
xvec_idval_init(&(ctx->alloc_req.host), 8);
|
||||||
|
|
|
||||||
|
|
@ -875,6 +875,56 @@ static void test_failed_compound_leaves_no_partial_allocation(void)
|
||||||
TEST_ASSERT_NULL(sjob->allocs);
|
TEST_ASSERT_NULL(sjob->allocs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* bug-1883: a COMPOUND's primaries share one ctx, and the job-level prepare
|
||||||
|
* pass reset only alloc_req.shared, so a later 1-task (seqjob) primary's
|
||||||
|
* host pre-filter ran against the previous primary's per-host want with
|
||||||
|
* slots:1 appended. Host A (4 slots) takes the 4-task primary, leaving
|
||||||
|
* slots:4 behind; host B (1 slot) fits the 1-task primary, but the stale
|
||||||
|
* slots:4 rejected it and the job pended.
|
||||||
|
*/
|
||||||
|
static void test_compound_later_seqjob_primary_ignores_previous_want(void)
|
||||||
|
{
|
||||||
|
xce_scheduler_t* sched = start_sched();
|
||||||
|
xce_core_event_t ev;
|
||||||
|
xce_worker_t* worker_ptrs[2];
|
||||||
|
xce_decision_t* decision;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.type = XCE_CORE_EVENT_QUEUE_UPDATE;
|
||||||
|
ev.u.queue_update.queue = make_wire_queue("cmp5_queue", 1);
|
||||||
|
TEST_ASSERT_TRUE(xce_core_push_events(&(sched->core), &ev, 1) >= 0);
|
||||||
|
|
||||||
|
worker_ptrs[0] = make_wire_worker(WORKER_A_NAME, 4.0f);
|
||||||
|
worker_ptrs[1] = make_wire_worker(WORKER_B_NAME, 1.0f);
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.type = XCE_CORE_EVENT_WORKERS_PUSH;
|
||||||
|
ev.u.workers_push.nworkers = 2;
|
||||||
|
ev.u.workers_push.workers = worker_ptrs;
|
||||||
|
TEST_ASSERT_TRUE(xce_core_push_events(&(sched->core), &ev, 1) >= 0);
|
||||||
|
|
||||||
|
decision = e2e_dispatch_resreq(sched, JOB_ID + 8, "cmp5_queue",
|
||||||
|
"4*{rusage[mem=1]} + 1*{rusage[mem=1]}");
|
||||||
|
TEST_ASSERT_NOT_NULL(decision);
|
||||||
|
TEST_ASSERT_EQUAL_INT_MESSAGE(XCE_DECISION_DISPATCH, decision->op,
|
||||||
|
"the 1-task primary fits host B's free slot -- it must not be "
|
||||||
|
"filtered on the 4-task primary's per-host want");
|
||||||
|
TEST_ASSERT_EQUAL_FLOAT(5.0f,
|
||||||
|
(float)e2e_alloc_total_slots(decision->u.alloc));
|
||||||
|
TEST_ASSERT_EQUAL_INT(2, decision->u.alloc->nhosts);
|
||||||
|
for (i = 0; i < decision->u.alloc->nhosts; i++) {
|
||||||
|
xce_job_alloc_t one = *(decision->u.alloc);
|
||||||
|
|
||||||
|
one.nhosts = 1;
|
||||||
|
one.allocs = &(decision->u.alloc->allocs[i]);
|
||||||
|
TEST_ASSERT_EQUAL_FLOAT(
|
||||||
|
(strcmp(decision->u.alloc->allocs[i]->host, WORKER_A_NAME) == 0)
|
||||||
|
? 4.0f : 1.0f,
|
||||||
|
(float)e2e_alloc_total_slots(&one));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
@ -887,5 +937,6 @@ int main(void)
|
||||||
RUN_TEST(test_compound_allocation_includes_every_primary);
|
RUN_TEST(test_compound_allocation_includes_every_primary);
|
||||||
RUN_TEST(test_compound_primaries_on_same_host_merge);
|
RUN_TEST(test_compound_primaries_on_same_host_merge);
|
||||||
RUN_TEST(test_failed_compound_leaves_no_partial_allocation);
|
RUN_TEST(test_failed_compound_leaves_no_partial_allocation);
|
||||||
|
RUN_TEST(test_compound_later_seqjob_primary_ignores_previous_want);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue