fix(scheduler): allocate every primary of a COMPOUND, not only the last (bug-1863)

Each primary's allocator (seqjob/parjob my_alloc) sets ctx->alloc to a
fresh job allocation covering only its own hosts, so a multi-primary
COMPOUND ("1*{A} + 2*{B}") dispatched only the last primary's hosts --
leaking the earlier allocations while every primary's hosts had been
debited in ctx.uncomitted.

sched_make_compound_alloc_ now folds each primary's allocation into one
job allocation via the new libxce merge_job_alloc(): a host placed by
several primaries becomes one entry with per-resource values added
(matched on resname, instid and reserve, so distinct instances are never
summed); new hosts and shared allocations are appended/merged the same
way. Partial-failure rollback remains bug-1766.

Tests: "1*{..} + 2*{..}" on 2x2 slots now allocates 3 slots across two
distinct hosts; "1*{..} + 1*{..}" yields one host entry with 2 slots
(both kept only the last primary before). Scheduler suite 87/87, lib 1/1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-25 13:14:28 +00:00
parent a6a0502d62
commit aea3473823
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
4 changed files with 176 additions and 5 deletions

View file

@ -323,3 +323,103 @@ xce_job_account_t* make_job_account(xce_job_alloc_t* jalloc)
return account;
}
/* merge_job_alloc() (bug-1863) -- add src's allocations into dst: a host
* already in dst gets src's per-resource values added to its matching
* entries (same resname, instid and reserve) or appended as new entries; a
* new host is appended; shared allocations merge the same way. Deep-copies
* from src (the caller still owns and frees it), so a host appears once in
* dst however many compound primaries placed tasks on it. Returns 0, or
* -ENOMEM with dst possibly partly merged (same OOM convention as
* dup_job_alloc above). */
static int same_alloc_slot(const xce_alloc_t* a, const xce_alloc_t* b)
{
return (strcmp(a->resname, b->resname) == 0) && (a->instid == b->instid)
&& (strcmp(a->reserve ? a->reserve : "",
b->reserve ? b->reserve : "") == 0);
}
static int merge_allocs(int* ndst, xce_alloc_t*** dst, int nsrc,
xce_alloc_t** src)
{
int i, j;
for (i = 0; i < nsrc; i++) {
xce_alloc_t** grown;
if (src[i] == NULL) {
continue;
}
for (j = 0; j < *ndst; j++) {
if (((*dst)[j] != NULL) && same_alloc_slot((*dst)[j], src[i])) {
(*dst)[j]->value += src[i]->value;
break;
}
}
if (j < *ndst) {
continue;
}
grown = (xce_alloc_t**)realloc(*dst,
sizeof(*grown) * (size_t)(*ndst + 1));
if (grown == NULL) {
return -ENOMEM;
}
*dst = grown;
(*dst)[*ndst] = dup_alloc(src[i]);
if ((*dst)[*ndst] == NULL) {
return -ENOMEM;
}
(*ndst)++;
}
return 0;
}
int merge_job_alloc(xce_job_alloc_t* dst, const xce_job_alloc_t* src)
{
int i, j, rc;
if ((dst == NULL) || (src == NULL)) {
return -EINVAL;
}
for (i = 0; i < src->nhosts; i++) {
const xce_host_alloc_t* sh = src->allocs[i];
xce_host_alloc_t** grown;
if (sh == NULL) {
continue;
}
for (j = 0; j < dst->nhosts; j++) {
if ((dst->allocs[j] != NULL)
&& (strcmp(dst->allocs[j]->host, sh->host) == 0)) {
break;
}
}
if (j < dst->nhosts) {
rc = merge_allocs(&(dst->allocs[j]->nallocs),
&(dst->allocs[j]->allocs), sh->nallocs,
sh->allocs);
if (rc < 0) {
return rc;
}
continue;
}
grown = (xce_host_alloc_t**)realloc(
dst->allocs, sizeof(*grown) * (size_t)(dst->nhosts + 1));
if (grown == NULL) {
return -ENOMEM;
}
dst->allocs = grown;
dst->allocs[dst->nhosts] = dup_host_alloc(sh);
if (dst->allocs[dst->nhosts] == NULL) {
return -ENOMEM;
}
dst->nhosts++;
}
return merge_allocs(&(dst->nres_shared), &(dst->allocs_shared),
src->nres_shared, src->allocs_shared);
}