diff --git a/.kiro/specs/scheduler-allocator-runner/research.md b/.kiro/specs/scheduler-allocator-runner/research.md index a5550fac..5aca5ab2 100644 --- a/.kiro/specs/scheduler-allocator-runner/research.md +++ b/.kiro/specs/scheduler-allocator-runner/research.md @@ -5,7 +5,7 @@ - **Discovery Scope**: Extension (light discovery). Refactor of an existing plugin seam inside `src/scheduler`; no new external dependency. - **Key Findings**: - The two `my_alloc()`s differ only in: the plan iterator they wire, parjob's `maxTasks <= 1` guard and NULL-input guard, parjob's per-bucket `alloc_req.host` reset, per-candidate `nslots/nhosts` assignment, and claim bookkeeping (parjob snapshots each claim's `host_req`; seqjob keeps a single `w` and reads the live `alloc_req.host` at commit). Everything else is duplicated text. - - `plugin.h` includes no headers and `xce_alloc_plan_t` is an anonymous typedef, so the full allocator iface cannot live in `plugin.h` without either a header cycle or a tag change. The picker iface already solves the same problem with a forward typedef in `plugin.h`. + - `xce_alloc_plan_t` is an anonymous typedef declared in `alloc_iter.h`, which itself includes `scheduler.h` → `plugin.h`, so the full allocator iface cannot live in `plugin.h` without either a header cycle or a tag change. The picker iface already solves the same problem with a forward typedef in `plugin.h`. - Three white-box tests call the static `my_alloc()` / `parjob_try_plan_()` / `parjob_rollback_claims_()` directly and stub `sched_make_allocs()` (bug-1882 OOM injection); none asserts pend-reason content. `test_parjob_combinatorial.c` passes `NULL` and a poisoned `xce_scheduler_t*` as the plan iterator's third argument. ## Research Log @@ -34,7 +34,7 @@ ### Plugin ABI and header layout - **Context**: Req 1.1–1.6, 6.1. - **Sources Consulted**: `include/plugin.h`, `include/sched/alloc_iter.h`, `include/scheduler.h`, `include/core.h`. -- **Findings**: `plugin.h` has no `#include`s; `core.h` and `scheduler.h` include it. `alloc_iter.h` includes `scheduler.h`. `picker_iface` is a forward-declared `xce_job_picker_iface_t*` pointing at static storage that the registry never frees (`plugin.c:111,149`). Plugin templates use designated initialisers and are `memcpy`'d at registration. +- **Findings**: `plugin.h` includes only `stdint.h`, `stdlib.h`, `decision.h`, `resreq.h` and `sched/types.h` (corrected 2026-09-30 during task 1.2: discovery misread a truncated listing as "no includes"); `core.h` and `scheduler.h` include it. `alloc_iter.h` includes `scheduler.h`. `picker_iface` is a forward-declared `xce_job_picker_iface_t*` pointing at static storage that the registry never frees (`plugin.c:111,149`). Plugin templates use designated initialisers and are `memcpy`'d at registration. - **Implications**: Forward typedef in `plugin.h`; full struct and runner prototype in `alloc_iter.h`; iface storage `static const` in each plugin file. ### Simulation mode diff --git a/.kiro/specs/scheduler-allocator-runner/tasks.md b/.kiro/specs/scheduler-allocator-runner/tasks.md index bb700434..71e27ebb 100644 --- a/.kiro/specs/scheduler-allocator-runner/tasks.md +++ b/.kiro/specs/scheduler-allocator-runner/tasks.md @@ -7,7 +7,7 @@ - Done when the full scheduler suite builds and passes under ASAN with no plan generator taking a scheduler object, and the poisoned-core test shows identical plan sequences for parjob - _Requirements: 6.1, 6.2_ -- [ ] 1.2 Declare the allocator plugin contract and the plugin's reference to it +- [x] 1.2 Declare the allocator plugin contract and the plugin's reference to it - Add the allocator contract (optional acceptance guard plus plan initialise / next / finish) next to the plan-iterator contract, and declare the framework allocation entry point there - Add the plugin's reference to its allocator contract, documented with the same ownership rule as the job-picker contract: static storage in the plugin file, never freed by the registry, NULL for non-allocator plugins - Done when the scheduler and its suite build and pass unchanged with the new declarations present and no plugin using them yet @@ -90,3 +90,7 @@ - Done when the suite and the audit are clean and item 4 is marked resolved - _Depends: 4.2, 5.1_ - _Requirements: 6.3, 7.1, 7.4, 7.5_ + +## Implementation Notes +- Build/test only in the dedicated dir `src/build.alloc` (`bash src/tools/xce-build.sh scheduler asan build.alloc --jobs N --incremental`; baseline 90/90). Another session uses `src/xce.build`. +- Several white-box scheduler test targets run with `detect_leaks=0` because their fixtures are not torn down (`testsuite/meson.build:1756`); a leak summary from running such a binary directly is pre-existing, not a regression. New tests that assert "no leak" (3.3) need leak detection on for their own target. diff --git a/src/scheduler/include/plugin.h b/src/scheduler/include/plugin.h index 87312b0e..d523b21a 100644 --- a/src/scheduler/include/plugin.h +++ b/src/scheduler/include/plugin.h @@ -60,6 +60,12 @@ typedef struct { * -- same idiom as cluster_t across myconf.h/persist.h/ha.h/cluster_def.h. */ typedef struct xce_job_picker_iface_s xce_job_picker_iface_t; +/* Forward declaration -- xce_sched_allocator_iface_t (the allocator plugin + * contract, scheduler-allocator-runner Req 1.2) is fully defined in + * sched/alloc_iter.h, next to the plan-iterator contract it wraps. Only + * needed here as a pointer field on xce_sched_plugin_t. */ +typedef struct xce_sched_allocator_iface_s xce_sched_allocator_iface_t; + /* xce_sched_core_t is defined in core.h -- forward-declared here (re-declared * again, harmlessly, near plugin_register_core()'s own declaration below) * so plugin_begin_session()/plugin_end_session() can be declared against it @@ -86,6 +92,12 @@ typedef struct { * job_elect_fn"). Stays NULL for plugin categories that are neither * (allocator/limit plugins never set job_elect_fn). */ xce_job_picker_iface_t* picker_iface; + /* Allocator plugin registration category (scheduler-allocator-runner + * Req 1.2 / 1.6): the plugin's allocator contract. Same ownership as + * picker_iface -- static storage owned by the plugin file (a `static + * const` object there), copied by pointer on registration and never + * freed by the registry. NULL for non-allocator plugins. */ + const xce_sched_allocator_iface_t* allocator_iface; /* scheduler-sim-live-allocation spec, task 1.2 ("Plugin Hook * Dispatchers" / "Limit Plugin Core-Safe Matching"): an honestly-typed * reference to the shared core state, set by BOTH plugin_register() diff --git a/src/scheduler/include/sched/alloc_iter.h b/src/scheduler/include/sched/alloc_iter.h index 1c75b560..b960f5b4 100644 --- a/src/scheduler/include/sched/alloc_iter.h +++ b/src/scheduler/include/sched/alloc_iter.h @@ -154,6 +154,41 @@ struct xce_host_iter_s { void* private; }; +/* + * xce_sched_allocator_iface_t - the allocator plugin contract + * (scheduler-allocator-runner Req 1.2). An allocator plugin supplies only + * its policy -- an optional acceptance guard plus a plan generator -- and + * sets job_alloc_fn = sched_allocator_run; the framework runner owns the + * rest of the allocation attempt. The typedef is forward-declared in + * plugin.h; the object is `static const` in the plugin file and + * referenced from xce_sched_plugin_t.allocator_iface. + */ +struct xce_sched_allocator_iface_s { + /* Optional. Returns 1 when this plugin allocates `primary`, 0 when it + * does not (the job stays pending with no reason and no checks run). + * NULL accepts every request. */ + int (*accepts)(const xce_primary_resreq_t* primary); + + /* Required. Same contract as xce_alloc_plan_iter_t init/next/fini + * above: plan_init returns 0 and leaves `iter` ready for plan_next, or + * nonzero with nothing to release (plan_fini is then not called); + * plan_next returns plans most-preferred first, NULL when exhausted; + * plan_fini releases the iterator's private state. */ + int (*plan_init)(xce_alloc_plan_iter_t* iter, xce_primary_resreq_t* resreq, + xce_sched_core_t* core); + xce_alloc_plan_t* (*plan_next)(xce_alloc_plan_iter_t* iter); + void (*plan_fini)(xce_alloc_plan_iter_t* iter); +}; + +/* + * sched_allocator_run - the framework allocation entry point, the + * job_alloc_fn of every allocator plugin; `plugin` is the + * xce_sched_plugin_t* whose allocator_iface drives the attempt. Returns + * > 0 when satisfied (ctx->alloc set), 0 when pending, < 0 a negative + * errno (-EINVAL for a missing plugin, ctx, core or allocator_iface). + */ +extern int sched_allocator_run(void* plugin, xce_sched_ctx_t* ctx); + /* * sched_host_iter_init/next/fini - the one shared xce_host_iter_t * implementation (host_iter.c, task 2.1, Req 3). Callers wire these onto a