feat(scheduler-allocator-runner): plan generators initialise from the scheduling core (task 1.1)

xce_alloc_plan_iter_t.init takes xce_sched_core_t* instead of
xce_scheduler_t*; both built-in generators, both allocators' call sites
and the contract-test stub follow. The parjob purity test now shows an
identical plan sequence for a NULL and a poisoned core.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-30 07:44:27 +00:00
parent e2266582b2
commit 86ee3491dd
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
6 changed files with 32 additions and 32 deletions

View file

@ -86,15 +86,15 @@ typedef struct xce_alloc_plan_iter_s xce_alloc_plan_iter_t;
* xce_alloc_plan_iter_t* iter = &iter_storage;
* xce_alloc_plan_t* plan;
*
* if (iter->init(iter, primary_resreq, sched) != 0) { ... }
* if (iter->init(iter, primary_resreq, core) != 0) { ... }
* while ((plan = iter->next(iter)) != NULL) { ... }
* iter->fini(iter);
*/
struct xce_alloc_plan_iter_s {
/* Initialize the iterator for a given primary resreq and scheduler
* state. Returns 0 on success, nonzero on error. */
/* Initialize the iterator for a given primary resreq and scheduling
* core state. Returns 0 on success, nonzero on error. */
int (*init)(xce_alloc_plan_iter_t* iter, xce_primary_resreq_t* resreq,
xce_scheduler_t* sched);
xce_sched_core_t* core);
/* Return the next candidate plan, or NULL when exhausted. Successive
* calls return plans ordered most-preferred to least-preferred, per

View file

@ -158,10 +158,10 @@ static int parjob_prepare_allocation(void* arg, xce_sched_ctx_t* ctx)
*
* Pure function of maxTasks alone (Req 4 AC 1's "1host x N, 2hosts x
* ceil(N/2), ..., Nhosts x 1" sequence, generalized to real partitions
* per requirements.md's "Data Structure Changes Summary"): `sched` is
* per requirements.md's "Data Structure Changes Summary"): `core` is
* accepted (fixed by the shared xce_alloc_plan_iter_t.init() signature)
* but never dereferenced -- plan SHAPE and ORDER never inspect
* sched->core.workers or any other live worker/resource state. Only
* core->workers or any other live worker/resource state. Only
* try_alloc_on_host()/host_iter.c (task 4.2's job) ever look at live
* worker state, to decide whether a generated plan succeeds.
*
@ -184,7 +184,7 @@ static int parjob_prepare_allocation(void* arg, xce_sched_ctx_t* ctx)
* documented "valid until the next next()/fini() call" contract.
*
* plan.ctx is left NULL here, exactly like seqjob_plan_iter_init()'s own
* convention (seqjob_allocator.c): init()'s fixed (iter, resreq, sched)
* convention (seqjob_allocator.c): init()'s fixed (iter, resreq, core)
* signature has no ctx parameter to thread it through. Setting
* plan->ctx = ctx on every plan returned from next(), before
* constructing a host_iter from it, is the CALLER's responsibility
@ -201,15 +201,15 @@ typedef struct {
static int parjob_plan_iter_init(xce_alloc_plan_iter_t* iter,
xce_primary_resreq_t* resreq,
xce_scheduler_t* sched)
xce_sched_core_t* core)
{
parjob_plan_iter_priv_t* priv;
int max_tasks;
/* Req 4 AC 1 / "pure function of the task count alone": sched is part
/* Req 4 AC 1 / "pure function of the task count alone": core is part
* of the shared init() signature but never inspected -- plan
* generation must not touch live worker/resource state. */
(void)sched;
(void)core;
if ((iter == NULL) || (resreq == NULL)) {
return -EINVAL;
@ -651,7 +651,7 @@ static int my_alloc(xce_sched_plugin_t* plugin, xce_sched_ctx_t* ctx)
plan_iter->next = parjob_plan_iter_next;
plan_iter->fini = parjob_plan_iter_fini;
if (plan_iter->init(plan_iter, primary, sched) != 0) {
if (plan_iter->init(plan_iter, primary, core) != 0) {
/* infra error (e.g. OOM) -- treated the same as "no candidates,"
* not a resource-availability determination (alloc_iter.h). */
return 0;

View file

@ -141,15 +141,15 @@ typedef struct {
static int seqjob_plan_iter_init(xce_alloc_plan_iter_t* iter,
xce_primary_resreq_t* resreq,
xce_scheduler_t* sched)
xce_sched_core_t* core)
{
seqjob_plan_iter_priv_t* priv;
/* Trivial plan: fixed shape (1 host x 1 slot), independent of the
* resreq/scheduler state -- both parameters exist only to satisfy Req
* resreq/core state -- both parameters exist only to satisfy Req
* 2's shared init() signature. */
(void)resreq;
(void)sched;
(void)core;
if (iter == NULL) {
return -EINVAL;
@ -293,7 +293,7 @@ static int my_alloc(xce_sched_plugin_t* plugin, xce_sched_ctx_t* ctx)
plan_iter->next = seqjob_plan_iter_next;
plan_iter->fini = seqjob_plan_iter_fini;
if (plan_iter->init(plan_iter, ctx->primary, sched) != 0) {
if (plan_iter->init(plan_iter, ctx->primary, core) != 0) {
/* An infrastructure error (e.g. OOM) is not a resource-
* availability determination -- treat it the same as "no
* candidates", matching alloc_iter.h's own error-propagation

View file

@ -21,7 +21,7 @@
* is usable, not just that a struct literal type-checks.
*
* Both stub implementations ignore the real xce_primary_resreq_t/
* xce_scheduler_t/plan/workers arguments (ok, per the header's own
* xce_sched_core_t/plan/workers arguments (ok, per the header's own
* "each concrete plan-iterator implementation carries whatever it needs"
* note) and always yield exactly one candidate before signaling stable
* exhaustion.
@ -45,15 +45,15 @@ typedef struct {
static int stub_plan_iter_init(xce_alloc_plan_iter_t* iter,
xce_primary_resreq_t* resreq,
xce_scheduler_t* sched)
xce_sched_core_t* core)
{
stub_plan_priv_t* priv;
/* Stub: ignores the real resreq/sched state -- a real implementation
/* Stub: ignores the real resreq/core state -- a real implementation
* (parjob's combinatorial one, seqjob's trivial one) would derive its
* plan space from resreq->maxTasks here. */
(void)resreq;
(void)sched;
(void)core;
if (iter == NULL) {
return -EINVAL;

View file

@ -41,7 +41,7 @@
* - purity: plan shape/order is a pure function of maxTasks alone,
* never inspecting live worker/scheduler state (Req 4 AC 1's "never
* inspects live worker state" clause) -- proved by passing a NULL
* xce_scheduler_t* to init() and confirming plans still generate
* xce_sched_core_t* to init() and confirming plans still generate
* correctly
* - fini() releases the iterator's private state (Req 2 AC 5)
* - Req 2 AC 6: next() calls pthread_testcancel() (or an equivalent
@ -521,21 +521,21 @@ static void test_single_task_yields_single_plan(void)
/*
* Req 4 AC 1 ("never inspects live worker state" / "pure function of the
* task count alone"): the SAME maxTasks value must yield IDENTICAL plan
* sequences regardless of what (if anything) `sched` points to --
* including a NULL scheduler, and a garbage-but-non-NULL one whose
* fields are never touched. Since parjob_plan_iter_init()/_next() never
* dereference `sched` at all, a non-NULL "poisoned" scheduler pointer
* that would crash on any real field access is a stronger proof of
* purity than merely passing NULL.
* sequences regardless of what (if anything) `core` points to --
* including a NULL core, and a garbage-but-non-NULL one whose fields are
* never touched. Since parjob_plan_iter_init()/_next() never dereference
* `core` at all, a non-NULL "poisoned" core pointer that would crash on
* any real field access is a stronger proof of purity than merely
* passing NULL.
*/
static void test_plan_generation_ignores_scheduler_state(void)
static void test_plan_generation_ignores_core_state(void)
{
xce_primary_resreq_t resreq;
xce_alloc_plan_iter_t iter_a_storage;
xce_alloc_plan_iter_t* iter_a = &iter_a_storage;
xce_alloc_plan_iter_t iter_b_storage;
xce_alloc_plan_iter_t* iter_b = &iter_b_storage;
xce_scheduler_t* poisoned_sched = (xce_scheduler_t*)0x1;
xce_alloc_plan_iter_t* iter_b = &iter_b_storage;
xce_sched_core_t* poisoned_core = (xce_sched_core_t*)0x1;
int k;
memset(&resreq, 0, sizeof(resreq));
@ -549,7 +549,7 @@ static void test_plan_generation_ignores_scheduler_state(void)
/* A deliberately invalid, non-dereferenceable pointer -- init()/
* next() must never read through it, or this test would crash
* instead of merely failing an assertion. */
TEST_ASSERT_EQUAL_INT(0, iter_b->init(iter_b, &resreq, poisoned_sched));
TEST_ASSERT_EQUAL_INT(0, iter_b->init(iter_b, &resreq, poisoned_core));
for (k = 1; k <= 7; k++) {
xce_alloc_plan_t* plan_a = iter_a->next(iter_a);
@ -1229,7 +1229,7 @@ int main(void)
RUN_TEST(test_plans_ordered_ascending_host_count_with_balanced_partitions);
RUN_TEST(test_plans_for_evenly_divisible_task_count);
RUN_TEST(test_single_task_yields_single_plan);
RUN_TEST(test_plan_generation_ignores_scheduler_state);
RUN_TEST(test_plan_generation_ignores_core_state);
RUN_TEST(test_init_rejects_invalid_input);
RUN_TEST(test_next_has_a_real_cancellation_point);
RUN_TEST(test_multi_bucket_plan_succeeds_through_real_my_alloc);