feat(scheduler-allocation): add lazy plan/host iterator contracts

Defines xce_alloc_plan_t (partition of a job's task count into
per-host slot buckets), xce_alloc_plan_iter_t, and xce_host_iter_t
(init/next/fini vtables, stack-allocate-then-init-in-place convention
matching sched_ctx_init) as reusable abstractions any allocator can
adopt. Proven usable by a trivial no-op reference implementation and
a fresh-per-bucket host_iter composition test, no allocator wired in
yet.

Task 1.1 (scheduler-allocation spec). Requirements 2.1, 3.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
This commit is contained in:
Rongsong Shen 2026-09-04 13:50:31 +00:00
parent d1a3e158ae
commit d5d2c1caf5
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 482 additions and 1 deletions

View file

@ -0,0 +1,140 @@
#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 <rtti.h>
#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).
*/
typedef struct {
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. */
} 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, sched) != 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. */
int (*init)(xce_alloc_plan_iter_t* iter, xce_primary_resreq_t* resreq,
xce_scheduler_t* sched);
/* 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;
};
#endif