feat(scheduler-core): implement session-start signal + decision callback (task 4.4)

Adds xce_core_go()/xce_core_end_session() (new core_session.c):
xce_core_go() is non-blocking, performs begin-session state reset
(select-eval-ctx, resval resets), signals the picker thread via wbuf,
and returns -EINVAL on a reentrant call while a session is already
active (proven a true no-op, not just an early return). Draining
core->decisions and invoking the driver's on_decision callback is the
driver's own responsibility between core_go() and core_end_session()
(confirmed driver-owned, not core-owned, per the Driver <-> Core
protocol). xce_core_set_decision_cb() registers the callback + driver
context as a separate setter, keeping xce_sched_core_init()'s live
sched_init() call site untouched. Adds XCE_DECISION_PREEMPT to
decision.h (jobid/victims[]/reason) alongside the existing DISPATCH/
PEND types. Additive only — sched_go/sched_begin_session/
sched_end_session/sched_allocator/main.c remain untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dw5WnN3DXStaEaPYfACU1
This commit is contained in:
Rongsong Shen 2026-08-04 03:08:18 +00:00
parent e6c0a2448a
commit de69b6288d
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
9 changed files with 900 additions and 26 deletions

View file

@ -31,6 +31,7 @@
#include <stdint.h>
#include "dataq.h"
#include "decision.h"
#include "mylock.h"
#include "plugin.h"
#include "sched/types.h"
@ -45,6 +46,16 @@
* matter). */
typedef struct xce_sched_core_s xce_sched_core_t;
/*
* xce_core_decision_cb_t - the driver-registered decision callback (task
* 4.4, Req 5 AC 6): `int (*on_decision)(void* driver_ctx, xce_decision_t*
* decision)`. Invoked once per decision, in allocator-output order, as the
* driver drains `core->decisions` between `xce_core_go()` and
* `xce_core_end_session()` -- see xce_core_go()/xce_core_end_session()'s
* own doc comments (core_session.c) for the full protocol.
*/
typedef int (*xce_core_decision_cb_t)(void*, xce_decision_t*);
struct xce_sched_core_s {
mylock_t lock;
int stop;
@ -55,30 +66,37 @@ struct xce_sched_core_s {
* name. NULL until sched_set_default_job_pick_plugin() (schedobj.c)
* applies the "builtin_fcfs" default, or a future config-driven
* assignment sets it explicitly. */
char* job_pick_plugin;
var plugins;
void* seqjob_allocator;
void* parjob_allocator;
void* affinity_allocator;
void* gpu_allocator;
dataq_t* pend_updates[2];
var resmgr;
var ugmgr;
var qgmgr;
var pgmgr;
var hgmgr;
zarray_t sharedInst;
zarray_t sharedInUse;
var jobs;
var queues;
var workers;
var users;
var projects;
pthread_t jobpicker;
pthread_t allocator;
cbuf_t wbuf; /* action to trigger scheduling */
cbuf_t jbuf; /* job picked for allocation */
cbuf_t decisions; /* scheduler decisions */
char* job_pick_plugin;
var plugins;
void* seqjob_allocator;
void* parjob_allocator;
void* affinity_allocator;
void* gpu_allocator;
dataq_t* pend_updates[2];
var resmgr;
var ugmgr;
var qgmgr;
var pgmgr;
var hgmgr;
zarray_t sharedInst;
zarray_t sharedInUse;
var jobs;
var queues;
var workers;
var users;
var projects;
pthread_t jobpicker;
pthread_t allocator;
cbuf_t wbuf; /* action to trigger scheduling */
cbuf_t jbuf; /* job picked for allocation */
cbuf_t decisions; /* scheduler decisions */
/* Req 5 AC 6 (scheduler-core spec, task 4.4): driver-registered
* decision callback + opaque driver context, set via
* xce_core_set_decision_cb() (core_session.c). NULL until a driver
* registers one -- nothing in the still-live sched_go()/main.c path
* reads these fields today. */
void* driver_ctx;
xce_core_decision_cb_t on_decision;
_Atomic int session_active; /* 1 during scheduling session, 0 between */
_Atomic int eval_in_progress; /* 1 while eval_select loop runs */
_Atomic int staging_merge_complete; /* 1 after staging merge completes */
@ -233,4 +251,55 @@ typedef struct {
*/
extern int xce_core_push_events(xce_sched_core_t*, xce_core_event_t*, int);
/*
* xce_core_set_decision_cb - registers the driver's decision callback +
* opaque driver context (task 4.4, Req 5 AC 6). May be called at any time
* (typically once, at driver startup, before the first xce_core_go()) --
* no session-active restriction, since it only stores two pointers and
* neither is read outside the decision-drain window between
* xce_core_go() and xce_core_end_session(). Returns -EINVAL if core is
* NULL.
*
* Deliberately a separate setter rather than an xce_sched_core_init()
* parameter (design.md's eventual `xce_sched_core_init(core, decision_cb,
* driver_ctx, prng_seed)` signature, not yet adopted): the sole existing
* xce_sched_core_init() call site (scheduler.c's sched_init(), still the
* live production path) has no real driver/callback to pass yet, and this
* task must not perturb that call site's behavior. A setter keeps
* xce_sched_core_init() untouched while still letting a future driver (or
* this task's own test) register a callback on an already-initialized
* core.
*/
extern int xce_core_set_decision_cb(xce_sched_core_t*, xce_core_decision_cb_t,
void*);
/*
* xce_core_go - Req 5 AC 5 (scheduler-core spec, task 4.4). Starts a
* scheduling session: NON-BLOCKING -- performs the begin-session state
* reset (select-eval-ctx, shared resval, per-worker resval/host-ctx) then
* signals the picker thread via a single `cbuf_put(&core->wbuf, ...)` and
* returns immediately. Does NOT wait for the picker/allocator to produce
* any decisions -- draining `core->decisions` and invoking the registered
* `on_decision` callback is the driver's own responsibility, in the
* driver's own loop between this call and xce_core_end_session()
* (design.md's Normal-Mode Scheduling Round: "the drain loop in the
* driver thread interleaves cbuf_get/on_decision one at a time").
*
* Returns 0 on success; -EINVAL if core is NULL OR a session is already
* active (core->session_active != 0) -- the AC's required "distinct
* error" for the reentrancy-rejection case.
*/
extern int xce_core_go(xce_sched_core_t*);
/*
* xce_core_end_session - Req 5 AC 5 (scheduler-core spec, task 4.4). Called
* by the driver after it has drained core->decisions down to the
* end-of-session NULL marker (the marker itself terminates the driver's
* drain loop -- it is never passed to on_decision; see core_session.c).
* Performs the end-session cleanup (select-eval-ctx teardown, select-node
* GC mark/sweep) then clears core->session_active to 0, unblocking the
* next xce_core_go() call. Returns -EINVAL if core is NULL.
*/
extern int xce_core_end_session(xce_sched_core_t*);
#endif

View file

@ -9,18 +9,47 @@ typedef enum {
XCE_DECISION_NONE = 0,
XCE_DECISION_DISPATCH,
XCE_DECISION_PEND,
XCE_DECISION_NEW_SESSION
XCE_DECISION_NEW_SESSION,
XCE_DECISION_PREEMPT
} xce_decision_op_t;
/*
* xce_preempt_victim_t / xce_preempt_t - PREEMPT decision payload
* (scheduler-core spec, task 4.4, Req 5 AC 6). Scheduler-owned (not
* xce/types.h): design.md's Boundary Commitments note that the PREEMPT
* wire shape is owned by this spec while victim-*selection* semantics
* belong to the (separate, not-yet-implemented) scheduler-preemption spec
* -- so these two structs carry exactly AC 6's field list ("jobid,
* victims[] (jobid, workerid per victim), reason") and nothing more.
*
* `victims` follows the same "int count; T** items" idiom as
* xce_job_dispatch_t.allocs / xce_preason_set_t.reasons (xce/types.h) --
* an array of pointers, not a flat array, for consistency with every other
* decision-adjacent list in this codebase.
*/
typedef struct {
uint64_t jobid;
char* workerid;
} xce_preempt_victim_t;
typedef struct {
uint64_t jobid;
int nvictims;
xce_preempt_victim_t** victims;
xce_pendreason_t* reason;
} xce_preempt_t;
typedef struct {
xce_decision_op_t op;
union {
xce_job_alloc_t* alloc;
xce_pendreason_t* reason;
xce_preempt_t* preempt;
} u;
} xce_decision_t;
extern xce_decision_t* make_pend_decision(xce_pendreason_t*);
extern xce_decision_t* make_dispatch_decision(xce_job_alloc_t*);
extern xce_decision_t* make_preempt_decision(xce_preempt_t*);
#endif