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:
parent
e2266582b2
commit
86ee3491dd
6 changed files with 32 additions and 32 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue