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).
157 lines
7.2 KiB
C
157 lines
7.2 KiB
C
#if !defined(_REPL_H_)
|
|
|
|
#define _REPL_H_ 1
|
|
|
|
#include "sim.h"
|
|
|
|
/*
|
|
* repl.h - interactive simulation REPL (scheduler-core spec, Req 5 AC 22).
|
|
* New file per design.md's File Structure Plan ("repl.c -- NEW (Phase 3):
|
|
* interactive REPL command loop (Req 5 AC 22)"), Phase 3, sibling to
|
|
* sim.h/sim.c/sim_loop.c. Task 12.1 built execution control (step/continue/
|
|
* quit, pre-replay pause); task 12.2 (this revision) adds breakpoints
|
|
* (event/BEGIN_ROUND/DECISION, with key=value filters) and the `info`
|
|
* state-inspection command family.
|
|
*
|
|
* This header declares repl.c's driver-facing entry points, plus `static
|
|
* inline` wrappers that fold the `sim->interactive` gate INTO the header
|
|
* itself. That inline-wrapper indirection is a deliberate, judgment-based
|
|
* deviation from this file family's usual "extern declared where consumed,
|
|
* no dedicated header" convention (sim_verify.c/sim_record.c/
|
|
* recording_session.c's entry points are all extern-declared directly
|
|
* inside sim_loop.c -- see that file's own header comment): sim_loop.c sits
|
|
* right at its 800-line cap (CLAUDE.md), leaving no room for even one bare
|
|
* `if (sim->interactive) { ... }` block at any of the five call sites
|
|
* design.md's REPL integration pseudocode requires (pre-replay pause,
|
|
* post-session pause, per-event/per-BEGIN_ROUND/per-decision breakpoint
|
|
* checks). Moving the gate into these inline wrappers keeps sim_loop.c's
|
|
* own diff to five single-line, brace-free call expressions. The real
|
|
* functions below still carry the actual logic; a `static inline` here
|
|
* costs nothing at link time (each including TU gets its own copy,
|
|
* trivially small, no ODR risk -- sim_loop.c is the only production
|
|
* consumer).
|
|
*
|
|
* Boundary (Req 5 AC 17, "Simulation isolation", same as sim.h): this header
|
|
* only depends on sim.h (itself core.h + stdbool/stdint/stdio) -- no
|
|
* scheduler.h, no client.h, no capnp types. xce_core_event_t (core.h) and
|
|
* xce_decision_t (decision.h, included transitively via core.h) are both
|
|
* already capnp-free, so the three new task 12.2 hooks below add no new
|
|
* dependency. repl.c's own file header comment documents the same boundary
|
|
* for its implementation.
|
|
*/
|
|
|
|
/*
|
|
* xce_repl_start - the pre-replay pause (Req 5 AC 22's interaction model:
|
|
* "blocks on stdin at startup"). Prints the load banner ("[sim] loaded: ...
|
|
* snapshot @ ts=...", "[sim] ready. type 'help' for commands.") then enters
|
|
* the REPL command loop, blocking on stdin until the operator issues a
|
|
* command that hands control back (`step [N]`/`continue`) or terminates the
|
|
* process (`quit`, which never returns). Called once, by
|
|
* sched_sim_driver_run() (sim_loop.c), strictly before its event loop's
|
|
* `for(;;)` begins -- see xce_repl_pre_replay_pause() below for the actual
|
|
* production call site.
|
|
*/
|
|
extern void xce_repl_start(xce_sched_sim_driver_t* sim);
|
|
|
|
/*
|
|
* xce_repl_post_session_check - the `step N` pause-decision point (Req 5 AC
|
|
* 22's interaction model: a `step` budget reaching zero). Called once per
|
|
* completed ReplaySession, immediately after xce_core_end_session()
|
|
* (sim_loop.c), when sim->interactive is set. This is a SEPARATE pause path
|
|
* from the breakpoint checks below -- it never inspects the breakpoint
|
|
* list (that would be too coarse: a breakpoint must interrupt mid-batch, at
|
|
* the exact matching event/decision, not merely once per completed
|
|
* session). See xce_repl_check_begin_round()'s own doc comment for why
|
|
* task 12.2 gave breakpoints their own, separately-triggered hooks instead
|
|
* of folding them into this function.
|
|
*/
|
|
extern void xce_repl_post_session_check(xce_sched_sim_driver_t* sim);
|
|
|
|
/*
|
|
* xce_repl_on_event / xce_repl_on_begin_round / xce_repl_on_decision (task
|
|
* 12.2, Req 5 AC 22 "Breakpoints" + "REPL integration with the simulation
|
|
* loop" pseudocode) - the three breakpoint-check hooks, one per granularity
|
|
* the pseudocode's `check_breakpoints(...)` calls require: per decoded
|
|
* ReplayEvent (before it is applied to core state), per ReplaySession
|
|
* boundary (BEGIN_ROUND, before the accumulated event batch is pushed), and
|
|
* per drained decision (DECISION, after verify/output-write, matching
|
|
* sim_loop.c's existing per-decision loop body). Each also does lightweight
|
|
* position bookkeeping (event_index/session_count/last-session decision
|
|
* tally) that `info session` reports -- folded in here rather than given
|
|
* separate hook call sites, since these are the only three places in
|
|
* sim_loop.c's loop where "one event/session/decision was just processed"
|
|
* is already known, and sim_loop.c has no line budget for a sixth call
|
|
* site. Implemented in repl.c; each checks sim->repl_state's breakpoint
|
|
* list for a match and, if found, prints a "[sim] hit breakpoint ..." line
|
|
* and re-enters the same blocking command loop xce_repl_start() uses. A
|
|
* NULL sim/ev/d, or a not-yet-allocated repl_state, is a safe no-op (same
|
|
* "never load-bearing, degrade to unattended" discipline
|
|
* repl_state_ensure()'s own doc comment establishes).
|
|
*/
|
|
extern void xce_repl_on_event(xce_sched_sim_driver_t* sim,
|
|
xce_core_event_t* ev);
|
|
extern void xce_repl_on_begin_round(xce_sched_sim_driver_t* sim,
|
|
uint64_t begin_round);
|
|
extern void xce_repl_on_decision(xce_sched_sim_driver_t* sim,
|
|
xce_decision_t* d);
|
|
|
|
/*
|
|
* xce_repl_on_replay_complete (task 12.5, Req 5 AC 22 amendment, bug-1594) -
|
|
* called once, by sched_sim_driver_run() (sim_loop.c), immediately after the
|
|
* replay loop reaches EOF or the AC 20 end-of-stream marker -- the ONE point
|
|
* in the whole REPL integration that requirements.md's original pseudocode
|
|
* let fall through to process exit with no re-entry into the command loop.
|
|
* Prints the existing "[sim] replay complete" banner, marks
|
|
* sim->repl_state->replay_complete, and re-enters repl_command_loop() so the
|
|
* operator can inspect final state; the only remaining exit paths at that
|
|
* point are an explicit `quit` or stdin EOF (both already funnel through
|
|
* repl_cmd_quit()). Implemented in repl.c.
|
|
*/
|
|
extern void xce_repl_on_replay_complete(xce_sched_sim_driver_t* sim);
|
|
|
|
static inline void xce_repl_pre_replay_pause(xce_sched_sim_driver_t* sim)
|
|
{
|
|
if (sim->interactive) {
|
|
xce_repl_start(sim);
|
|
}
|
|
}
|
|
|
|
static inline void xce_repl_post_session_pause(xce_sched_sim_driver_t* sim)
|
|
{
|
|
if (sim->interactive) {
|
|
xce_repl_post_session_check(sim);
|
|
}
|
|
}
|
|
|
|
static inline void xce_repl_check_event(xce_sched_sim_driver_t* sim,
|
|
xce_core_event_t* ev)
|
|
{
|
|
if (sim->interactive) {
|
|
xce_repl_on_event(sim, ev);
|
|
}
|
|
}
|
|
|
|
static inline void xce_repl_check_begin_round(xce_sched_sim_driver_t* sim,
|
|
uint64_t begin_round)
|
|
{
|
|
if (sim->interactive) {
|
|
xce_repl_on_begin_round(sim, begin_round);
|
|
}
|
|
}
|
|
|
|
static inline void xce_repl_check_decision(xce_sched_sim_driver_t* sim,
|
|
xce_decision_t* d)
|
|
{
|
|
if (sim->interactive) {
|
|
xce_repl_on_decision(sim, d);
|
|
}
|
|
}
|
|
|
|
static inline void xce_repl_check_replay_complete(xce_sched_sim_driver_t* sim)
|
|
{
|
|
if (sim->interactive) {
|
|
xce_repl_on_replay_complete(sim);
|
|
}
|
|
}
|
|
|
|
#endif
|