fix(scheduler): clock/resval/jobid-format fixes plus REPL live-operator gaps (task 12.5)
Bugs fixed, discovered across a day of live-operator testing against a real deployed cluster: - bug-1584: clock anchors never initialized to real wall-clock values, timestamps showed system uptime instead of Unix epoch. - bug-1585: heap-use-after-free in the normal-mode driver's periodic resval update path -- core_events.c's RESVALS_FULL/_DELTA handlers do a shallow zarray_put() of the wire-decoded xce_val_t*, but driver.c's release_translated_payload() unconditionally freed the same decoded array right after, so the next periodic report for the same worker+resource triggered a UAF read via kill_sched_resval(). New regression test: test_resval_ownership.c. - bug-1587/1588/1591: raw-ns/raw-uint64 jobid/timestamp prints across the REPL/SIM family converted to the same human-readable formatting already used elsewhere (fmt_jobid(), timestamp formatting). - bug-1590: repl_info.c's XCE_VAL_NUMERIC checks corrected to Value_u_fvalue, fixing `info worker`/`info resource` falling back to 0.00 for real numeric resvals. task 12.5 (Req 5.22 amendment) -- five REPL gaps surfaced by a live operator session, each with its own bug id: - bug-1594: `continue`/`step` reaching EOF with no breakpoint hit now re-enters the REPL via xce_repl_on_replay_complete() instead of exiting the process; step/continue issued after replay is already complete re-prompt instead of silently exiting. - bug-1595: `step`/`continue` now echo each input event (not just decisions) via repl_print_event_summary(), in real event/decision order. - bug-1593: `info resource <name>|all` now reports resource definition metadata restricted to XCE_RESF_GLOBAL/XCE_RESF_SHARED resources, read from core->sharedInst, instead of aggregating host-based resources across every worker. - bug-1596: repl_split_tokens() strips one matching pair of surrounding quote characters from each token. See requirements.md's Req 5 AC 22 "Amendment (2026-08-19)" for the full rationale on each of the five task 12.5 gaps. Full scheduler suite green (41/42; the one remaining failure, test_recordstart_symlink_escape_replies_error, is the pre-existing unrelated bug-1560).
This commit is contained in:
parent
515f4ca098
commit
c9acd502c0
32 changed files with 2547 additions and 295 deletions
|
|
@ -65,6 +65,39 @@ void kill_sched_worker(void* arg) { (void)arg; }
|
|||
void kill_sched_user(void* arg) { (void)arg; }
|
||||
void kill_sched_project(void* arg) { (void)arg; }
|
||||
void kill_sched_resval(xce_val_t* val) { (void)val; }
|
||||
|
||||
/* dup_val - real reimplementation of snapshot_wire.c's function of the same
|
||||
* name (verbatim logic), needed because core_events.c's RESVALS_FULL/
|
||||
* RESVALS_DELTA handlers now call it (bug-1585 fix) and this file's
|
||||
* white-box #include of ../src/core_events.c does not pull in
|
||||
* snapshot_wire.c (same "reimplement the one small function instead of the
|
||||
* whole file" rationale this file's own sched_resolve_job_picker copy
|
||||
* already documents). This file's own kill_sched_resval is a no-op stub, so
|
||||
* the deep copy's contents are never inspected here -- only link-time
|
||||
* presence matters for this file's own tests. */
|
||||
xce_val_t* dup_val(const xce_val_t* src)
|
||||
{
|
||||
xce_val_t* v;
|
||||
|
||||
if (src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
v = (xce_val_t*)calloc(1, sizeof(*v));
|
||||
if (v == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
v->which = src->which;
|
||||
if (src->which == XCE_VAL_STRING) {
|
||||
v->u.svalue
|
||||
= (src->u.svalue != NULL) ? makeString(src->u.svalue) : NULL;
|
||||
} else {
|
||||
v->u = src->u;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
void plugin_unregister(void* arg) { (void)arg; }
|
||||
|
||||
/* Link-satisfying stubs for scheduler.c's sched_publish_dispatch()/
|
||||
|
|
@ -673,19 +706,29 @@ static void test_drain_translates_and_pushes_all_ten_op_types(void)
|
|||
r3->u.resourceupdate.resources = resources;
|
||||
enqueue(&core, r3);
|
||||
|
||||
/* 4. UPDATE_RESVALS_FULL -> RESVALS_FULL. NOTE: core_events.c's
|
||||
* RESVALS_FULL case does `zarray_put(&core->sharedInst, res->idx,
|
||||
* rv->val)` -- a SHALLOW store of the SAME `val` pointer (verified by
|
||||
* reading zarray_put_ex()'s source, src/lib/zarray.c) -- and this
|
||||
* function's cleanup then frees `full_resval` (and, via
|
||||
* free_ResourceVal->free_Value_ptr, `full_val` too), exactly mirroring
|
||||
* scheduler.c's existing sched_pend_update_killer()'s identical
|
||||
* RESVALS_FULL cleanup. This is a PRE-EXISTING production ownership
|
||||
* characteristic (the same hazard already exists in sched_update()'s
|
||||
* own live path today, untouched by this task) -- so after the drain
|
||||
* below, core->sharedInst's stored pointer for "cpu" is only safe to
|
||||
* check for non-NULL (the slot was populated), never to dereference. */
|
||||
full_val = (xce_val_t*)calloc(1, sizeof(*full_val));
|
||||
/* 4. UPDATE_RESVALS_FULL -> RESVALS_FULL. NOTE (bug-1585, FIXED): this
|
||||
* comment used to document a PRE-EXISTING production ownership hazard --
|
||||
* core_events.c's RESVALS_FULL case used to do `zarray_put(&core->
|
||||
* sharedInst, res->idx, rv->val)`, a SHALLOW store of the SAME `val`
|
||||
* pointer this function's cleanup then frees via
|
||||
* free_ResourceVal->free_Value_ptr, exactly mirroring scheduler.c's
|
||||
* sched_pend_update_killer()'s identical (also now-fixed) RESVALS_FULL
|
||||
* cleanup -- meaning core->sharedInst's stored pointer for "cpu" used to
|
||||
* be a dangling pointer the moment this drain returned, unsafe to
|
||||
* dereference (only a non-NULL check was safe). core_events.c's
|
||||
* RESVALS_FULL/RESVALS_DELTA handlers now `dup_val()` before
|
||||
* `zarray_put()`, so the core owns an independent copy and this
|
||||
* function's unconditional free of `full_val`/`delta_val` below is
|
||||
* exactly what should happen -- core->sharedInst's stored value is now
|
||||
* genuinely safe to read back and assert on (see
|
||||
* test_resval_ownership.c for the dedicated regression test proving
|
||||
* this end-to-end under a REAL, non-stubbed kill_sched_resval). `which`
|
||||
* must be set explicitly here (XCE_VAL_NUMERIC) -- dup_val() now
|
||||
* actually inspects it, and a bare calloc() leaves it 0 (==
|
||||
* XCE_VAL_STRING), which would make dup_val() misread `u.ivalue32`'s
|
||||
* bit pattern as a `char*` and crash in makeString(). */
|
||||
full_val = (xce_val_t*)calloc(1, sizeof(*full_val));
|
||||
full_val->which = XCE_VAL_NUMERIC;
|
||||
full_val->u.ivalue32 = 16;
|
||||
full_resval = (xce_resval_t*)calloc(1, sizeof(*full_resval));
|
||||
full_resval->name = strdup("cpu");
|
||||
|
|
@ -698,9 +741,10 @@ static void test_drain_translates_and_pushes_all_ten_op_types(void)
|
|||
enqueue(&core, r4);
|
||||
|
||||
/* 5. UPDATE_RESVALS -> RESVALS_DELTA: attributes to worker "hostA".
|
||||
* Same shallow-store-then-free hazard as 4 above (per-worker resvals
|
||||
* zarray instead of core->sharedInst) -- same "non-NULL only" caveat. */
|
||||
delta_val = (xce_val_t*)calloc(1, sizeof(*delta_val));
|
||||
* Same fix as 4 above (per-worker resvals zarray instead of
|
||||
* core->sharedInst) -- `which` set explicitly for the same reason. */
|
||||
delta_val = (xce_val_t*)calloc(1, sizeof(*delta_val));
|
||||
delta_val->which = XCE_VAL_NUMERIC;
|
||||
delta_val->u.ivalue32 = 4;
|
||||
delta_resval = (xce_resval_t*)calloc(1, sizeof(*delta_resval));
|
||||
delta_resval->name = strdup("cpu");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue