myxce/src/scheduler/include/sched/alloc_iter.h
Rongsong Shen 4f951f2b52
feat(scheduler-sim-decision-trace): trace entry sequence and line budget
Task 1.4: requirement path push/pop with balanced begin/end entries
carrying the full nesting path, one entry per traced event kind, and a
line budget that stops storing at the bound and counts omitted lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:05:44 +00:00

222 lines
10 KiB
C

#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).
*
* 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