myxce/src/lib/xcealloc.c
2026-09-28 09:35:08 +00:00

682 lines
18 KiB
C

/* vasprintf() (GNU extension) builds the equality functions' difference
* descriptions. */
#define _GNU_SOURCE
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
#include "xce/types.h"
#include "xcealloc.h"
/* make_alloc() never returns a record with a NULL resname or reserve
* (bug-1882): a NULL resname is rejected (errno EINVAL) and a failed
* strdup() frees what was built and returns NULL (errno ENOMEM), because
* merge_job_alloc()'s slot matching and every other consumer strcmp() these
* strings. */
xce_alloc_t* make_alloc(char* resname, char* reserve, uint64_t instid,
float value, int duration)
{
xce_alloc_t* alloc = NULL;
if (resname == NULL) {
errno = EINVAL;
return NULL;
}
alloc = (xce_alloc_t*)calloc(1, sizeof(*alloc));
if (alloc == NULL) {
return NULL;
}
alloc->resname = strdup(resname);
alloc->reserve = strdup((reserve != NULL) ? reserve : "");
if ((alloc->resname == NULL) || (alloc->reserve == NULL)) {
FREEUP(alloc->resname);
FREEUP(alloc->reserve);
FREEUP(alloc);
errno = ENOMEM;
return NULL;
}
alloc->instid = instid;
alloc->value = value;
alloc->from = time(NULL);
alloc->duration = duration;
return alloc;
}
static xce_res_account_t* make_res_account(xce_alloc_t* alloc)
{
xce_res_account_t* account = NULL;
account = (xce_res_account_t*)calloc(1, sizeof(*account));
if (account == NULL) {
return NULL;
}
account->instid = alloc->instid;
account->resname = strdup(alloc->resname);
account->residx = -1;
account->used = 0.0;
account->reserved = alloc->value;
account->allocated = alloc->value;
return account;
}
xce_alloc_t** make_allocs(int nallocs, ...)
{
xce_alloc_t** allocs = NULL;
int i;
va_list ap;
if (nallocs <= 0) {
return NULL;
}
allocs = (xce_alloc_t**)calloc(nallocs, sizeof(*allocs));
if (allocs == NULL) {
return NULL;
}
va_start(ap, nallocs);
for (i = 0; i < nallocs; i++) {
xce_alloc_t* alloc = va_arg(ap, xce_alloc_t*);
allocs[i] = alloc;
}
va_end(ap);
return allocs;
}
/* make_host_alloc() takes ownership of `allocs` only on success; on failure
* (NULL host: EINVAL, failed strdup(): ENOMEM) it returns NULL and the
* caller still owns `allocs` (bug-1882: never a record with a NULL host). */
xce_host_alloc_t* make_host_alloc(char* host, int nalloc, xce_alloc_t** allocs)
{
xce_host_alloc_t* halloc = NULL;
if (host == NULL) {
errno = EINVAL;
return NULL;
}
halloc = (xce_host_alloc_t*)calloc(1, sizeof(*halloc));
if (halloc == NULL) {
return NULL;
}
halloc->host = strdup(host);
if (halloc->host == NULL) {
FREEUP(halloc);
errno = ENOMEM;
return NULL;
}
halloc->nallocs = nalloc;
halloc->allocs = allocs;
return halloc;
}
static xce_host_account_t* make_host_account(xce_host_alloc_t* halloc)
{
xce_host_account_t* haccount = NULL;
int i;
haccount = (xce_host_account_t*)calloc(1, sizeof(*haccount));
if (haccount == NULL) {
return NULL;
}
haccount->accounts = (xce_res_account_t**)calloc(
halloc->nallocs, sizeof(xce_res_account_t*));
if (haccount->accounts == NULL) {
FREEUP(haccount);
return NULL;
}
for (i = 0; i < halloc->nallocs; i++) {
haccount->accounts[i] = make_res_account(halloc->allocs[i]);
}
haccount->naccount = halloc->nallocs;
haccount->host = strdup(halloc->host);
return haccount;
}
xce_host_alloc_t** make_host_allocs(int nhosts, ...)
{
xce_host_alloc_t** hallocs = NULL;
int i;
va_list ap;
if (nhosts <= 0) {
return NULL;
}
hallocs = (xce_host_alloc_t**)calloc((size_t)nhosts, sizeof(*hallocs));
if (hallocs == NULL) {
return NULL;
}
va_start(ap, nhosts);
for (i = 0; i < nhosts; i++) {
xce_host_alloc_t* halloc = va_arg(ap, xce_host_alloc_t*);
hallocs[i] = halloc;
}
va_end(ap);
return hallocs;
}
xce_job_alloc_t* make_job_alloc(uint64_t jobid, int nhost,
xce_host_alloc_t** hallocs)
{
xce_job_alloc_t* jalloc = NULL;
jalloc = (xce_job_alloc_t*)calloc(1, sizeof(*jalloc));
if (jalloc == NULL) {
return NULL;
}
jalloc->jobid = jobid;
jalloc->nhosts = nhost;
jalloc->allocs = hallocs;
jalloc->nres_shared = 0;
jalloc->allocs_shared = NULL;
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) {
free_ResAlloc_list(src->nallocs, allocs);
return NULL;
}
}
}
halloc = make_host_alloc(src->host, src->nallocs, allocs);
if (halloc == NULL) {
free_ResAlloc_list(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;
int i;
account = (xce_job_account_t*)calloc(1, sizeof(*account));
if (account == NULL) {
return NULL;
}
account->accounts = (xce_host_account_t**)calloc(
(size_t)jalloc->nhosts, sizeof(xce_host_account_t*));
if (account->accounts == NULL) {
FREEUP(account);
return NULL;
}
for (i = 0; i < jalloc->nhosts; i++) {
account->accounts[i] = make_host_account(jalloc->allocs[i]);
}
account->nhosts = jalloc->nhosts;
account->accounts_shared = NULL;
if (jalloc->nres_shared) {
account->accounts_shared = (xce_res_account_t**)calloc(
jalloc->nres_shared, sizeof(xce_res_account_t*));
for (i = 0; i < jalloc->nres_shared; i++) {
account->accounts_shared[i]
= make_res_account(jalloc->allocs_shared[i]);
}
account->nres_shared = jalloc->nres_shared;
}
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);
}
/* ------------------------------------------------------------------------
* Allocation equality (libxce-alloc-preason-equality). Contract in
* xcealloc.h. The description, when requested, is built bottom-up: the
* innermost comparison formats the differing field, and each enclosing list
* prefixes its own path segment and frees the inner string.
* ---------------------------------------------------------------------- */
/* *why = formatted string, or NULL if the allocation fails (vasprintf leaves
* its output undefined then). No-op when why is NULL. */
static void eq_why(char** why, const char* fmt, ...)
__attribute__((format(printf, 2, 3)));
static void eq_why(char** why, const char* fmt, ...)
{
va_list ap;
char* s = NULL;
if (why == NULL) {
return;
}
va_start(ap, fmt);
if (vasprintf(&s, fmt, ap) < 0) {
s = NULL;
}
va_end(ap);
*why = s;
}
/* Prefix an inner description with the list entry's path segment,
* `list[i](key=label).`; a failed inner description stays NULL. */
static void eq_why_wrap(char** why, const char* list, int i, const char* key,
const char* label)
{
char* inner;
if ((why == NULL) || (*why == NULL)) {
return;
}
inner = *why;
eq_why(why, "%s[%d](%s=%s).%s", list, i, key, label, inner);
free(inner);
}
static const char* eq_label(const char* a, const char* b)
{
if (a != NULL) {
return a;
}
return (b != NULL) ? b : "?";
}
static const char* eq_nullness(const void* p)
{
return (p != NULL) ? "non-NULL" : "NULL";
}
static int eq_str_field(const char* name, const char* a, const char* b,
char** why)
{
if (str_eq(a, b)) {
return 1;
}
eq_why(why, "%s: %s%s%s != %s%s%s", name, (a != NULL) ? "\"" : "",
(a != NULL) ? a : "NULL", (a != NULL) ? "\"" : "",
(b != NULL) ? "\"" : "", (b != NULL) ? b : "NULL",
(b != NULL) ? "\"" : "");
return 0;
}
/* In-order comparison of two `n`-entry xce_alloc_t lists named `list`. */
static int eq_alloc_list(const char* list, xce_alloc_t* const* a,
xce_alloc_t* const* b, int n, char** why)
{
int i;
for (i = 0; i < n; i++) {
if (a[i] == b[i]) {
continue;
}
if ((a[i] == NULL) || (b[i] == NULL)) {
eq_why(why, "%s[%d]: %s != %s", list, i, eq_nullness(a[i]),
eq_nullness(b[i]));
return 0;
}
if (!alloc_eq(a[i], b[i], why)) {
eq_why_wrap(why, list, i, "resname",
eq_label(a[i]->resname, b[i]->resname));
return 0;
}
}
return 1;
}
int alloc_eq(const xce_alloc_t* a, const xce_alloc_t* b, char** why)
{
if (why != NULL) {
*why = NULL;
}
if (a == b) {
return 1;
}
if ((a == NULL) || (b == NULL)) {
eq_why(why, "alloc: %s != %s", eq_nullness(a), eq_nullness(b));
return 0;
}
if (!eq_str_field("resname", a->resname, b->resname, why)
|| !eq_str_field("reserve", a->reserve, b->reserve, why)) {
return 0;
}
if (a->value != b->value) {
eq_why(why, "value: %.17g != %.17g", a->value, b->value);
return 0;
}
if (a->duration != b->duration) {
eq_why(why, "duration: %" PRId64 " != %" PRId64, a->duration,
b->duration);
return 0;
}
if (a->from != b->from) {
eq_why(why, "from: %" PRIu64 " != %" PRIu64, a->from, b->from);
return 0;
}
return 1;
}
int host_alloc_eq(const xce_host_alloc_t* a, const xce_host_alloc_t* b,
char** why)
{
if (why != NULL) {
*why = NULL;
}
if (a == b) {
return 1;
}
if ((a == NULL) || (b == NULL)) {
eq_why(why, "host_alloc: %s != %s", eq_nullness(a), eq_nullness(b));
return 0;
}
if (!eq_str_field("host", a->host, b->host, why)) {
return 0;
}
if (a->nallocs != b->nallocs) {
eq_why(why, "nallocs: %d != %d", a->nallocs, b->nallocs);
return 0;
}
return eq_alloc_list("allocs", a->allocs, b->allocs, a->nallocs, why);
}
static void eq_fmt_jobid(char* buf, size_t sz, uint64_t jobid)
{
if (fmt_jobid(buf, sz, jobid) < 0) {
snprintf(buf, sz, "%" PRIu64, jobid);
}
}
int job_alloc_eq(const xce_job_alloc_t* a, const xce_job_alloc_t* b,
char** why)
{
int i;
if (why != NULL) {
*why = NULL;
}
if (a == b) {
return 1;
}
if ((a == NULL) || (b == NULL)) {
eq_why(why, "job_alloc: %s != %s", eq_nullness(a), eq_nullness(b));
return 0;
}
if (a->jobid != b->jobid) {
if (why != NULL) {
char ja[128];
char jb[128];
eq_fmt_jobid(ja, sizeof(ja), a->jobid);
eq_fmt_jobid(jb, sizeof(jb), b->jobid);
eq_why(why, "jobid: %s != %s", ja, jb);
}
return 0;
}
if (a->nhosts != b->nhosts) {
eq_why(why, "nhosts: %d != %d", a->nhosts, b->nhosts);
return 0;
}
if (a->nres_shared != b->nres_shared) {
eq_why(why, "nres_shared: %d != %d", a->nres_shared, b->nres_shared);
return 0;
}
for (i = 0; i < a->nhosts; i++) {
const xce_host_alloc_t* ha = a->allocs[i];
const xce_host_alloc_t* hb = b->allocs[i];
if (ha == hb) {
continue;
}
if ((ha == NULL) || (hb == NULL)) {
eq_why(why, "allocs[%d]: %s != %s", i, eq_nullness(ha),
eq_nullness(hb));
return 0;
}
if (!host_alloc_eq(ha, hb, why)) {
eq_why_wrap(why, "allocs", i, "host", eq_label(ha->host, hb->host));
return 0;
}
}
return eq_alloc_list("allocs_shared", a->allocs_shared, b->allocs_shared,
a->nres_shared, why);
}