refactor(scheduler): move job-total limit computation to the limit plugin (bug-1790)
bug-1790's fix worked but put limit-plugin-specific logic in the wrong place: seqjob_prepare_allocation()/parjob_prepare_allocation() (the allocator plugins) unconditionally pre-computed a job-wide total for every per-host resource in the job's rusages, "just in case" some global limit needed one. That's the allocator plugins' concern leaking into limit evaluation -- duplicated across both allocators (a third would need it too), and wasteful (computed for resources no limit actually references). Moved ownership to sched_fill_limit_requests() (resource.c), called once centrally from plugin_prepare_allocation() regardless of which allocator ran: - Removed calc_wanted_host_resval_job_totals() and its calls from both allocator plugins entirely. - Added calc_wanted_job_total_resval() (resreq.c): an on-demand, single-resource lookup by name (no resmgr/key_find needed -- the caller already has the resolved xce_resource_t), computing the same job-wide-total formula only for the one (scope, underlying-resource) combination that needs it -- a GLOBAL limit on a per-host resource. The other three combinations are unchanged, still reading straight out of alloc_req. - sched_fill_limit_requests() gains rusages/nhosts/nslots parameters back (a genuine need this time, not a re-added unused one). plugin.c's two call sites pass obj->u.primary->resreq->rusages (NULL for COMPOUND/ALTER, matching this codebase's existing scope convention for those tags) and ctx->nhosts/ctx->nslots. All 23 testsuite link-satisfying stubs mechanically updated. Removed the two allocator-level regression tests from the first pass (the behavior they proved no longer exists by design); rewrote test_fill_limit_requests_global_limit_on_host_resource_reads_host to exercise the new on-demand path directly, nslots=3 to prove the job's WHOLE total is used. Full xce-build.sh all asan --incremental: 94/94 Ok, zero regressions. Live re-verified against the same ug1_global_mem deploy/scenario: byte-identical scheduler trace to the pre-refactor result, confirming behavior-preserving. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLfB5uWRdmx2WiZSFC7Fm7
This commit is contained in:
parent
b612f80bd6
commit
fa34b038f7
33 changed files with 308 additions and 350 deletions
|
|
@ -315,8 +315,8 @@ extern int calc_wanted_global_resvals(xvec_idval_t*, var, xlist_t*,
|
||||||
rusage_per_type_t, int, int);
|
rusage_per_type_t, int, int);
|
||||||
extern int calc_wanted_host_resvals(xvec_idval_t*, var, xlist_t*,
|
extern int calc_wanted_host_resvals(xvec_idval_t*, var, xlist_t*,
|
||||||
rusage_per_type_t, int, int);
|
rusage_per_type_t, int, int);
|
||||||
extern int calc_wanted_host_resval_job_totals(xvec_idval_t*, var, xlist_t*,
|
extern int calc_wanted_job_total_resval(xlist_t*, rusage_per_type_t, int,
|
||||||
rusage_per_type_t, int, int);
|
int, const char*, float*);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Overwrite minTasks/maxTasks on resreq's own top-level primary with the
|
* Overwrite minTasks/maxTasks on resreq's own top-level primary with the
|
||||||
|
|
|
||||||
130
src/lib/resreq.c
130
src/lib/resreq.c
|
|
@ -731,21 +731,9 @@ int format_resreq(char** buf, xce_resreq_t* resreq)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int calc_wanted_global_resvals(xvec_idval_t* resvals, var resmgr,
|
||||||
* calc_wanted_job_total_resvals_ - shared body for calc_wanted_global_resvals()
|
xlist_t* rusages, rusage_per_type_t perflag,
|
||||||
* and calc_wanted_host_resval_job_totals(): computes each matching rusage
|
int nhosts, int nslots)
|
||||||
* entry's JOB-WIDE TOTAL want (PER_TASK: value*nslots, PER_JOB: value,
|
|
||||||
* PER_HOST: value*nhosts -- nslots/nhosts are the whole job's totals, not
|
|
||||||
* any one host's share). `match_global` selects which resources are
|
|
||||||
* "matching": nonzero processes only XCE_RESF_GLOBAL resources (the
|
|
||||||
* original calc_wanted_global_resvals() behavior), zero processes only
|
|
||||||
* non-GLOBAL (per-host) ones instead.
|
|
||||||
*/
|
|
||||||
static int calc_wanted_job_total_resvals_(xvec_idval_t* resvals, var resmgr,
|
|
||||||
xlist_t* rusages,
|
|
||||||
rusage_per_type_t perflag,
|
|
||||||
int nhosts, int nslots,
|
|
||||||
int match_global)
|
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
|
@ -772,14 +760,8 @@ static int calc_wanted_job_total_resvals_(xvec_idval_t* resvals, var resmgr,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match_global) {
|
if ((r->flags & XCE_RESF_GLOBAL) == 0) {
|
||||||
if ((r->flags & XCE_RESF_GLOBAL) == 0) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((r->flags & XCE_RESF_GLOBAL) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
perflag1 = v->per_flag;
|
perflag1 = v->per_flag;
|
||||||
|
|
@ -808,45 +790,77 @@ static int calc_wanted_job_total_resvals_(xvec_idval_t* resvals, var resmgr,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int calc_wanted_global_resvals(xvec_idval_t* resvals, var resmgr,
|
|
||||||
xlist_t* rusages, rusage_per_type_t perflag,
|
|
||||||
int nhosts, int nslots)
|
|
||||||
{
|
|
||||||
return calc_wanted_job_total_resvals_(resvals, resmgr, rusages, perflag,
|
|
||||||
nhosts, nslots, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* calc_wanted_host_resval_job_totals - bug-1790: like
|
* calc_wanted_job_total_resval - bug-1790, limit-plugin-ownership refactor:
|
||||||
* calc_wanted_global_resvals(), but for resources NOT flagged
|
* looks up a SINGLE resource by name in `rusages` and returns its JOB-WIDE
|
||||||
* XCE_RESF_GLOBAL (per-host resources) -- computes each such resource's
|
* TOTAL want (PER_TASK: value*nslots, PER_JOB: value, PER_HOST:
|
||||||
* JOB-WIDE TOTAL want, not any one host's own share (calc_wanted_host_
|
* value*nhosts -- nslots/nhosts are the whole job's totals, not any one
|
||||||
* resvals(), below, computes that instead, for the real per-host capacity
|
* host's share) via *out. Returns 1 if `resname` was found (*out set), 0
|
||||||
* check). Exists so a GLOBAL limit built on top of a per-host resource
|
* if not (*out left untouched).
|
||||||
* (e.g. a future absolute global mem cap, "slots" already being the one
|
|
||||||
* bug-1789 fixed) can read a real aggregate instead of an always-missing
|
|
||||||
* value.
|
|
||||||
*
|
*
|
||||||
* seqjob_prepare_allocation()/parjob_prepare_allocation()'s global-phase
|
* Same job-wide-total formula as calc_wanted_global_resvals() above, but
|
||||||
* branch (ctx->worker == NULL) calls this into ctx->alloc_req.host
|
* targeted at ONE resource by name instead of iterating every rusage entry
|
||||||
* immediately alongside its existing calc_wanted_global_resvals() call.
|
* into a destination vector -- for sched_fill_limit_requests() (resource.c)
|
||||||
* This is safe despite .host normally being per-host/per-bucket-varying
|
* to call on demand, only for the specific per-host resources a matched
|
||||||
* data (see parjob_allocator.c's own per-bucket reset): plugin_prepare_
|
* GLOBAL limit actually references (e.g. a future absolute global mem cap,
|
||||||
* allocation() (plugin.c) only resets .host at the START of a PER-HOST
|
* "slots" being the one bug-1789 fixed). This replaces an earlier version
|
||||||
* call (ctx->worker != NULL), never during the global phase, so the value
|
* of this fix that instead had seqjob_prepare_allocation()/parjob_
|
||||||
* written here survives long enough for sched_fill_limit_requests()
|
* prepare_allocation() unconditionally pre-compute totals for every
|
||||||
* (resource.c) to read it -- called later in the SAME global-phase
|
* per-host resource in the job's rusages "just in case some limit wants
|
||||||
* invocation, for obj->glimits -- before any later per-host call wipes
|
* one" -- the wrong owner: those allocator plugins should only ever
|
||||||
* and correctly repopulates .host with that specific host's own share via
|
* compute what the JOB itself wants for capacity-satisfy purposes, not
|
||||||
* calc_wanted_host_resvals().
|
* what some unrelated limit might need to see. No resmgr/key_find needed
|
||||||
|
* here (unlike the two functions above) since the caller already has the
|
||||||
|
* limit's own resolved xce_resource_t and just needs the matching rusage
|
||||||
|
* entry's value/per_flag by name.
|
||||||
*/
|
*/
|
||||||
int calc_wanted_host_resval_job_totals(xvec_idval_t* resvals, var resmgr,
|
int calc_wanted_job_total_resval(xlist_t* rusages, rusage_per_type_t perflag,
|
||||||
xlist_t* rusages,
|
int nhosts, int nslots, const char* resname,
|
||||||
rusage_per_type_t perflag, int nhosts,
|
float* out)
|
||||||
int nslots)
|
|
||||||
{
|
{
|
||||||
return calc_wanted_job_total_resvals_(resvals, resmgr, rusages, perflag,
|
int i;
|
||||||
nhosts, nslots, 0);
|
|
||||||
|
if ((rusages == NULL) || (resname == NULL) || (out == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < rusages->count; i++) {
|
||||||
|
rusage_value_t* v = (rusage_value_t*)xlist_get(rusages, i);
|
||||||
|
rusage_per_type_t perflag1;
|
||||||
|
|
||||||
|
if ((v == NULL) || (v->res == NULL)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(v->res, resname) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
perflag1 = v->per_flag;
|
||||||
|
if (perflag1 == PER_DEFAULT) {
|
||||||
|
perflag1 = perflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (perflag1) {
|
||||||
|
case PER_TASK:
|
||||||
|
*out = v->value * nslots;
|
||||||
|
break;
|
||||||
|
case PER_JOB:
|
||||||
|
*out = v->value;
|
||||||
|
break;
|
||||||
|
case PER_HOST:
|
||||||
|
*out = v->value * nhosts;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* something wrong */
|
||||||
|
*out = 0.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int calc_wanted_host_resvals(xvec_idval_t* resvals, var resmgr,
|
int calc_wanted_host_resvals(xvec_idval_t* resvals, var resmgr,
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,7 @@ extern int sched_try_alloc_on_host_(xce_sched_worker_t*,
|
||||||
extern int get_resvals(zarray_t*, int, int*, float*);
|
extern int get_resvals(zarray_t*, int, int*, float*);
|
||||||
extern int put_resvals(zarray_t*, int, int*, float*);
|
extern int put_resvals(zarray_t*, int, int*, float*);
|
||||||
extern request_res_t* make_resource_request(char*, float);
|
extern request_res_t* make_resource_request(char*, float);
|
||||||
extern int sched_fill_limit_requests(xce_sched_alloc_req_t*, xlist_t*);
|
extern int sched_fill_limit_requests(xce_sched_alloc_req_t*, xlist_t*,
|
||||||
|
xlist_t*, int, int);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -640,11 +640,21 @@ int plugin_prepare_allocation(void* arg, xce_sched_ctx_t* ctx)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fill the extra requests based on limit(global/host) */
|
/* fill the extra requests based on limit(global/host). rusages is only
|
||||||
|
* meaningful for a PRIMARY resreq (obj->u.primary) -- COMPOUND/ALTER
|
||||||
|
* jobs pass NULL, same as this function's existing, pre-established
|
||||||
|
* "job->allocs/accounts are out of scope" convention for those tags
|
||||||
|
* elsewhere in this codebase (core_events.c); sched_fill_limit_requests()
|
||||||
|
* only ever dereferences rusages for the one limit shape (a GLOBAL limit
|
||||||
|
* on a per-host resource) that needs it, and is NULL-safe otherwise. */
|
||||||
if (ctx->worker == NULL) {
|
if (ctx->worker == NULL) {
|
||||||
/* sequence/parallel job plugin should fill the basic rusages based on
|
/* sequence/parallel job plugin should fill the basic rusages based on
|
||||||
* resreq */
|
* resreq */
|
||||||
sched_fill_limit_requests(&(ctx->alloc_req), &(obj->glimits));
|
sched_fill_limit_requests(
|
||||||
|
&(ctx->alloc_req), &(obj->glimits),
|
||||||
|
(obj->tag == XCE_RESREQ_PRIMARY) ? &(obj->u.primary->resreq->rusages)
|
||||||
|
: NULL,
|
||||||
|
ctx->nhosts, ctx->nslots);
|
||||||
} else {
|
} else {
|
||||||
xlist_t* hlimits = NULL;
|
xlist_t* hlimits = NULL;
|
||||||
|
|
||||||
|
|
@ -652,7 +662,12 @@ int plugin_prepare_allocation(void* arg, xce_sched_ctx_t* ctx)
|
||||||
* resreq */
|
* resreq */
|
||||||
hlimits = (xlist_t*)zarray_get(&(obj->hlimits), ctx->worker->idx);
|
hlimits = (xlist_t*)zarray_get(&(obj->hlimits), ctx->worker->idx);
|
||||||
if (hlimits != NULL) {
|
if (hlimits != NULL) {
|
||||||
sched_fill_limit_requests(&(ctx->alloc_req), hlimits);
|
sched_fill_limit_requests(
|
||||||
|
&(ctx->alloc_req), hlimits,
|
||||||
|
(obj->tag == XCE_RESREQ_PRIMARY)
|
||||||
|
? &(obj->u.primary->resreq->rusages)
|
||||||
|
: NULL,
|
||||||
|
ctx->nhosts, ctx->nslots);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,15 +116,6 @@ static int parjob_prepare_allocation(void* arg, xce_sched_ctx_t* ctx)
|
||||||
calc_wanted_global_resvals(&(ctx->alloc_req.shared), sched->core.resmgr,
|
calc_wanted_global_resvals(&(ctx->alloc_req.shared), sched->core.resmgr,
|
||||||
&(primary->rusages), PER_TASK, ctx->nhosts,
|
&(primary->rusages), PER_TASK, ctx->nhosts,
|
||||||
ctx->nslots);
|
ctx->nslots);
|
||||||
/* bug-1790: also compute this job's job-wide total want for any
|
|
||||||
* per-host resource in its own rusages (e.g. a future global limit
|
|
||||||
* built on top of "mem") -- see calc_wanted_host_resval_job_totals()'s
|
|
||||||
* own comment (resreq.c) for why writing into .host here, during the
|
|
||||||
* global phase, is safe. */
|
|
||||||
calc_wanted_host_resval_job_totals(&(ctx->alloc_req.host),
|
|
||||||
sched->core.resmgr,
|
|
||||||
&(primary->rusages), PER_TASK,
|
|
||||||
ctx->nhosts, ctx->nslots);
|
|
||||||
} else {
|
} else {
|
||||||
calc_wanted_host_resvals(&(ctx->alloc_req.host), sched->core.resmgr,
|
calc_wanted_host_resvals(&(ctx->alloc_req.host), sched->core.resmgr,
|
||||||
&(primary->rusages), PER_TASK, ctx->nhosts,
|
&(primary->rusages), PER_TASK, ctx->nhosts,
|
||||||
|
|
|
||||||
|
|
@ -84,15 +84,6 @@ static int seqjob_prepare_allocation(void* arg, xce_sched_ctx_t* ctx)
|
||||||
ctx->nslots = 1;
|
ctx->nslots = 1;
|
||||||
calc_wanted_global_resvals(&(ctx->alloc_req.shared), sched->core.resmgr,
|
calc_wanted_global_resvals(&(ctx->alloc_req.shared), sched->core.resmgr,
|
||||||
&(primary->rusages), PER_TASK, 1, 1);
|
&(primary->rusages), PER_TASK, 1, 1);
|
||||||
/* bug-1790: also compute this job's job-wide total want for any
|
|
||||||
* per-host resource in its own rusages (e.g. a future global limit
|
|
||||||
* built on top of "mem") -- see calc_wanted_host_resval_job_totals()'s
|
|
||||||
* own comment (resreq.c) for why writing into .host here, during the
|
|
||||||
* global phase, is safe. */
|
|
||||||
calc_wanted_host_resval_job_totals(&(ctx->alloc_req.host),
|
|
||||||
sched->core.resmgr,
|
|
||||||
&(primary->rusages), PER_TASK, 1,
|
|
||||||
1);
|
|
||||||
} else {
|
} else {
|
||||||
calc_wanted_host_resvals(&(ctx->alloc_req.host), sched->core.resmgr,
|
calc_wanted_host_resvals(&(ctx->alloc_req.host), sched->core.resmgr,
|
||||||
&(primary->rusages), PER_TASK, 1, 1);
|
&(primary->rusages), PER_TASK, 1, 1);
|
||||||
|
|
|
||||||
|
|
@ -909,51 +909,72 @@ static float get_resval_by_id(xvec_idval_t* vec, int idx)
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sched_fill_limit_requests - for each matched limit, compute what THIS
|
||||||
|
* job would consume against it and store it into the limit's own virtual
|
||||||
|
* resource entry, ready for the generic satisfy-check machinery to compare
|
||||||
|
* against the limit's cap like any other resource.
|
||||||
|
*
|
||||||
|
* A limit's own scope (global vs host -- which of alloc_req->shared/.host
|
||||||
|
* its OWN virtual resource entry, vres->vres, gets written into) is
|
||||||
|
* independent of its UNDERLYING resource's (vres->res) own nature: "slots"
|
||||||
|
* is a per-host resource (not XCE_RESF_GLOBAL) even when the limit built on
|
||||||
|
* top of it is global (e.g. limits.yaml's ug1_normal_slots, summing slots
|
||||||
|
* across the whole cluster) -- bug-1789/1790. Both are derivable from each
|
||||||
|
* resource's own XCE_RESF_GLOBAL flag: `vres->res`'s flag is the SAME one
|
||||||
|
* calc_wanted_global_resvals()/calc_wanted_host_resvals() (resreq.c)
|
||||||
|
* dispatch on to decide where THEY write a resource's want; `vres->vres`'s
|
||||||
|
* flag was set by instantiate_limit()->add_vres() from the exact same
|
||||||
|
* is_host_based_limit() call that decided whether this limit landed in
|
||||||
|
* obj->glimits or obj->hlimits in the first place.
|
||||||
|
*
|
||||||
|
* Three of the four (scope, underlying-resource) combinations read the
|
||||||
|
* underlying resource's want straight out of alloc_req, already populated
|
||||||
|
* by whichever allocator plugin ran (calc_wanted_global_resvals()/
|
||||||
|
* calc_wanted_host_resvals()). The fourth -- a GLOBAL limit on a per-host
|
||||||
|
* underlying resource -- is different in kind, not just in which vector to
|
||||||
|
* read: alloc_req->host only ever holds per-host/per-bucket-specific data
|
||||||
|
* for the REAL per-host capacity check (a different question, with a
|
||||||
|
* different answer, computed fresh per candidate host), never a job-WIDE
|
||||||
|
* total. For that one combination, `rusages`/`nhosts`/`nslots` (the job's
|
||||||
|
* own declared resource requests, and its whole-job totals -- not any one
|
||||||
|
* host's share) are used to compute the aggregate directly, on demand, via
|
||||||
|
* calc_wanted_job_total_resval() (resreq.c) -- deliberately kept as this
|
||||||
|
* function's own responsibility rather than the allocator plugins': what a
|
||||||
|
* LIMIT needs to see is this function's concern, not seqjob/parjob's, which
|
||||||
|
* should only ever compute what the job itself wants for capacity-satisfy
|
||||||
|
* purposes.
|
||||||
|
*/
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < limits->count; i++) {
|
for (i = 0; i < limits->count; i++) {
|
||||||
xce_vres_t* vres = (xce_vres_t*)xlist_get(limits, i);
|
xce_vres_t* vres = (xce_vres_t*)xlist_get(limits, i);
|
||||||
xvec_idval_t* src;
|
xvec_idval_t* dst;
|
||||||
xvec_idval_t* dst;
|
int src_is_global;
|
||||||
float val = 0.0;
|
int dst_is_global;
|
||||||
|
float val = 0.0;
|
||||||
|
|
||||||
if (vres == NULL) {
|
if (vres == NULL) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* bug-1789: a limit's own scope (global vs host -- which of
|
src_is_global = (vres->res->flags & XCE_RESF_GLOBAL) != 0;
|
||||||
* alloc_req->shared/.host its OWN virtual resource entry,
|
dst_is_global = (vres->vres->flags & XCE_RESF_GLOBAL) != 0;
|
||||||
* vres->vres, gets written into) is independent of its
|
dst = dst_is_global ? &(alloc_req->shared) : &(alloc_req->host);
|
||||||
* UNDERLYING resource's (vres->res) own nature. "slots" is a
|
|
||||||
* per-host resource (not XCE_RESF_GLOBAL) even when the limit
|
|
||||||
* built on top of it is global (e.g. limits.yaml's
|
|
||||||
* ug1_normal_slots, summing slots across the whole cluster) --
|
|
||||||
* reading its want from `dst` itself (as this used to, via a
|
|
||||||
* caller-supplied `vec` equal to `dst`) silently missed every
|
|
||||||
* time for such a limit, since seqjob_prepare_allocation()/
|
|
||||||
* parjob_prepare_allocation() only ever stage a per-host
|
|
||||||
* resource's want into alloc_req->host, never alloc_req->shared,
|
|
||||||
* regardless of which phase is running.
|
|
||||||
*
|
|
||||||
* Both `src` (where the underlying resource's want lives) and
|
|
||||||
* `dst` (where THIS limit's own vres entry belongs) are fully
|
|
||||||
* derivable from each resource's own XCE_RESF_GLOBAL flag --
|
|
||||||
* `vres->res`'s flag is the SAME one calc_wanted_global_resvals()/
|
|
||||||
* calc_wanted_host_resvals() (resreq.c) dispatch on to decide
|
|
||||||
* where THEY write a resource's want; `vres->vres`'s flag was set
|
|
||||||
* by instantiate_limit()->add_vres() from the exact same
|
|
||||||
* is_host_based_limit() call that decided whether this limit
|
|
||||||
* landed in obj->glimits or obj->hlimits in the first place -- so
|
|
||||||
* no caller-supplied vec is needed at all. */
|
|
||||||
src = ((vres->res->flags & XCE_RESF_GLOBAL) != 0) ? &(alloc_req->shared)
|
|
||||||
: &(alloc_req->host);
|
|
||||||
dst = ((vres->vres->flags & XCE_RESF_GLOBAL) != 0) ? &(alloc_req->shared)
|
|
||||||
: &(alloc_req->host);
|
|
||||||
|
|
||||||
val = get_resval_by_id(src, vres->res->idx);
|
if (dst_is_global && !src_is_global) {
|
||||||
|
calc_wanted_job_total_resval(rusages, PER_TASK, nhosts, nslots,
|
||||||
|
vres->res->name, &val);
|
||||||
|
} else {
|
||||||
|
xvec_idval_t* src
|
||||||
|
= src_is_global ? &(alloc_req->shared) : &(alloc_req->host);
|
||||||
|
|
||||||
|
val = get_resval_by_id(src, vres->res->idx);
|
||||||
|
}
|
||||||
|
|
||||||
xvec_idval_store(dst, vres->vres->idx, val);
|
xvec_idval_store(dst, vres->vres->idx, val);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,10 +241,14 @@ int sched_publish_pend_reason(xce_scheduler_t* sched, xce_preason_set_t* r)
|
||||||
* invoked here (this test never calls plugin_prepare_allocation()). See
|
* invoked here (this test never calls plugin_prepare_allocation()). See
|
||||||
* file header comment. */
|
* file header comment. */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1134,93 +1134,6 @@ static void test_shared_resource_debited_once_regardless_of_host_count(void)
|
||||||
sched_ctx_fini(&ctx);
|
sched_ctx_fini(&ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* bug-1790: same regression as test_seqjob_allocator.c's own sibling test,
|
|
||||||
* for parjob's prepare_allocation hook -- a GLOBAL limit built on a
|
|
||||||
* per-host resource OTHER than "slots" (e.g. a future global mem cap)
|
|
||||||
* needs ctx->alloc_req.host to carry that resource's JOB-WIDE TOTAL want
|
|
||||||
* during the global phase (ctx->worker == NULL) so sched_fill_limit_
|
|
||||||
* requests() (resource.c) can read it. Uses maxTasks=3 (nslots=3 during
|
|
||||||
* the global phase) specifically to prove the PER_TASK formula uses the
|
|
||||||
* job's WHOLE nslots, not any one bucket's share (parjob_try_plan_() only
|
|
||||||
* assigns a per-bucket nslots later, in the per-host phase -- unaffected
|
|
||||||
* by this test, called directly with ctx.worker == NULL).
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
test_parjob_prepare_allocation_global_phase_stores_host_resource_job_total(
|
|
||||||
void)
|
|
||||||
{
|
|
||||||
xce_scheduler_t sched;
|
|
||||||
xce_resource_t* slots_res;
|
|
||||||
xce_resource_t* mem_res;
|
|
||||||
xce_resource_t mem_res_in = { 0 };
|
|
||||||
xce_sched_obj_t obj;
|
|
||||||
xce_primary_schedobj_t primary_obj;
|
|
||||||
xce_primary_resreq_t primary;
|
|
||||||
xce_sched_ctx_t ctx;
|
|
||||||
xce_sched_plugin_t plugin;
|
|
||||||
rusage_value_t rv;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
memset(&sched, 0, sizeof(sched));
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
||||||
|
|
||||||
slots_res = register_slots_resource(sched.core.resmgr);
|
|
||||||
TEST_ASSERT_NOT_NULL(slots_res);
|
|
||||||
|
|
||||||
/* "mem" -- per-host resource (NOT XCE_RESF_GLOBAL), the shape a future
|
|
||||||
* GLOBAL limit on top of it (bug-1790) would need populated. */
|
|
||||||
mem_res_in.name = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
||||||
mem_res = makeResource(sched.core.resmgr, &mem_res_in);
|
|
||||||
TEST_ASSERT_NOT_NULL(mem_res);
|
|
||||||
|
|
||||||
memset(&primary, 0, sizeof(primary));
|
|
||||||
primary.minTasks = 1;
|
|
||||||
primary.maxTasks = 3; /* parjob's job, not seqjob's -- see nslots note above */
|
|
||||||
primary.select = NULL;
|
|
||||||
|
|
||||||
rc = xlist_init(&(primary.rusages), 4);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
||||||
|
|
||||||
memset(&rv, 0, sizeof(rv));
|
|
||||||
rv.res = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
||||||
rv.value = 2.0f;
|
|
||||||
rv.per_flag = PER_TASK;
|
|
||||||
rc = xlist_store(&(primary.rusages), &rv);
|
|
||||||
TEST_ASSERT_TRUE(rc >= 0);
|
|
||||||
|
|
||||||
init_primary_obj(&obj, &primary_obj, &primary);
|
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &sched, &obj));
|
|
||||||
|
|
||||||
memset(&plugin, 0, sizeof(plugin));
|
|
||||||
plugin.sched = &sched;
|
|
||||||
|
|
||||||
ctx.worker = NULL;
|
|
||||||
rc = parjob_prepare_allocation(&plugin, &ctx);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
||||||
|
|
||||||
/* "mem" is not XCE_RESF_GLOBAL, so it's correctly excluded from
|
|
||||||
* .shared -- only a genuinely global resource would land there. */
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, ctx.alloc_req.shared.count);
|
|
||||||
|
|
||||||
/* THE regression assertion: .host now carries "mem"'s job-wide total
|
|
||||||
* (PER_TASK: value * nslots == 2.0 * 3, the job's WHOLE maxTasks, not
|
|
||||||
* any one bucket's share) alongside the pre-existing unconditional
|
|
||||||
* "slots" entry (also job-wide here: ctx->nslots itself, per the
|
|
||||||
* existing xvec_idval_store() call below in this same branch). */
|
|
||||||
TEST_ASSERT_EQUAL_INT(2, ctx.alloc_req.host.count);
|
|
||||||
TEST_ASSERT_EQUAL_INT(mem_res->idx, ((int*)ctx.alloc_req.host.idxs.data)[0]);
|
|
||||||
TEST_ASSERT_EQUAL_FLOAT(6.0f, ((float*)ctx.alloc_req.host.vals.data)[0]);
|
|
||||||
TEST_ASSERT_EQUAL_INT(slots_res->idx,
|
|
||||||
((int*)ctx.alloc_req.host.idxs.data)[1]);
|
|
||||||
TEST_ASSERT_EQUAL_FLOAT(3.0f, ((float*)ctx.alloc_req.host.vals.data)[1]);
|
|
||||||
|
|
||||||
xlist_fini(&(primary.rusages));
|
|
||||||
FREEUP(rv.res);
|
|
||||||
sched_ctx_fini(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUp(void) { return; }
|
void setUp(void) { return; }
|
||||||
|
|
||||||
void tearDown(void) { return; }
|
void tearDown(void) { return; }
|
||||||
|
|
@ -1240,7 +1153,5 @@ int main(void)
|
||||||
test_mid_plan_bucket_failure_rolls_back_before_later_plan_succeeds);
|
test_mid_plan_bucket_failure_rolls_back_before_later_plan_succeeds);
|
||||||
RUN_TEST(test_returns_zero_when_every_plan_is_exhausted);
|
RUN_TEST(test_returns_zero_when_every_plan_is_exhausted);
|
||||||
RUN_TEST(test_shared_resource_debited_once_regardless_of_host_count);
|
RUN_TEST(test_shared_resource_debited_once_regardless_of_host_count);
|
||||||
RUN_TEST(
|
|
||||||
test_parjob_prepare_allocation_global_phase_stores_host_resource_job_total);
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,14 @@ void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
* resolution) calls this -- stub matching test_plugin_picker_registration.c's
|
* resolution) calls this -- stub matching test_plugin_picker_registration.c's
|
||||||
* own. */
|
* own. */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,14 @@
|
||||||
* test never exercises plugin_prepare_allocation, so a no-op body is
|
* test never exercises plugin_prepare_allocation, so a no-op body is
|
||||||
* sufficient to satisfy the linker. */
|
* sufficient to satisfy the linker. */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,10 +148,14 @@ int sched_publish_pend_reason(xce_scheduler_t* sched, xce_preason_set_t* r)
|
||||||
* plugin.c's plugin_prepare_allocation() references it; never actually
|
* plugin.c's plugin_prepare_allocation() references it; never actually
|
||||||
* invoked here. */
|
* invoked here. */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,10 +165,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,10 +133,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,10 +130,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,10 +127,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,10 +92,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,10 +146,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -817,28 +817,29 @@ static void test_ctx_uncommit_host_reverses_provisional_debit(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* bug-1789: sched_fill_limit_requests() (resource.c) computes, for each
|
* bug-1789/1790, limit-plugin-ownership refactor: sched_fill_limit_requests()
|
||||||
* matched limit, how many units of the limit's own virtual resource
|
* (resource.c) computes, for each matched limit, how many units of the
|
||||||
* (vres->vres) a job wants -- by reading how many units of the limit's
|
* limit's own virtual resource (vres->vres) a job wants -- by reading how
|
||||||
* UNDERLYING resource (vres->res) the job already requested. Both the
|
* many units of the limit's UNDERLYING resource (vres->res) the job already
|
||||||
* READ source and the WRITE destination are one of alloc_req->shared/
|
* requested. The write destination is one of alloc_req->shared/.host,
|
||||||
* .host, each independently chosen by that resource's own
|
* chosen by vres->vres's own XCE_RESF_GLOBAL flag (set by instantiate_
|
||||||
* XCE_RESF_GLOBAL flag: vres->res's flag decides the read source, the
|
* limit()->add_vres() from the same is_host_based_limit() call that put
|
||||||
* SAME flag calc_wanted_global_resvals()/calc_wanted_host_resvals()
|
* this limit in obj->glimits vs obj->hlimits in the first place).
|
||||||
* (resreq.c) dispatch on to decide where THEY write it; vres->vres's own
|
*
|
||||||
* flag (set by instantiate_limit()->add_vres() from the same
|
* The read source depends on BOTH flags together: three of the four
|
||||||
* is_host_based_limit() call that put this limit in obj->glimits vs
|
* (scope, underlying-resource) combinations read straight out of alloc_req
|
||||||
* obj->hlimits in the first place) decides the write destination -- no
|
* (already populated by whichever allocator plugin ran). This test proves
|
||||||
* caller-supplied vector needed for either side. These two flags are
|
* the fourth, different-in-kind combination (bug-1789's actual reported
|
||||||
* independent: "slots" is per-host (not XCE_RESF_GLOBAL) even when the
|
* case): a GLOBAL limit (write target alloc_req->shared) on a per-host
|
||||||
* limit built on it is GLOBAL (limits.yaml's ug1_normal_slots, summing
|
* underlying resource like "slots" (limits.yaml's ug1_normal_slots,
|
||||||
* slots across the whole cluster) -- seqjob_prepare_allocation()/
|
* summing slots across the whole cluster) -- alloc_req->host never holds
|
||||||
* parjob_prepare_allocation() only ever stage a per-host resource's want
|
* a job-WIDE total for this (only per-host/per-bucket-specific data for
|
||||||
* into alloc_req->host, never alloc_req->shared, regardless of phase.
|
* the real per-host capacity check), so the want is computed directly
|
||||||
* This test proves the GLOBAL-limit/per-host-resource combination
|
* from the job's own rusages instead, via calc_wanted_job_total_resval()
|
||||||
* (bug-1789's actual reported case): the limit's own vres is
|
* (resreq.c) -- PER_TASK: value * nslots, using nslots=3 here specifically
|
||||||
* GLOBAL-flagged (write target alloc_req->shared), but its underlying
|
* to prove the job's WHOLE total is used, not any one host's/bucket's
|
||||||
* resource is not (its want must be read from alloc_req->host).
|
* share (this is the limit-evaluation layer, allocator-agnostic -- no
|
||||||
|
* seqjob/parjob-specific test needed for this any more).
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
test_fill_limit_requests_global_limit_on_host_resource_reads_host(void)
|
test_fill_limit_requests_global_limit_on_host_resource_reads_host(void)
|
||||||
|
|
@ -851,6 +852,8 @@ test_fill_limit_requests_global_limit_on_host_resource_reads_host(void)
|
||||||
xce_sched_alloc_req_t alloc_req;
|
xce_sched_alloc_req_t alloc_req;
|
||||||
xlist_t limits;
|
xlist_t limits;
|
||||||
xce_vres_t vres;
|
xce_vres_t vres;
|
||||||
|
xlist_t rusages;
|
||||||
|
rusage_value_t rv;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
memset(&sched, 0, sizeof(sched));
|
memset(&sched, 0, sizeof(sched));
|
||||||
|
|
@ -880,26 +883,34 @@ test_fill_limit_requests_global_limit_on_host_resource_reads_host(void)
|
||||||
rc = xlist_store(&limits, &vres);
|
rc = xlist_store(&limits, &vres);
|
||||||
TEST_ASSERT_TRUE(rc >= 0);
|
TEST_ASSERT_TRUE(rc >= 0);
|
||||||
|
|
||||||
|
/* the job's own declared "slots" rusage -- 2.0/task, 3 tasks total
|
||||||
|
* (nslots below), for an expected job-wide total of 6.0. */
|
||||||
|
rc = xlist_init(&rusages, 4);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
memset(&rv, 0, sizeof(rv));
|
||||||
|
rv.res = strdup("slots");
|
||||||
|
rv.value = 2.0f;
|
||||||
|
rv.per_flag = PER_TASK;
|
||||||
|
rc = xlist_store(&rusages, &rv);
|
||||||
|
TEST_ASSERT_TRUE(rc >= 0);
|
||||||
|
|
||||||
rc = xvec_idval_init(&(alloc_req.shared), 8);
|
rc = xvec_idval_init(&(alloc_req.shared), 8);
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
rc = xvec_idval_init(&(alloc_req.host), 8);
|
rc = xvec_idval_init(&(alloc_req.host), 8);
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
/* pre-staged into .host, as seqjob/parjob_prepare_allocation() always
|
|
||||||
* do for "slots" regardless of phase -- .shared (this limit's own
|
|
||||||
* write target, derived from limit_res's GLOBAL flag) starts empty. */
|
|
||||||
rc = xvec_idval_store(&(alloc_req.host), slots_res->idx, 6.0f);
|
|
||||||
TEST_ASSERT_TRUE(rc >= 0);
|
|
||||||
|
|
||||||
rc = sched_fill_limit_requests(&alloc_req, &limits);
|
rc = sched_fill_limit_requests(&alloc_req, &limits, &rusages, 1, 3);
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(1, alloc_req.shared.count);
|
TEST_ASSERT_EQUAL_INT(1, alloc_req.shared.count);
|
||||||
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.shared.idxs.data)[0]);
|
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.shared.idxs.data)[0]);
|
||||||
TEST_ASSERT_EQUAL_FLOAT(6.0f, ((float*)alloc_req.shared.vals.data)[0]);
|
TEST_ASSERT_EQUAL_FLOAT(6.0f, ((float*)alloc_req.shared.vals.data)[0]);
|
||||||
/* .host stays exactly as pre-staged -- the limit's own vres entry
|
/* .host is untouched -- the limit's own vres entry went to .shared,
|
||||||
* went to .shared, nothing new was written to .host. */
|
* and this combination never reads/writes .host at all. */
|
||||||
TEST_ASSERT_EQUAL_INT(1, alloc_req.host.count);
|
TEST_ASSERT_EQUAL_INT(0, alloc_req.host.count);
|
||||||
|
|
||||||
|
xlist_fini(&rusages);
|
||||||
|
FREEUP(rv.res);
|
||||||
xlist_fini(&limits);
|
xlist_fini(&limits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -956,7 +967,7 @@ test_fill_limit_requests_host_limit_on_host_resource_reads_host(void)
|
||||||
rc = xvec_idval_store(&(alloc_req.host), mem_res->idx, 3.0f);
|
rc = xvec_idval_store(&(alloc_req.host), mem_res->idx, 3.0f);
|
||||||
TEST_ASSERT_TRUE(rc >= 0);
|
TEST_ASSERT_TRUE(rc >= 0);
|
||||||
|
|
||||||
rc = sched_fill_limit_requests(&alloc_req, &limits);
|
rc = sched_fill_limit_requests(&alloc_req, &limits, NULL, 0, 0);
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(2, alloc_req.host.count);
|
TEST_ASSERT_EQUAL_INT(2, alloc_req.host.count);
|
||||||
|
|
@ -1022,7 +1033,7 @@ test_fill_limit_requests_host_limit_on_global_resource_reads_shared(void)
|
||||||
rc = xvec_idval_store(&(alloc_req.shared), license_res->idx, 5.0f);
|
rc = xvec_idval_store(&(alloc_req.shared), license_res->idx, 5.0f);
|
||||||
TEST_ASSERT_TRUE(rc >= 0);
|
TEST_ASSERT_TRUE(rc >= 0);
|
||||||
|
|
||||||
rc = sched_fill_limit_requests(&alloc_req, &limits);
|
rc = sched_fill_limit_requests(&alloc_req, &limits, NULL, 0, 0);
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(1, alloc_req.host.count);
|
TEST_ASSERT_EQUAL_INT(1, alloc_req.host.count);
|
||||||
|
|
|
||||||
|
|
@ -361,100 +361,11 @@ test_seqjob_prepare_allocation_guards_against_maxtasks_gt_1_fanout(void)
|
||||||
sched_ctx_fini(&ctx);
|
sched_ctx_fini(&ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* bug-1790: a GLOBAL limit built on a per-host resource OTHER than "slots"
|
|
||||||
* (e.g. a hypothetical global mem cap) needs ctx->alloc_req.host to carry
|
|
||||||
* that resource's JOB-WIDE TOTAL want during the global phase (ctx->worker
|
|
||||||
* == NULL) so sched_fill_limit_requests() (resource.c) can read it -- see
|
|
||||||
* that function's own bug-1789 comment. Before this fix, only "slots" got
|
|
||||||
* this treatment (via seqjob_prepare_allocation()'s own unconditional
|
|
||||||
* store below); any OTHER per-host resource declared in the job's own
|
|
||||||
* rusages (here "mem") was silently never populated into .host during the
|
|
||||||
* global phase, since calc_wanted_host_resvals() (the thing that DOES
|
|
||||||
* compute a per-host resource's want) only ever ran in the per-host
|
|
||||||
* branch.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
test_seqjob_prepare_allocation_global_phase_stores_host_resource_job_total(
|
|
||||||
void)
|
|
||||||
{
|
|
||||||
xce_scheduler_t sched;
|
|
||||||
xce_resource_t* slots_res;
|
|
||||||
xce_resource_t* mem_res;
|
|
||||||
xce_resource_t mem_res_in = { 0 };
|
|
||||||
xce_sched_obj_t obj;
|
|
||||||
xce_primary_schedobj_t primary_obj;
|
|
||||||
xce_primary_resreq_t primary;
|
|
||||||
xce_sched_ctx_t ctx;
|
|
||||||
xce_sched_plugin_t plugin;
|
|
||||||
rusage_value_t rv;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
memset(&sched, 0, sizeof(sched));
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
||||||
|
|
||||||
slots_res = register_slots_resource(sched.core.resmgr);
|
|
||||||
TEST_ASSERT_NOT_NULL(slots_res);
|
|
||||||
|
|
||||||
/* "mem" -- per-host resource (NOT XCE_RESF_GLOBAL), same convention
|
|
||||||
* every real host-capacity resource uses -- the exact shape a future
|
|
||||||
* GLOBAL limit on top of it (bug-1790) would need populated. */
|
|
||||||
mem_res_in.name = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
||||||
mem_res = makeResource(sched.core.resmgr, &mem_res_in);
|
|
||||||
TEST_ASSERT_NOT_NULL(mem_res);
|
|
||||||
|
|
||||||
memset(&primary, 0, sizeof(primary));
|
|
||||||
primary.minTasks = 1;
|
|
||||||
primary.maxTasks = 1;
|
|
||||||
primary.select = NULL;
|
|
||||||
|
|
||||||
rc = xlist_init(&(primary.rusages), 4);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
||||||
|
|
||||||
memset(&rv, 0, sizeof(rv));
|
|
||||||
rv.res = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
||||||
rv.value = 2.0f;
|
|
||||||
rv.per_flag = PER_TASK;
|
|
||||||
rc = xlist_store(&(primary.rusages), &rv);
|
|
||||||
TEST_ASSERT_TRUE(rc >= 0);
|
|
||||||
|
|
||||||
init_primary_obj(&obj, &primary_obj, &primary);
|
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &sched, &obj));
|
|
||||||
|
|
||||||
memset(&plugin, 0, sizeof(plugin));
|
|
||||||
plugin.sched = &sched;
|
|
||||||
|
|
||||||
ctx.worker = NULL;
|
|
||||||
rc = seqjob_prepare_allocation(&plugin, &ctx);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
||||||
|
|
||||||
/* "mem" is not XCE_RESF_GLOBAL, so it's correctly excluded from
|
|
||||||
* .shared -- only a genuinely global resource would land there. */
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, ctx.alloc_req.shared.count);
|
|
||||||
|
|
||||||
/* THE regression assertion: .host now carries "mem"'s job-wide total
|
|
||||||
* (PER_TASK: value * nslots == 2.0 * 1) alongside the pre-existing
|
|
||||||
* unconditional "slots" entry -- both present, in staging order. */
|
|
||||||
TEST_ASSERT_EQUAL_INT(2, ctx.alloc_req.host.count);
|
|
||||||
TEST_ASSERT_EQUAL_INT(mem_res->idx, ((int*)ctx.alloc_req.host.idxs.data)[0]);
|
|
||||||
TEST_ASSERT_EQUAL_FLOAT(2.0f, ((float*)ctx.alloc_req.host.vals.data)[0]);
|
|
||||||
TEST_ASSERT_EQUAL_INT(slots_res->idx,
|
|
||||||
((int*)ctx.alloc_req.host.idxs.data)[1]);
|
|
||||||
TEST_ASSERT_EQUAL_FLOAT(1.0f, ((float*)ctx.alloc_req.host.vals.data)[1]);
|
|
||||||
|
|
||||||
xlist_fini(&(primary.rusages));
|
|
||||||
FREEUP(rv.res);
|
|
||||||
sched_ctx_fini(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
RUN_TEST(
|
RUN_TEST(
|
||||||
test_my_alloc_allocates_single_matching_host_through_shared_iterators);
|
test_my_alloc_allocates_single_matching_host_through_shared_iterators);
|
||||||
RUN_TEST(test_seqjob_prepare_allocation_guards_against_maxtasks_gt_1_fanout);
|
RUN_TEST(test_seqjob_prepare_allocation_guards_against_maxtasks_gt_1_fanout);
|
||||||
RUN_TEST(
|
|
||||||
test_seqjob_prepare_allocation_global_phase_stores_host_resource_job_total);
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,10 +124,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
}
|
}
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,14 @@ void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
* invoked (this fixture never drives real resource-matching allocation --
|
* invoked (this fixture never drives real resource-matching allocation --
|
||||||
* see this file's header comment). */
|
* see this file's header comment). */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,10 +145,14 @@ void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
* linker dependency -- never invoked (this file never drives real
|
* linker dependency -- never invoked (this file never drives real
|
||||||
* allocation, Req 5 AC 17 / this file's own header comment). */
|
* allocation, Req 5 AC 17 / this file's own header comment). */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,10 +110,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
|
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,10 +181,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
* invoked (this fixture never drives real resource-matching allocation --
|
* invoked (this fixture never drives real resource-matching allocation --
|
||||||
* same rationale test_sim_equivalence.c's own copy documents). */
|
* same rationale test_sim_equivalence.c's own copy documents). */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,14 @@ int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||||
* linker dependency -- never invoked (this file never drives real
|
* linker dependency -- never invoked (this file never drives real
|
||||||
* allocation). */
|
* allocation). */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,10 +136,14 @@ void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||||
* linker dependency -- never invoked (this file never drives real
|
* linker dependency -- never invoked (this file never drives real
|
||||||
* allocation). */
|
* allocation). */
|
||||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||||
xlist_t* limits)
|
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||||
|
int nslots)
|
||||||
{
|
{
|
||||||
(void)alloc_req;
|
(void)alloc_req;
|
||||||
(void)limits;
|
(void)limits;
|
||||||
|
(void)rusages;
|
||||||
|
(void)nhosts;
|
||||||
|
(void)nslots;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue