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>