myxce/src/scheduler/include/repl.h
Rongsong Shen c9acd502c0
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).
2026-08-19 09:48:46 +00:00

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