#if !defined(_SCHED_ALLOC_ITER_H_) #define _SCHED_ALLOC_ITER_H_ 1 /* * alloc_iter.h - lazy allocation-plan and host-candidate iterator * contracts (scheduler-allocation spec, Req 2 AC 1-6 / Req 3 AC 1-6). * * This header defines two reusable abstractions any allocator plugin can * adopt: * * xce_alloc_plan_iter_t - generates candidate allocation plans (a * partition of a primary resreq's maxTasks into per-host slot buckets), * one per next() call, most-preferred first. * * xce_host_iter_t - for a given plan, generates candidate hosts (one * per next() call) matching the plan's select-expression + availability * filter. * * No allocator is wired onto these interfaces yet -- see * ../../testsuite/test_alloc_plan_iter.c for a trivial, no-op reference * implementation of each proving the contract shape compiles and is * usable. */ #include #include "resreq.h" #include "scheduler.h" #include "xce/types.h" /* * xce_alloc_plan_t - a candidate allocation plan: a partition of a * primary resreq's maxTasks into per-host slot buckets, e.g. for * maxTasks=3: [3], [2,1], [1,1,1] -- NOT a uniform (nhosts, * slots_per_host) pair, which cannot express uneven splits. * * Produced by xce_alloc_plan_iter_t.next(); the returned pointer is valid * only until the following next() or fini() call on that same iterator * (it is not independently heap-owned by the caller). * * Tagged so sched/trace.h can forward-declare it without including this * header (which pulls in scheduler.h -> core.h, and core.h will carry the * trace record pointer). */ typedef struct xce_alloc_plan_s { int nbuckets; /* number of hosts this plan needs (== * len(bucket_slots)) */ int* bucket_slots; /* partition of maxTasks; bucket_slots[i] = slots * needed on the i-th distinct host claimed for * this plan */ /* host_filter: derived from primary_resreq->select, plus slot * availability -- consumed by xce_host_iter_t (Req 3), not exposed as * a separate field here. This interface does not mandate a * representation for it; each concrete plan-iterator implementation * carries whatever it needs to reconstruct the filter. * * Added by task 2.1 (host_iter.c, the real implementation): `ctx` is * exactly that carrier. xce_host_iter_t.init()'s own three declared * parameters (iter, plan, workers) cannot, on their own, reach the * select expression or the currently-staged resource want -- `workers` * is a bare RTTI var with no back-pointer to the owning * xce_scheduler_t (confirmed against sched_collect_workers()'s own * iter_make(&it, sched->core.workers) body, scheduler.c), and this * struct's other two fields (nbuckets/bucket_slots) carry only the * partition shape. `ctx` (already carrying ->sched, ->primary->select, * and ->alloc_req.host -- the resource want staged by whichever * allocator's prepare_allocation_hook ran) supplies everything * sched_meet_host_selection()/sched_check_host_satisfy() need, without * widening init()'s own parameter list. Not owned by the plan; the * caller (allocate_primary(), a later task) retains ownership for the * duration of one job's allocation attempt. */ xce_sched_ctx_t* ctx; } xce_alloc_plan_t; typedef struct xce_alloc_plan_iter_s xce_alloc_plan_iter_t; /* * xce_alloc_plan_iter_t - lazy, cancel-safe candidate-plan generator (Req * 2). One instance is owned by a single allocator's job_alloc_fn for the * lifetime of one job's allocation attempt. * * Allocation convention: stack-allocated by the caller, then initialized * IN PLACE -- init() takes the iterator's own address as its first * argument, matching this codebase's existing sched_ctx_init(&ctx, ...) * convention (schedobj.c). This is NOT a heap-allocating factory * function: * * xce_alloc_plan_iter_t iter_storage; * xce_alloc_plan_iter_t* iter = &iter_storage; * xce_alloc_plan_t* plan; * * 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 scheduling * core state. Returns 0 on success, nonzero on error. */ int (*init)(xce_alloc_plan_iter_t* iter, xce_primary_resreq_t* resreq, 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 * the owning allocator's own preference policy; the NULL * end-of-iteration signal is stable across repeated calls once * exhausted. The returned plan pointer is valid until the next * next() or fini() call. Calls pthread_testcancel() (or an * equivalent real cancellation point) at least once per invocation. */ xce_alloc_plan_t* (*next)(xce_alloc_plan_iter_t* iter); /* Release all resources held by the iterator. The interface defines * no other required cleanup step. */ void (*fini)(xce_alloc_plan_iter_t* iter); /* Private state: current position, strategy-specific data. */ void* private; }; typedef struct xce_host_iter_s xce_host_iter_t; /* * xce_host_iter_t - lazy, cancel-safe candidate-host filter for one plan * bucket (Req 3). Shares the same stack-allocate-then-init-in-place * convention as xce_alloc_plan_iter_t above. * * Scoped PER PLAN BUCKET, not per plan: a multi-bucket plan requires a * fresh xce_host_iter_t instance (a new init() call) for each bucket -- * never one instance shared across a plan's buckets. next() is a * forward-only cursor with no rewind; sharing one instance across buckets * would permanently drop a host an earlier, larger bucket tried and * rejected as too small, even when that same host exactly satisfies a * later, smaller bucket. Host exclusivity ACROSS buckets of the same plan * (is_claimed()) is the caller's responsibility, not this interface's -- * this iterator only ever answers "does this select expression + * availability filter match," with no memory of prior buckets. */ struct xce_host_iter_s { /* Initialize the iterator for a given plan and worker set. Returns 0 * on success, nonzero on error. */ int (*init)(xce_host_iter_t* iter, xce_alloc_plan_t* plan, var workers); /* Return the next candidate worker matching the plan's host filter, * or NULL when exhausted for this instance's bucket search -- * exhaustion is scoped to this one instance, not to the plan as a * whole (see the per-bucket-freshness note above: a plan with more * buckets remaining requires a fresh host_iter instance). The * returned worker pointer is valid until the next next() or fini() * call. Calls pthread_testcancel() (or an equivalent real * cancellation point) at least once per invocation. */ xce_sched_worker_t* (*next)(xce_host_iter_t* iter); /* Release all resources held by the host iterator, independent of * the owning plan iterator's lifecycle. */ void (*fini)(xce_host_iter_t* iter); /* Private state: current position, filtering state. */ 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 * stack-allocated, caller-owned xce_host_iter_t before calling init(), * following the exact same manual-vtable-assignment convention task 1.1's * own reference stubs established (see testsuite/test_alloc_plan_iter.c): * * xce_host_iter_t host_iter_storage; * xce_host_iter_t* host_iter = &host_iter_storage; * * host_iter->init = sched_host_iter_init; * host_iter->next = sched_host_iter_next; * host_iter->fini = sched_host_iter_fini; * * if (host_iter->init(host_iter, plan, sched->core.workers) == 0) { ... } * * plan->ctx (see xce_alloc_plan_t above) must be non-NULL: it supplies the * xce_scheduler_t/select-expression/resource-want state this * implementation needs but init()'s own (iter, plan, workers) parameters * cannot reach on their own. */ extern int sched_host_iter_init(xce_host_iter_t* iter, xce_alloc_plan_t* plan, var workers); extern xce_sched_worker_t* sched_host_iter_next(xce_host_iter_t* iter); extern void sched_host_iter_fini(xce_host_iter_t* iter); #endif