feat(scheduler-core): implement REPL breakpoints and state inspection (task 12.2)
Adds the interactive simulation REPL's breakpoint and state-inspection commands (Req 5 AC 22): `break <event_type> [key=value ...]` / `info break` / `delete <id>|all` on event and decision types, and `info session/job/queue/worker/resource/summary`. repl.c (370 lines after task 12.1) couldn't hold this task's full surface under the project's 800-line cap, so it's split three ways (matching the snapshot.c/snapshot_wire.c/snapshot_load.c precedent): repl.c (execution control + dispatch), repl_break.c (matching/CRUD + the three new driver-facing hooks), repl_info.c (info dumps), sharing state via repl_internal.h. sim_loop.c gained 3 new bare hook calls (repl.h inline wrappers, same pattern as task 12.1's two) at the points design.md's pseudocode requires -- per decoded event before it joins the batch, per session boundary, and per drained decision -- landing sim_loop.c at exactly 800/800 lines. Two real bugs found and fixed during development (bug-1506, bug-1507): an uninitialized xstr_table left breakpoint filter fields silently NULL instead of failing loud, and repl_breakpoint_free() called a bare free() on an RTTI-refcounted makeString() string, corrupting the heap. Review found one gap: `info job` printed only a resreq presence flag instead of the actual resreq text, when format_resreq() already existed in this repo for exactly this purpose. Fixed to match. Scheduler suite: 35/35 clean (was 34/34). RED-phase evidence (2 real bugs plus 2 synthetic probes, all reverted with confirmed zero residue) recorded in tasks.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPPXeKSUXUqcqvzNXY1rzJ
This commit is contained in:
parent
02f7f48ff9
commit
2fecea3592
18 changed files with 3067 additions and 212 deletions
|
|
@ -5,36 +5,39 @@
|
|||
#include "sim.h"
|
||||
|
||||
/*
|
||||
* repl.h - interactive simulation REPL, execution-control slice (scheduler-
|
||||
* core spec, task 12.1, 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.
|
||||
* 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 two driver-facing entry points, plus two
|
||||
* `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/
|
||||
* 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 was
|
||||
* already at 796/800 lines (this project's per-file cap, CLAUDE.md) before
|
||||
* this task, leaving no room for even one bare `if (sim->interactive) { ...
|
||||
* }` block at each of the two call sites design.md's REPL integration
|
||||
* pseudocode requires (pre-replay pause, post-session pause), let alone two.
|
||||
* Moving the gate into these inline wrappers keeps sim_loop.c's own diff to
|
||||
* two single-line, brace-free call expressions -- this task's own text
|
||||
* anticipates exactly this fallback ("move the hook logic into a tiny
|
||||
* static inline helper in repl.h, or extract something unrelated out of
|
||||
* sim_loop.c"). The two real functions (`xce_repl_start`/
|
||||
* `xce_repl_post_session_check`) 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
|
||||
* 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. repl.c's own file header
|
||||
* comment documents the same boundary for its implementation.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
@ -51,20 +54,47 @@
|
|||
extern void xce_repl_start(xce_sched_sim_driver_t* sim);
|
||||
|
||||
/*
|
||||
* xce_repl_post_session_check - the per-session pause-decision point (Req 5
|
||||
* AC 22's interaction model: "blocks on stdin ... at each breakpoint hit";
|
||||
* task 12.1's own narrower scope: only a `step N` budget reaching zero, no
|
||||
* breakpoints yet). Called once per completed ReplaySession, immediately
|
||||
* after xce_core_end_session() (sim_loop.c), when sim->interactive is set.
|
||||
* Internally decides whether to pause (a `step` budget hit zero; `continue`
|
||||
* mode never pauses here in 12.1's scope) and, if so, re-enters the same
|
||||
* blocking command loop xce_repl_start() uses. Task 12.2's own seam: it
|
||||
* extends the INTERNAL pause decision (repl.c) to also check a breakpoint
|
||||
* list -- this function's signature and sim_loop.c's own call site are not
|
||||
* expected to change.
|
||||
* 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);
|
||||
|
||||
static inline void xce_repl_pre_replay_pause(xce_sched_sim_driver_t* sim)
|
||||
{
|
||||
if (sim->interactive) {
|
||||
|
|
@ -79,4 +109,28 @@ static inline void xce_repl_post_session_pause(xce_sched_sim_driver_t* 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);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue