scheduler-allocator-runner: brief and requirements for moving the duplicated seqjob/parjob my_alloc() body into one framework-owned allocator runner. scheduler-sim-decision-trace: brief and requirements for a per-job decision trace in the simulator REPL (converged after 9 review rounds). The decision-reasoning seed capability in scheduler-sim-diagnostics now points to this spec. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.9 KiB
Brief: scheduler-allocator-runner
Problem
Scheduler plugin authors and maintainers. seqjob_allocator.c (my_alloc,
line 213, ~215 lines) and parjob_allocator.c (my_alloc, line 545, ~220
lines) each own the entire allocation algorithm, although only plan
generation is plugin-specific. ~100+ lines are duplicated verbatim (lazy
pend-reason, sched_ctx_get_uncomit_shared, sched_check_shared_satisfy +
PREASON_NO_RESOURCE fill, add-uncommitted-shared, plan-iterator loop
skeleton, commit tail with sched_make_allocs + -ENOMEM cleanup chain).
Every bug fix (bug-1882, bug-1767, bug-1768) has to land twice, and a new
allocator plugin must copy the whole my_alloc().
Driver: issues/new_requirements.md item 4.
Current State
- Framework dispatch
sched_make_primary_alloc_()(scheduler.c:235) pickscore->seqjob_allocator(maxTasks == 1) orcore->parjob_allocatorand callsplugin->job_alloc_fn(plugin, ctx). - Plan generation is already abstracted behind
xce_alloc_plan_iter_t(include/sched/alloc_iter.h); host scan behindxce_host_iter_t. - The picker side already uses a framework-owned iface pattern
(
xce_job_picker_iface_t,plugin->picker_iface,plugin.h:88). alloc_iter.hinit's third parameter isxce_scheduler_t*, unused by both implementations ((void)sched); sim mode has no real scheduler.- parjob has
parjob_try_plan_/parjob_rollback_claims_/parjob_snapshot_host_req_claim bookkeeping; seqjob keeps a singlew.
Desired Outcome
- Neither plugin defines
my_alloc(); both register.job_alloc_fn = sched_allocator_runand supply only anxce_sched_allocator_iface_t(optionalacceptsguard + plan iterator) plus their existingprepare_allocation. - One framework function owns shared-resource prep, the bucket -> host-claim loop with rollback, and the commit tail.
- Allocation decisions are bit-for-bit identical to today for every existing test and sim-parity fixture.
new_requirements.mditem 4 closed.
Approach
Framework runner, lean (chosen 2026-09-28). The design proposal of
2026-09-23 (§5), with the optional commit hook left out because nothing
uses it yet:
xce_sched_allocator_iface_t { accepts; plan_init; plan_next; plan_fini; }inplugin.h;allocator_ifacepointer onxce_sched_plugin_twith the same ownership aspicker_iface.sched_allocator_run()= guards ->accepts->sched_alloc_prepare_shared_()-> plan loop ->sched_try_plan_()(promotedparjob_try_plan_) ->sched_finalize_job_alloc_()(onemake_host_allocper claim ->make_job_alloc->allocs_sharedblock).- Retype
alloc_iter.hinit's third parameter toxce_sched_core_t*. - Staged migration keeps the suite green: (1) extract shared helpers
used by both existing
my_alloc()s; (2) runner + iface, parjob first; (3) seqjob; (4) retypeplan_init; (5) close item 4.
Rejected: adding the commit override hook (an unused extension point that
would need its own tests); only extracting helpers (would not meet item 4's
goal that plugins no longer export my_alloc()).
Scope
- In: new allocator iface + framework runner; shared prep/try-plan/
finalize helpers; delete both
my_alloc()s and parjob's claim helpers; retypexce_alloc_plan_iter_t.inittoxce_sched_core_t*; equivalence tests; updateissues/new_requirements.mditem 4. - Out: see Out of Boundary.
Boundary Candidates
- Plugin ABI (
plugin.hiface + plugin template wiring) - Framework allocation runner (new
plugin_alloc.corscheduler.c) — shared-resource prep, claim loop/rollback, commit tail - Plan-iterator contract (
alloc_iter.hretype; seqjob/parjob plan iters)
Out of Boundary
- Retyping
sched_make_allocs()toxce_sched_core_t*and removing the(xce_scheduler_t*)plugin->schedcast (follow-up; callers outside allocators). - Collapsing
sched_make_seqjob_allocation/sched_make_parjob_allocationdispatch inscheduler.c(optional, only if trivially falls out). - The
commitoverride hook. - The
FIXME: no duration now(NULL duration tosched_make_allocs): carry it over unchanged, don't fix it here. - Any change to plan ordering, host ordering, pend-reason content, or preemption.
Upstream / Downstream
- Upstream:
scheduler-allocation(plan/host iterators, Req 4 combinatorial parjob, Req 9 no-regression);scheduler-core(plugin/picker iface pattern, sim mode);libxce-alloc-preason-equality(job_alloc_eqused by sim verify). - Downstream: any future allocator plugin (it would need only a plan
iterator);
scheduler-preemption; the follow-upsched_make_allocsretype.
Existing Spec Touchpoints
- Extends: none (scheduler-allocation is implementation-complete; its Req 9 is inherited as a constraint, not reopened).
- Adjacent:
scheduler-allocation,scheduler-core,scheduler-sim-discrete-clock(in flight, touches sim_loop — avoid overlapping files),libxce-alloc-preason-equality.
Constraints
- TDD: tests in
src/scheduler/testsuite/first; full ASAN build via/xce-build scheduler. - Behavioral equivalence is mandatory. Preserve exactly: bug-1882 (half-built
alloc never left on
ctx->alloc), bug-1767 (parjobmaxTasks <= 1returns 0 with no fabricated reason, and before the lazy reason is created), bug-1768;-ENOMEMcleanup order of both current tails. - Divergences found in discovery that the design MUST resolve explicitly
(they are not in the 2026-09-23 proposal):
- parjob does
xvec_idval_fini/init(&ctx->alloc_req.host)per bucket (parjob_allocator.c:452); seqjob never resets it. Decide whether the shared loop always resets, and prove it is a no-op for seqjob. - seqjob builds its halloc directly from
ctx->alloc_req.host; under the shared per-claim snapshot it gains a new-ENOMEMpath. It must go through the same rollback (sched_ctx_uncommit_host). - seqjob does not set
ctx->nslots/nhostsin its loop. Its values come fromseqjob_prepare_allocation(1,1).sched_try_alloc_on_host_()(resource.c) does not read them directly; check which hook does, and thatbucket_slots[0] == 1for seqjob. - seqjob has no
maxTasksguard today. Aseqjob_accepts(maxTasks == 1) changes behaviour for any direct caller with another value (several testsuite files referenceseqjob_allocator). Either makeacceptsNULL for seqjob or audit those callers. - Proposal-sketch defects to avoid:
sched_ctx_primary == NULLtypo;claims[plan->nbuckets]VLA (heap-allocate or bound bymaxTasks).
- parjob does
- Claim
host_reqsnapshots are owned by the framework: fini on every exit path (success, bucket failure, OOM). - Verify with:
test_seqjob_*,test_parjob_*,test_seqjob_rewrap_equivalence,test_maxtasks_wiring_*,test_sim_*_parity,test_sim_live_allocation,test_snapshot_load_allocator_regression;/xce-audit-invariants scheduler(driver state-equivalence).