Task 6.4: a 1000-host, 64-task parallel job traced at full level stores exactly the line bound with exact omitted counts and counters; 10,000 traced decisions keep the trace store within 64 MiB without evicting the newest. The scheduler guide lists the REPL trace files and test rules and carries measured file sizes. Full all-components ASAN build passes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7.4 KiB
XCE Scheduler
Overview
C-based job scheduling engine with a plugin architecture. Determines job-to-node assignments based on resource requirements and scheduling policies. Since scheduler-core (2026-08), the engine is split into a mode-agnostic core state machine driven by two interchangeable drivers — a controller-connected normal-mode driver and a standalone .replay-file simulation driver — so scheduling decisions can be recorded live and deterministically replayed offline for debugging/tuning.
Build
/xce-build scheduler # debug + ASAN + tests; add `all` instead of `scheduler` when a downstream
# task also needs xceadmin/controller binaries coexisting in the same build dir
Architecture
-
Core (
core.c/core_events.c/core_session.c/core_clock.c) — passive, event-drivenxce_sched_core_tstate machine. Zero controller I/O (nonetio/xceapi/client/delay/idgen);core_clock.cis the one sanctioned exception (clock_gettime, virtualized two-base+speed clock formula,xce_core_now()).test_core_isolation.cmechanically enforces this forcore.c/core.h/core_events.c/core_session.c(source-text scan) pluscore_clock.ctoo on thenm(1)undefined-symbol layer (all four compiled together into the isolatedsched_core_isolation_libarchive, meson.build) —core_clock.cis deliberately excluded only from the wall-clock text scan, since it owns the one sanctionedclock_gettime()call. Picker→allocator handoff is a strictjbufcbuf capacity-1 rendezvous — this is the project's determinism invariant, not a tuning knob; never raise it without re-deriving Req 5 AC 7 in.kiro/specs/scheduler-core/requirements.md. -
Picker plugin interface (
plugin.h'sxce_job_picker_iface_t,plugins/builtin_fcfs.c) — job-selection strategies are pluggable;scheduler.c'ssched_job_picker()branches on whether a plugin still uses the legacyjob_elect_fn-only shape (plugins/fcfs.c, kept as-is for a compat-wrapper exercise, not the live default) vs the native hook interface (builtin_fcfs, the registered default). New scheduling policies → add as a picker plugin undersrc/plugins/, do not modify the core loop. -
Drivers —
driver.c(normal mode: capnp↔core event translation, decision publishing, deferred admin-request handling) andsim.c/sim_loop.c/sim_record.c/sim_verify.c(simulation mode:.replayfile loading, discrete virtual clock,--sim-verify/--sim-alt-events/--sim-output). Both drive the identical core session/decision-callback protocol — one core, two consumers. This equivalence is a DESIGN INVARIANT, not just an architectural description — see.kiro/steering/scheduler-driver-equivalence.mdfor the precise (state-level, not just decision-level) contract, known violations (bug-1611, bug-1613), and the testing discipline required of any change in this area. -
Replay/recording —
recording.c/recording_events.c/recording_session.c(live capture) andsim.c/sim_loop.c(simulation-side decode) do the actual capnp encode/decode of the.replaybinary stream (src/schema/controller.capnp);snapshot.c/snapshot_wire.c/snapshot_load.conly populate/consume the plain in-memorysched_snapshot_data_tstruct thatrecording.c'swrite_snapshot_entry()then serializes — no capnp or file I/O lives in the snapshot files themselves. Controlled viaOPCode_schedAdmin(src/controller/cluster.c) andxceadmin scheduler record start|stop|status/scheduler dump <FILE>. -
Interactive REPL (
repl.c/repl_break.c/repl_info.c/repl_params.c,--interactive) — single-step/breakpoint/state-inspection/snapshot-capture control over the simulation driver; drives the exact samesched_sim_driver_run()as batch--sim, gated only bysim->interactive, so REPL and batch runs are decision-equivalent by construction (test_repl_equivalence.c). -
Decision trace (
scheduler-sim-decision-trace, simulator REPL only) —include/sched/trace.his the public interface and theSCHED_TRACE(core, call)hook macro, which evaluatescallonly whilecore->trace_recis set; live never sets it, so hooks cost one pointer test. The sources are split to stay under the line cap:sched_trace.c: the record, its arena, the entry sequence and line budget, and requirement/plan/check entries.sched_trace_scan.c: positions, host verdicts and rows, summary tallies, rollback, commit and search end.sched_trace_capture.c: copies selection facts, cluster-wide limits, check rows and attempt reasons out of scheduler state.sched_trace_outcome.c: dispatch and pend outcomes, and reconstructed-dispatch records.sched_trace_internal.h: helpers shared between those files (sti_prefix).
None of them may use stdio, file I/O or logging, and none belongs in
sched_core_isolation_lib;test_core_isolation.cscans all five. Every white-box test that#includes a hooked source (resource, host_iter, alloc_runner, scheduler, sched_election, driver) must also#includethe four trace.cfiles, exactly once.The REPL side (sim thread, stdio allowed, declared in
repl_internal.h;repl.hholds the interactive-only gatessim_loop.ccalls):repl_trace.c: thetracecommand grammar,trace status, and the refcountednext/cursession settings with their trace file.repl_trace_emit.c: session begin/end hooks and per-decision emission to the console and trace file, then into the store.repl_trace_store.c: latest record per job, oldest-first eviction at 64 MiB (never the newest), discarded marks,info trace.repl_trace_render.c,repl_trace_render_outcome.c,repl_trace_render.h(private): the one renderer; every output path reuses it.
Every test that
#includesrepl.corsim_loop.c(rather than linking them fromscheduler_srcs) must also#includethe five REPL trace.cfiles and the four core trace.cfiles, exactly once;sched_sim_isolation_libcarries all nine, andtest_sim_isolation.cscans the REPL trace files andrepl_trace_render.h. A new REPL trace file goes into all three. Bounds under large traces are covered bytest_trace_bounds.c.
Links against libxce from src/lib/. Full design/requirements: .kiro/specs/scheduler-core/.
Conventions
- C standard: gnu17
- Format with
clang-format - New scheduling policies → add as a plugin under
src/plugins/, do not modify the core engine - Shared types defined in
src/include/xce/(libxce) - Files stay under the repo's 800-line cap — several core/sim/repl/snapshot/recording modules are deliberately split across multiple
*_events.c/*_session.c/*_wire.c-style files for this reason; keep new logic in the file matching its concern rather than growing one file past the cap. Not yet true project-wide:scheduler.c(2060 lines),sim_loop.c(1844),repl_info.c(1779),core_events.c(1488),driver.c(1117),resource.c(1106),plugins/builtin_fcfs.c(1104),repl_break.c(980) andinclude/core.h(861) (measured 2026-10-08) are pre-existing exceptions over the cap with no split or exemption note — do not use them as precedent for new files, and prefer splitting them opportunistically rather than growing them further.
See Also
- Shared library:
src/lib/CLAUDE.md - Root guidelines:
CLAUDE.md(repo root) - Architecture/requirements:
.kiro/specs/scheduler-core/design.md,.kiro/specs/scheduler-core/requirements.md