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
|
|
@ -1,7 +1,7 @@
|
|||
# Implementation Plan
|
||||
|
||||
- [ ] 1. Foundation: plan-iterator contract and allocator contract
|
||||
- [ ] 1.1 Retype the plan generator's initialisation to take the scheduling core
|
||||
- [x] 1.1 Retype the plan generator's initialisation to take the scheduling core
|
||||
- Change the plan-iterator initialisation parameter from the scheduler object to the scheduling core in the shared contract, in both built-in plan generators, in both allocators' current calls that initialise their plan generator (they pass the scheduling core instead of the scheduler object), and in the stub generator used by the plan-iterator contract tests
|
||||
- Update the parjob combinatorial tests' direct initialisation calls; turn the poisoned-scheduler case into a poisoned-core case that shows the plan sequence is identical with no core and with a poisoned core
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -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