fix(scheduler): free registry entries after their tables at core teardown; fix dup_val and the unmeet re-init leak (bug-1884, part 5)

A failed key_add() runs the table's datakiller on the rejected value, and
callers keep using it (JOBS_PUSH merges a duplicate job into the existing
one; sched_init_queues() re-adds every queue on each config apply), so the
job, queue, worker and resource datakillers must stay shallow. Part 4 made
kill_sched_job() free the whole job; that is reverted. Instead
xce_sched_core_fini() collects each table's entries, destroys the table,
then frees each entry with a full destructor (core.c). The resource table
is released the same way (a reference per resobj).

Also:
- dup_val() tested which == XCE_VAL_STRING (0, the resource value type,
  equal to Value_u_ivalue32): real string values were shallow-copied and a
  nonzero int32 went to makeString() as a pointer. It now tests
  Value_u_svalue and strdup()s, as free_Value() expects.
- The shared/host satisfy checks re-initialize the unmeet vector they are
  given; the allocators passed the ctx's live one, dropping its buffers on
  every candidate host. The callers finalize it first.
- add_vres() registers a resobj via makeResource(), like every other
  resource, instead of a bare calloc(); test_limit.c drops it with
  dereference().
- plugin_register_core() rejects a duplicate name before init_fn/key_add,
  since a failed add now finalizes (frees) the plugin and callers free it
  on failure.
- sharedInUse gets kill_sched_resval, as put_resvals() fills it.

Exit-time leak on the reproduction replay 7,469 -> 928 bytes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-29 09:28:08 +00:00
parent ebfe4794f0
commit c1cd8f917d
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
11 changed files with 216 additions and 122 deletions

View file

@ -660,11 +660,10 @@ static void test_fmt_limit_wrapper_matches_fmt_limit_units_raw_mode(void)
* private and its existing white-box test harness stubs get_resvals()/
* put_resvals() as no-ops, see test_repl_limit_table.c).
*
* add_vres() (xcelimit.c) key_add()s the limit's own new "limit=<name>"
* vres into resmgr with no datakiller, so each test frees that one entry
* by hand before tearing down the shared make_test_resmgr() fixture --
* mirroring free_test_resmgr()'s own manual-free pattern for its 8
* pre-registered resources.
* add_vres() (xcelimit.c) registers the limit's own new "limit=<name>"
* vres resobj in resmgr, and this fixture's resmgr has no datakiller, so
* each test drops that one entry by hand before tearing down the shared
* make_test_resmgr() fixture.
*/
static void free_instantiated_vres(var resmgr, xce_vres_t* vres)
{
@ -674,8 +673,7 @@ static void free_instantiated_vres(var resmgr, xce_vres_t* vres)
if (vres->vres != NULL) {
key_del(resmgr, (void*)vres->vres->name);
FREEUP(vres->vres->name);
FREEUP(vres->vres);
dereference(vres->vres); /* a resobj since bug-1884 */
}
FREEUP(vres);

View file

@ -8,6 +8,7 @@
#include "group.h"
#include "kvmgr.h"
#include "resobj.h"
#include "utils.h"
#include "xce/types.h"
#include "xcelimit.h"
@ -507,40 +508,31 @@ static const char* percent_capacity_resname(const char* resname)
return NULL;
}
/* add_vres - registers a limit's virtual resource. bug-1884: built with
* makeResource() like every other resmgr entry (a reference-counted
* resobj the manager releases), not a bare calloc(). */
int add_vres(xce_resource_t** pvres, var resmgr, char* name, int global)
{
xce_resource_t* vres = NULL;
int rc;
xce_resource_t tmpl;
vres = (xce_resource_t*)calloc(1, sizeof(*vres));
*pvres = vres;
if (vres == NULL) {
return -ENOMEM;
}
vres->name = strdup(name);
vres->idx = -1;
vres->desc = NULL;
vres->valtype = XCE_VAL_NUMERIC;
vres->ordtype = XCE_ORD_DECR;
vres->flags = XCE_RESF_LIMIT | XCE_RESF_CONSUMABLE | XCE_RESF_RELEASE;
memset(&tmpl, 0, sizeof(tmpl));
tmpl.name = name;
tmpl.idx = -1;
tmpl.valtype = XCE_VAL_NUMERIC;
tmpl.ordtype = XCE_ORD_DECR;
tmpl.flags = XCE_RESF_LIMIT | XCE_RESF_CONSUMABLE | XCE_RESF_RELEASE;
if (global) {
vres->flags |= (XCE_RESF_SHARED | XCE_RESF_GLOBAL);
tmpl.flags |= (XCE_RESF_SHARED | XCE_RESF_GLOBAL);
} else {
vres->flags |= XCE_RESF_DEFAULT_HOST;
tmpl.flags |= XCE_RESF_DEFAULT_HOST;
}
lock(resmgr, RTTI_LOCK_WRITE);
rc = key_add(resmgr, (void*)vres->name, (void*)vres);
unlock(resmgr);
if (rc < 0) {
return rc;
*pvres = makeResource(resmgr, &tmpl);
if (*pvres == NULL) {
return (errno > 0) ? -errno : -ENOMEM;
}
vres->idx = rc;
return rc;
return (*pvres)->idx;
}
int instantiate_limit(xce_vres_t** pvres, var resmgr, xce_limit_t* limit,