docs(spec): scheduler-allocator-runner and scheduler-sim-decision-trace requirements
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>
This commit is contained in:
parent
c89e4e4520
commit
b283f3b93c
6 changed files with 554 additions and 2 deletions
129
.kiro/specs/scheduler-allocator-runner/brief.md
Normal file
129
.kiro/specs/scheduler-allocator-runner/brief.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# 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`) picks
|
||||
`core->seqjob_allocator` (`maxTasks == 1`) or `core->parjob_allocator`
|
||||
and calls `plugin->job_alloc_fn(plugin, ctx)`.
|
||||
- Plan generation is already abstracted behind `xce_alloc_plan_iter_t`
|
||||
(`include/sched/alloc_iter.h`); host scan behind `xce_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.h` `init`'s third parameter is `xce_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 single `w`.
|
||||
|
||||
## Desired Outcome
|
||||
- Neither plugin defines `my_alloc()`; both register
|
||||
`.job_alloc_fn = sched_allocator_run` and supply only an
|
||||
`xce_sched_allocator_iface_t` (optional `accepts` guard + plan iterator)
|
||||
plus their existing `prepare_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.md` item 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; }`
|
||||
in `plugin.h`; `allocator_iface` pointer on `xce_sched_plugin_t` with the
|
||||
same ownership as `picker_iface`.
|
||||
- `sched_allocator_run()` = guards -> `accepts` -> `sched_alloc_prepare_shared_()`
|
||||
-> plan loop -> `sched_try_plan_()` (promoted `parjob_try_plan_`) ->
|
||||
`sched_finalize_job_alloc_()` (one `make_host_alloc` per claim ->
|
||||
`make_job_alloc` -> `allocs_shared` block).
|
||||
- Retype `alloc_iter.h` `init`'s third parameter to `xce_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) retype `plan_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;
|
||||
retype `xce_alloc_plan_iter_t.init` to `xce_sched_core_t*`; equivalence
|
||||
tests; update `issues/new_requirements.md` item 4.
|
||||
- **Out**: see Out of Boundary.
|
||||
|
||||
## Boundary Candidates
|
||||
- Plugin ABI (`plugin.h` iface + plugin template wiring)
|
||||
- Framework allocation runner (new `plugin_alloc.c` or `scheduler.c`) —
|
||||
shared-resource prep, claim loop/rollback, commit tail
|
||||
- Plan-iterator contract (`alloc_iter.h` retype; seqjob/parjob plan iters)
|
||||
|
||||
## Out of Boundary
|
||||
- Retyping `sched_make_allocs()` to `xce_sched_core_t*` and removing the
|
||||
`(xce_scheduler_t*)plugin->sched` cast (follow-up; callers outside
|
||||
allocators).
|
||||
- Collapsing `sched_make_seqjob_allocation` / `sched_make_parjob_allocation`
|
||||
dispatch in `scheduler.c` (optional, only if trivially falls out).
|
||||
- The `commit` override hook.
|
||||
- The `FIXME: no duration now` (NULL duration to `sched_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_eq` used by sim verify).
|
||||
- **Downstream**: any future allocator plugin (it would need only a plan
|
||||
iterator); `scheduler-preemption`; the follow-up `sched_make_allocs` retype.
|
||||
|
||||
## 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 (parjob `maxTasks <= 1`
|
||||
returns 0 with no fabricated reason, and before the lazy reason is
|
||||
created), bug-1768; `-ENOMEM` cleanup order of both current tails.
|
||||
- Divergences found in discovery that the design MUST resolve explicitly
|
||||
(they are not in the 2026-09-23 proposal):
|
||||
1. 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.
|
||||
2. seqjob builds its halloc directly from `ctx->alloc_req.host`; under the
|
||||
shared per-claim snapshot it gains a new `-ENOMEM` path. It must go
|
||||
through the same rollback (`sched_ctx_uncommit_host`).
|
||||
3. seqjob does not set `ctx->nslots/nhosts` in its loop. Its values come
|
||||
from `seqjob_prepare_allocation` (1,1). `sched_try_alloc_on_host_()`
|
||||
(`resource.c`) does not read them directly; check which hook does, and
|
||||
that `bucket_slots[0] == 1` for seqjob.
|
||||
4. seqjob has no `maxTasks` guard today. A `seqjob_accepts`
|
||||
(`maxTasks == 1`) changes behaviour for any direct caller with another
|
||||
value (several testsuite files reference `seqjob_allocator`). Either
|
||||
make `accepts` NULL for seqjob or audit those callers.
|
||||
5. Proposal-sketch defects to avoid: `sched_ctx_primary == NULL` typo;
|
||||
`claims[plan->nbuckets]` VLA (heap-allocate or bound by `maxTasks`).
|
||||
- Claim `host_req` snapshots 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue