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

@ -13,5 +13,6 @@ extern xce_job_alloc_t* make_job_alloc(uint64_t, int, xce_host_alloc_t**);
extern xce_alloc_t** make_allocs(int, ...);
extern xce_host_alloc_t** make_host_allocs(int, ...);
extern xce_job_account_t* make_job_account(xce_job_alloc_t*);
extern xce_job_alloc_t* dup_job_alloc(const xce_job_alloc_t*);
#endif

View file

@ -1,12 +1,17 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "misc.h"
#include "unity.h"
#include "xce/types.h"
#include "xcealloc.h"
/* generated Cap'n Proto codec — pulls in free_JobAlloc_ptr(), the existing
* NULL-safe free path dup_job_alloc()'s output must remain compatible with. */
#include "controller.capnp.h"
/* Heap buffer overflow: make_job_account() allocates account->accounts with
* room for exactly 1 xce_host_account_t* (xcealloc.c, calloc(1, ...)) but
* its own loop writes account->accounts[i] for i up to jalloc->nhosts-1 --
@ -88,10 +93,375 @@ static void test_make_host_allocs_multi_host_no_overflow(void)
TEST_ASSERT_EQUAL_STRING("host1", hallocs[1]->host);
}
/* ── dup_job_alloc() (scheduler-snapshot-job-allocation task 1.1) ───────
*
* dup_job_alloc() must build a fresh, independent xce_job_alloc_t from a
* source one -- composed entirely from make_alloc()/make_host_alloc()/
* make_job_alloc() -- so a snapshot-restored job's resource allocation can
* be reconstructed without aliasing the snapshot-producing core's own copy
* (Req 1.1, 1.4). The output must remain compatible with the existing,
* NULL-safe free_JobAlloc_ptr() -- no new free path is introduced.
*
* Unity here is built without UNITY_INCLUDE_DOUBLE, so xce_alloc_t.value
* (a double) is compared via float cast with TEST_ASSERT_EQUAL_FLOAT,
* matching the existing convention (scheduler/testsuite/test.c,
* controller/testsuite/test_e2e_capnp.c). */
/* single-host/single-resource copy: the simplest shape. */
static void test_dup_job_alloc_single_host_single_resource(void)
{
xce_alloc_t* a;
xce_host_alloc_t* h;
xce_host_alloc_t** hallocs;
xce_job_alloc_t* src;
xce_job_alloc_t* dup;
a = make_alloc((char*)"slots", (char*)"resv-x", 42, 3.0f, 60);
TEST_ASSERT_NOT_NULL(a);
h = make_host_alloc((char*)"host0", 1, make_allocs(1, a));
TEST_ASSERT_NOT_NULL(h);
hallocs = (xce_host_alloc_t**)calloc(1, sizeof(*hallocs));
hallocs[0] = h;
src = make_job_alloc(7777, 1, hallocs);
TEST_ASSERT_NOT_NULL(src);
dup = dup_job_alloc(src);
TEST_ASSERT_NOT_NULL(dup);
TEST_ASSERT_EQUAL_UINT64(src->jobid, dup->jobid);
TEST_ASSERT_EQUAL_INT(1, dup->nhosts);
TEST_ASSERT_EQUAL_INT(0, dup->nres_shared);
TEST_ASSERT_NULL(dup->allocs_shared);
TEST_ASSERT_EQUAL_STRING("host0", dup->allocs[0]->host);
TEST_ASSERT_EQUAL_INT(1, dup->allocs[0]->nallocs);
TEST_ASSERT_EQUAL_STRING("slots", dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_EQUAL_STRING("resv-x", dup->allocs[0]->allocs[0]->reserve);
TEST_ASSERT_EQUAL_UINT64(42, dup->allocs[0]->allocs[0]->instid);
TEST_ASSERT_EQUAL_FLOAT(3.0f, (float)dup->allocs[0]->allocs[0]->value);
/* independence: no pointer shared with src anywhere in the tree */
TEST_ASSERT_NOT_EQUAL(src, dup);
TEST_ASSERT_NOT_EQUAL(src->allocs, dup->allocs);
TEST_ASSERT_NOT_EQUAL(src->allocs[0], dup->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->host, dup->allocs[0]->host);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs, dup->allocs[0]->allocs);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0], dup->allocs[0]->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0]->resname,
dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0]->reserve,
dup->allocs[0]->allocs[0]->reserve);
free_JobAlloc_ptr(&src);
free_JobAlloc_ptr(&dup);
}
/* multi-host copy: 2 hosts, 1 resource each. */
static void test_dup_job_alloc_multi_host(void)
{
xce_alloc_t* a0;
xce_alloc_t* a1;
xce_host_alloc_t* h0;
xce_host_alloc_t* h1;
xce_host_alloc_t** hallocs;
xce_job_alloc_t* src;
xce_job_alloc_t* dup;
a0 = make_alloc((char*)"slots", NULL, 10, 1.0f, 30);
a1 = make_alloc((char*)"slots", NULL, 20, 2.0f, 45);
TEST_ASSERT_NOT_NULL(a0);
TEST_ASSERT_NOT_NULL(a1);
h0 = make_host_alloc((char*)"host0", 1, make_allocs(1, a0));
h1 = make_host_alloc((char*)"host1", 1, make_allocs(1, a1));
TEST_ASSERT_NOT_NULL(h0);
TEST_ASSERT_NOT_NULL(h1);
hallocs = (xce_host_alloc_t**)calloc(2, sizeof(*hallocs));
hallocs[0] = h0;
hallocs[1] = h1;
src = make_job_alloc(8888, 2, hallocs);
TEST_ASSERT_NOT_NULL(src);
dup = dup_job_alloc(src);
TEST_ASSERT_NOT_NULL(dup);
TEST_ASSERT_EQUAL_INT(2, dup->nhosts);
TEST_ASSERT_EQUAL_STRING("host0", dup->allocs[0]->host);
TEST_ASSERT_EQUAL_STRING("host1", dup->allocs[1]->host);
TEST_ASSERT_EQUAL_UINT64(10, dup->allocs[0]->allocs[0]->instid);
TEST_ASSERT_EQUAL_UINT64(20, dup->allocs[1]->allocs[0]->instid);
TEST_ASSERT_NOT_EQUAL(src->allocs, dup->allocs);
TEST_ASSERT_NOT_EQUAL(src->allocs[0], dup->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[1], dup->allocs[1]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->host, dup->allocs[0]->host);
TEST_ASSERT_NOT_EQUAL(src->allocs[1]->host, dup->allocs[1]->host);
free_JobAlloc_ptr(&src);
free_JobAlloc_ptr(&dup);
}
/* multi-resource-per-host copy: 1 host, 2 resources. */
static void test_dup_job_alloc_multi_resource_per_host(void)
{
xce_alloc_t* a0;
xce_alloc_t* a1;
xce_host_alloc_t* h0;
xce_host_alloc_t** hallocs;
xce_job_alloc_t* src;
xce_job_alloc_t* dup;
a0 = make_alloc((char*)"cpu", NULL, 1, 4.0f, 60);
a1 = make_alloc((char*)"mem", NULL, 2, 8.0f, 60);
TEST_ASSERT_NOT_NULL(a0);
TEST_ASSERT_NOT_NULL(a1);
h0 = make_host_alloc((char*)"host0", 2, make_allocs(2, a0, a1));
TEST_ASSERT_NOT_NULL(h0);
hallocs = (xce_host_alloc_t**)calloc(1, sizeof(*hallocs));
hallocs[0] = h0;
src = make_job_alloc(9999, 1, hallocs);
TEST_ASSERT_NOT_NULL(src);
dup = dup_job_alloc(src);
TEST_ASSERT_NOT_NULL(dup);
TEST_ASSERT_EQUAL_INT(2, dup->allocs[0]->nallocs);
TEST_ASSERT_EQUAL_STRING("cpu", dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_EQUAL_STRING("mem", dup->allocs[0]->allocs[1]->resname);
TEST_ASSERT_EQUAL_FLOAT(4.0f, (float)dup->allocs[0]->allocs[0]->value);
TEST_ASSERT_EQUAL_FLOAT(8.0f, (float)dup->allocs[0]->allocs[1]->value);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs, dup->allocs[0]->allocs);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0], dup->allocs[0]->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[1], dup->allocs[0]->allocs[1]);
free_JobAlloc_ptr(&src);
free_JobAlloc_ptr(&dup);
}
/* shared-resource-pool copy, combined with multi-host + multi-resource-per-
* host, as the comprehensive "byte-for-byte equivalent, shares no pointers"
* proof called out in the task's observable completion criterion. */
static void test_dup_job_alloc_shared_resource_pool_full_equivalence(void)
{
xce_alloc_t* h0r0;
xce_alloc_t* h0r1;
xce_alloc_t* h1r0;
xce_host_alloc_t* h0;
xce_host_alloc_t* h1;
xce_host_alloc_t** hallocs;
xce_alloc_t* s0;
xce_alloc_t* s1;
xce_job_alloc_t* src;
xce_job_alloc_t* dup;
h0r0 = make_alloc((char*)"cpu", (char*)"resv-a", 100, 2.0f, 60);
h0r1 = make_alloc((char*)"mem", (char*)"resv-b", 101, 4.0f, 90);
h1r0 = make_alloc((char*)"cpu", NULL, 200, 8.0f, 120);
TEST_ASSERT_NOT_NULL(h0r0);
TEST_ASSERT_NOT_NULL(h0r1);
TEST_ASSERT_NOT_NULL(h1r0);
h0 = make_host_alloc((char*)"host0", 2, make_allocs(2, h0r0, h0r1));
h1 = make_host_alloc((char*)"host1", 1, make_allocs(1, h1r0));
TEST_ASSERT_NOT_NULL(h0);
TEST_ASSERT_NOT_NULL(h1);
hallocs = (xce_host_alloc_t**)calloc(2, sizeof(*hallocs));
hallocs[0] = h0;
hallocs[1] = h1;
src = make_job_alloc(4242, 2, hallocs);
TEST_ASSERT_NOT_NULL(src);
s0 = make_alloc((char*)"license", NULL, 300, 1.0f, 30);
s1 = make_alloc((char*)"license2", (char*)"resv-c", 301, 3.0f, 45);
TEST_ASSERT_NOT_NULL(s0);
TEST_ASSERT_NOT_NULL(s1);
src->nres_shared = 2;
src->allocs_shared = make_allocs(2, s0, s1);
dup = dup_job_alloc(src);
TEST_ASSERT_NOT_NULL(dup);
/* --- byte-for-byte content equivalence --- */
TEST_ASSERT_EQUAL_UINT64(src->jobid, dup->jobid);
TEST_ASSERT_EQUAL_INT(src->nhosts, dup->nhosts);
TEST_ASSERT_EQUAL_INT(src->nres_shared, dup->nres_shared);
TEST_ASSERT_EQUAL_STRING("host0", dup->allocs[0]->host);
TEST_ASSERT_EQUAL_INT(2, dup->allocs[0]->nallocs);
TEST_ASSERT_EQUAL_STRING("cpu", dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_EQUAL_STRING("resv-a", dup->allocs[0]->allocs[0]->reserve);
TEST_ASSERT_EQUAL_UINT64(100, dup->allocs[0]->allocs[0]->instid);
TEST_ASSERT_EQUAL_FLOAT(2.0f, (float)dup->allocs[0]->allocs[0]->value);
TEST_ASSERT_EQUAL_STRING("mem", dup->allocs[0]->allocs[1]->resname);
TEST_ASSERT_EQUAL_STRING("resv-b", dup->allocs[0]->allocs[1]->reserve);
TEST_ASSERT_EQUAL_UINT64(101, dup->allocs[0]->allocs[1]->instid);
TEST_ASSERT_EQUAL_FLOAT(4.0f, (float)dup->allocs[0]->allocs[1]->value);
TEST_ASSERT_EQUAL_STRING("host1", dup->allocs[1]->host);
TEST_ASSERT_EQUAL_INT(1, dup->allocs[1]->nallocs);
TEST_ASSERT_EQUAL_STRING("cpu", dup->allocs[1]->allocs[0]->resname);
TEST_ASSERT_EQUAL_STRING("", dup->allocs[1]->allocs[0]->reserve);
TEST_ASSERT_EQUAL_UINT64(200, dup->allocs[1]->allocs[0]->instid);
TEST_ASSERT_EQUAL_FLOAT(8.0f, (float)dup->allocs[1]->allocs[0]->value);
TEST_ASSERT_NOT_NULL(dup->allocs_shared);
TEST_ASSERT_EQUAL_STRING("license", dup->allocs_shared[0]->resname);
TEST_ASSERT_EQUAL_UINT64(300, dup->allocs_shared[0]->instid);
TEST_ASSERT_EQUAL_FLOAT(1.0f, (float)dup->allocs_shared[0]->value);
TEST_ASSERT_EQUAL_STRING("license2", dup->allocs_shared[1]->resname);
TEST_ASSERT_EQUAL_STRING("resv-c", dup->allocs_shared[1]->reserve);
TEST_ASSERT_EQUAL_UINT64(301, dup->allocs_shared[1]->instid);
TEST_ASSERT_EQUAL_FLOAT(3.0f, (float)dup->allocs_shared[1]->value);
/* --- no pointer anywhere in the copy is shared with the source --- */
TEST_ASSERT_NOT_EQUAL(src, dup);
TEST_ASSERT_NOT_EQUAL(src->allocs, dup->allocs);
TEST_ASSERT_NOT_EQUAL(src->allocs_shared, dup->allocs_shared);
TEST_ASSERT_NOT_EQUAL(src->allocs[0], dup->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[1], dup->allocs[1]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->host, dup->allocs[0]->host);
TEST_ASSERT_NOT_EQUAL(src->allocs[1]->host, dup->allocs[1]->host);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs, dup->allocs[0]->allocs);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0], dup->allocs[0]->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[1], dup->allocs[0]->allocs[1]);
TEST_ASSERT_NOT_EQUAL(src->allocs[1]->allocs[0], dup->allocs[1]->allocs[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0]->resname,
dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_NOT_EQUAL(src->allocs[0]->allocs[0]->reserve,
dup->allocs[0]->allocs[0]->reserve);
TEST_ASSERT_NOT_EQUAL(src->allocs_shared[0], dup->allocs_shared[0]);
TEST_ASSERT_NOT_EQUAL(src->allocs_shared[1], dup->allocs_shared[1]);
TEST_ASSERT_NOT_EQUAL(src->allocs_shared[0]->resname,
dup->allocs_shared[0]->resname);
TEST_ASSERT_NOT_EQUAL(src->allocs_shared[1]->reserve,
dup->allocs_shared[1]->reserve);
free_JobAlloc_ptr(&src);
free_JobAlloc_ptr(&dup);
}
/* independence proof: mutate the source's strings/values in place, then
* free the source entirely, and confirm the copy is unaffected either way
* -- the direct test of the Req 1.4 "independent of the snapshot-producing
* instance" guarantee. */
static void test_dup_job_alloc_independence_after_mutate_and_free(void)
{
xce_alloc_t* a;
xce_host_alloc_t* h;
xce_host_alloc_t** hallocs;
xce_job_alloc_t* src;
xce_job_alloc_t* dup;
a = make_alloc((char*)"slots", (char*)"resv-x", 55, 5.0f, 70);
TEST_ASSERT_NOT_NULL(a);
h = make_host_alloc((char*)"host0", 1, make_allocs(1, a));
TEST_ASSERT_NOT_NULL(h);
hallocs = (xce_host_alloc_t**)calloc(1, sizeof(*hallocs));
hallocs[0] = h;
src = make_job_alloc(1234, 1, hallocs);
TEST_ASSERT_NOT_NULL(src);
dup = dup_job_alloc(src);
TEST_ASSERT_NOT_NULL(dup);
/* mutate source strings in place (same length -- strdup'd buffers are
* sized exactly, no room to grow) and mutate source numeric fields. */
strcpy(src->allocs[0]->host, "hostX");
strcpy(src->allocs[0]->allocs[0]->resname, "slotX");
src->allocs[0]->allocs[0]->value = 999.0;
src->allocs[0]->allocs[0]->instid = 999;
TEST_ASSERT_EQUAL_STRING("host0", dup->allocs[0]->host);
TEST_ASSERT_EQUAL_STRING("slots", dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_EQUAL_FLOAT(5.0f, (float)dup->allocs[0]->allocs[0]->value);
TEST_ASSERT_EQUAL_UINT64(55, dup->allocs[0]->allocs[0]->instid);
/* now free the source entirely; the copy must remain fully valid */
free_JobAlloc_ptr(&src);
TEST_ASSERT_NULL(src);
TEST_ASSERT_EQUAL_STRING("host0", dup->allocs[0]->host);
TEST_ASSERT_EQUAL_STRING("slots", dup->allocs[0]->allocs[0]->resname);
TEST_ASSERT_EQUAL_STRING("resv-x", dup->allocs[0]->allocs[0]->reserve);
TEST_ASSERT_EQUAL_FLOAT(5.0f, (float)dup->allocs[0]->allocs[0]->value);
TEST_ASSERT_EQUAL_UINT64(55, dup->allocs[0]->allocs[0]->instid);
free_JobAlloc_ptr(&dup);
}
/* fidelity check: make_alloc() itself stamps ->from = time(NULL) internally
* (xcealloc.c) -- a naive copy that only called make_alloc() and stopped
* would silently reset every restored allocation's start-time to copy-time.
* dup_job_alloc() must explicitly carry over the source's original from/
* duration instead. */
static void test_dup_job_alloc_from_duration_fidelity(void)
{
xce_alloc_t* a;
xce_host_alloc_t* h;
xce_host_alloc_t** hallocs;
xce_job_alloc_t* src;
xce_job_alloc_t* dup;
uint64_t old_from;
uint64_t now;
a = make_alloc((char*)"slots", NULL, 1, 1.0f, 0);
TEST_ASSERT_NOT_NULL(a);
/* overwrite make_alloc()'s own time(NULL) stamp with a fixed value far
* in the past, and a distinctive (negative -- "indefinite", per the
* scheduler.c convention of duration=-1) duration. */
old_from = (uint64_t)1000000;
a->from = old_from;
a->duration = -1;
h = make_host_alloc((char*)"host0", 1, make_allocs(1, a));
TEST_ASSERT_NOT_NULL(h);
hallocs = (xce_host_alloc_t**)calloc(1, sizeof(*hallocs));
hallocs[0] = h;
src = make_job_alloc(1, 1, hallocs);
TEST_ASSERT_NOT_NULL(src);
now = (uint64_t)time(NULL);
dup = dup_job_alloc(src);
TEST_ASSERT_NOT_NULL(dup);
TEST_ASSERT_EQUAL_UINT64(old_from, dup->allocs[0]->allocs[0]->from);
TEST_ASSERT_EQUAL_INT64(-1, dup->allocs[0]->allocs[0]->duration);
/* explicitly confirm the copy was NOT re-stamped to copy-time */
TEST_ASSERT_NOT_EQUAL(now, dup->allocs[0]->allocs[0]->from);
free_JobAlloc_ptr(&src);
free_JobAlloc_ptr(&dup);
}
void test_xcealloc(void)
{
printf("\n>> xcealloc test\n\n");
RUN_TEST(test_make_job_account_multi_host_no_overflow);
RUN_TEST(test_make_host_allocs_multi_host_no_overflow);
RUN_TEST(test_dup_job_alloc_single_host_single_resource);
RUN_TEST(test_dup_job_alloc_multi_host);
RUN_TEST(test_dup_job_alloc_multi_resource_per_host);
RUN_TEST(test_dup_job_alloc_shared_resource_pool_full_equivalence);
RUN_TEST(test_dup_job_alloc_independence_after_mutate_and_free);
RUN_TEST(test_dup_job_alloc_from_duration_fidelity);
}

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;