feat(scheduler-snapshot-job-allocation): add dup_job_alloc deep-copy primitive

Task 1.1: new deep-copy for xce_job_alloc_t composed from existing
make_alloc/make_host_alloc/make_job_alloc, explicitly preserving each
allocation's original from/duration rather than accepting make_alloc's
own time(NULL) stamp. TDD: 6 new unit tests (single-host, multi-host,
multi-resource-per-host, shared-resource-pool, independence-after-free,
from/duration fidelity), all passing under ASAN.

Also logs bug-1723 (pre-existing, unrelated leak in two bug-1722 test
fixtures, confirmed via git-stash A/B rebuild to be byte-for-byte
identical before and after this change) -- documentation only, not fixed
here.

Requirements: 1.1, 1.4
This commit is contained in:
Rongsong Shen 2026-08-27 12:07:27 +00:00
parent 1b4737360b
commit 097e33ae34
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
5 changed files with 1023 additions and 418 deletions

View file

@ -167,6 +167,124 @@ xce_job_alloc_t* make_job_alloc(uint64_t jobid, int nhost,
return jalloc;
}
/* dup_job_alloc() (scheduler-snapshot-job-allocation task 1.1) -- deep-copy
* a job's resource allocation, independent of the source, for safe
* cross-core snapshot restoration (Req 1.1, 1.4). Composed entirely from
* make_alloc()/make_host_alloc()/make_job_alloc() below; on any allocation
* failure partway through, returns NULL rather than a partial/inconsistent
* result -- matching this file's existing convention (make_host_account()/
* make_job_account() do not unwind partial state on OOM either; a leak on
* this rare path is accepted, not a defect). */
static xce_alloc_t* dup_alloc(const xce_alloc_t* src)
{
xce_alloc_t* alloc = NULL;
if (src == NULL) {
return NULL;
}
/* make_alloc() stamps ->from = time(NULL) internally rather than
* accepting the caller's original value, so from/duration are
* explicitly re-stamped from src below after construction. */
alloc = make_alloc(src->resname, src->reserve, src->instid,
(float)src->value, 0);
if (alloc == NULL) {
return NULL;
}
alloc->from = src->from;
alloc->duration = src->duration;
return alloc;
}
static xce_host_alloc_t* dup_host_alloc(const xce_host_alloc_t* src)
{
xce_alloc_t** allocs = NULL;
xce_host_alloc_t* halloc = NULL;
int i;
if (src == NULL) {
return NULL;
}
if (src->nallocs > 0) {
allocs = (xce_alloc_t**)calloc((size_t)src->nallocs,
sizeof(*allocs));
if (allocs == NULL) {
return NULL;
}
for (i = 0; i < src->nallocs; i++) {
allocs[i] = dup_alloc(src->allocs[i]);
if (allocs[i] == NULL) {
return NULL;
}
}
}
halloc = make_host_alloc(src->host, src->nallocs, allocs);
return halloc;
}
xce_job_alloc_t* dup_job_alloc(const xce_job_alloc_t* src)
{
xce_host_alloc_t** hallocs = NULL;
xce_alloc_t** shared = NULL;
xce_job_alloc_t* jalloc = NULL;
int i;
if (src == NULL) {
return NULL;
}
if (src->nhosts > 0) {
hallocs = (xce_host_alloc_t**)calloc((size_t)src->nhosts,
sizeof(*hallocs));
if (hallocs == NULL) {
return NULL;
}
for (i = 0; i < src->nhosts; i++) {
hallocs[i] = dup_host_alloc(src->allocs[i]);
if (hallocs[i] == NULL) {
return NULL;
}
}
}
jalloc = make_job_alloc(src->jobid, src->nhosts, hallocs);
if (jalloc == NULL) {
return NULL;
}
/* make_job_alloc() always zeroes nres_shared/allocs_shared itself, so
* the shared-resource pool is populated here, after construction --
* the same "patch after construct" pattern used for from/duration
* above. */
if (src->nres_shared > 0) {
shared = (xce_alloc_t**)calloc((size_t)src->nres_shared,
sizeof(*shared));
if (shared == NULL) {
return NULL;
}
for (i = 0; i < src->nres_shared; i++) {
shared[i] = dup_alloc(src->allocs_shared[i]);
if (shared[i] == NULL) {
return NULL;
}
}
jalloc->allocs_shared = shared;
jalloc->nres_shared = src->nres_shared;
}
return jalloc;
}
xce_job_account_t* make_job_account(xce_job_alloc_t* jalloc)
{
xce_job_account_t* account = NULL;