feat(scheduler-core): define job picker plugin contract (task 1.1)
Add xce_job_picker_iface_t to plugin.h with all 13 hooks across lifecycle/state-change/session/post-allocation/optional categories, matching design.md's literal struct. Documents per-category thread assignment, next_job() cancel-safety, and begin_session() session-reset requirements. Forward-declares xce_sched_core_t (built in a later task). Requirements: 1.2, 1.5, 2.6
This commit is contained in:
parent
4153b35b22
commit
065025a3a6
4 changed files with 701 additions and 1 deletions
|
|
@ -79,10 +79,118 @@ extern int plugin_end_session(void*);
|
|||
extern int plugin_commit_decision(void*, xce_decision_t*);
|
||||
extern int plugin_prepare_allocation(void*, xce_sched_ctx_t*);
|
||||
extern int plugin_job_election(void*, xce_sched_job_t*);
|
||||
extern int plugin_job_rusage_update(void*, xce_sched_job_t*, xce_job_rusages_t*);
|
||||
extern int plugin_job_rusage_update(void*, xce_sched_job_t*,
|
||||
xce_job_rusages_t*);
|
||||
extern int register_fcfs(void*);
|
||||
extern int register_limit(void*);
|
||||
extern int register_seqjob_allocator(void*);
|
||||
extern int register_parjob_allocator(void*);
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Job picker plugin interface (xce_job_picker_iface_t)
|
||||
*
|
||||
* scheduler-core spec, Req 1 AC 2 / AC 5, Req 2 AC 6.
|
||||
*
|
||||
* Supersedes plugin_job_election_hook_t / plugin_job_election_fn_t
|
||||
* (job_elect_fn above) as the job-selection contract: the core scheduling
|
||||
* loop calls picker->next_job(picker) to get the next schedulable job (or
|
||||
* NULL) and carries zero knowledge of project/queue/job iteration or
|
||||
* ordering — all of that lives behind this interface (Req 1 AC 1).
|
||||
* job_elect_fn-only plugins keep working during the transition via a
|
||||
* compatibility wrapper (registered in plugin.c, scheduler-core task 1.2);
|
||||
* new pickers implement xce_job_picker_iface_t directly.
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/* xce_sched_core_t is defined in core.h, added by the scheduler-core spec's
|
||||
* Phase 2 core/driver extraction. Forward-declared here (same idiom as
|
||||
* cluster_t in myconf.h/persist.h/ha.h) to avoid a circular include —
|
||||
* init() below only ever holds/passes a pointer to it. */
|
||||
typedef struct xce_sched_core_s xce_sched_core_t;
|
||||
|
||||
/*
|
||||
* Hook categories and the thread each category runs in (Req 1 AC 3):
|
||||
*
|
||||
* Lifecycle hooks (MUST) — driver thread, at plugin register/teardown:
|
||||
* init One-time setup; receives a core reference for state queries.
|
||||
* Called exactly once, before any other hook.
|
||||
* fini Teardown; frees all picker-owned resources.
|
||||
*
|
||||
* State-change hooks (MUST) — driver thread; called synchronously, only
|
||||
* from inside core_push_events(), which itself is only invoked between
|
||||
* sessions (picker thread blocked on cbuf_get(&wbuf), allocator thread
|
||||
* blocked on cbuf_get(&jbuf)). No mid-session event injection:
|
||||
* on_queue_update Add/update a queue in the picker's internal
|
||||
* ordering. <- QUEUE_UPDATE event
|
||||
* on_job_push Add a newly pending job to the candidate set.
|
||||
* <- JOBS_PUSH event
|
||||
* on_job_state_change Drop the job from candidates if it left pending;
|
||||
* re-add if it returned to pending.
|
||||
* <- JOB_STATE_CHANGE event
|
||||
* on_job_update Reorder the job in candidates if its priority or
|
||||
* queue changed. <- JOB_UPDATE event
|
||||
*
|
||||
* Session hooks (MUST) — picker thread, at scheduling-round boundaries:
|
||||
* begin_session Reset the iteration cursor. Called at the start of
|
||||
* every scheduling round. Session-aware requirement: a
|
||||
* picker MUST reset all iteration state here — never
|
||||
* carry it over from the previous session.
|
||||
* next_job Return the next schedulable job per the picker's
|
||||
* ordering, or NULL once exhausted. Cancel-safe
|
||||
* requirement: next_job() runs in the dedicated picker
|
||||
* thread, which is torn down via pthread_cancel — the
|
||||
* iteration loop inside next_job() (and any blocking
|
||||
* point it reaches, e.g. cbuf_get(&wbuf) between
|
||||
* sessions) MUST have defined pthread_cancel
|
||||
* cancellation points (pthread_testcancel() or an
|
||||
* existing cancellation-point call).
|
||||
* end_session End-of-round notification; called before the core
|
||||
* sleeps between rounds.
|
||||
*
|
||||
* Post-allocation hooks (MUST) — allocator thread, after each allocation
|
||||
* attempt for a job next_job() returned:
|
||||
* on_dispatch Job was allocated; remove it from candidates
|
||||
* permanently.
|
||||
* on_pend Job was pended (or preempted back to pend); keep it for
|
||||
* the next round, may deprioritize.
|
||||
*
|
||||
* Optional hooks (MAY be NULL) — same thread as the triggering event
|
||||
* above (on_config: driver thread inside core_push_events(); on_job_rusage:
|
||||
* driver thread inside core_push_events()):
|
||||
* on_config Rebuild picker strategy from config. NULL if the
|
||||
* picker is config-agnostic.
|
||||
* on_job_rusage Track per-job resource usage. NULL if the picker is
|
||||
* not resource-aware.
|
||||
*
|
||||
* Contract (Req 1 AC 5, Req 2 AC 6):
|
||||
* - Cancel-safe: next_job() has defined pthread_cancel cancellation
|
||||
* points.
|
||||
* - Session-aware: begin_session() resets all iteration state.
|
||||
* - Hook implementations never block indefinitely — the picker thread
|
||||
* holds no lock the allocator thread needs.
|
||||
* - next_job() never returns the same job twice within one session.
|
||||
*
|
||||
* Preconditions / postconditions:
|
||||
* - init() is called exactly once before any other hook.
|
||||
* - begin_session() precedes every next_job() sequence.
|
||||
* - next_job() returns NULL exactly when no ready job remains for the
|
||||
* current session.
|
||||
*/
|
||||
typedef struct {
|
||||
int (*init)(void* picker, xce_sched_core_t* core);
|
||||
void (*fini)(void* picker);
|
||||
int (*begin_session)(void* picker);
|
||||
xce_sched_job_t* (*next_job)(void* picker);
|
||||
int (*end_session)(void* picker);
|
||||
int (*on_dispatch)(void* picker, uint64_t jobid);
|
||||
int (*on_pend)(void* picker, uint64_t jobid);
|
||||
int (*on_queue_update)(void* picker, xce_sched_queue_t* queue);
|
||||
int (*on_job_push)(void* picker, xce_sched_job_t* job);
|
||||
int (*on_job_state_change)(void* picker, xce_sched_job_t* job,
|
||||
uint32_t state);
|
||||
int (*on_job_update)(void* picker, xce_sched_job_t* job);
|
||||
int (*on_config)(void* picker, xce_conf4sched_t* config); /* MAY be NULL */
|
||||
int (*on_job_rusage)(void* picker, xce_sched_job_t* job,
|
||||
xce_job_rusages_t* rusages); /* MAY be NULL */
|
||||
} xce_job_picker_iface_t;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -56,6 +56,18 @@ sched_job_array_wire_e2e_test = executable('sched_job_array_wire_e2e_test',
|
|||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
||||
# Compile-check for the job picker plugin contract, xce_job_picker_iface_t
|
||||
# (scheduler-core spec, task 1.1). Only #includes plugin.h -- no picker
|
||||
# implementation exists yet (that's task 2.x). -Werror on incompatible
|
||||
# pointer types so a hook signature drift in plugin.h fails the build, not
|
||||
# just an assertion: the test's real content is "this compiles at all".
|
||||
sched_picker_iface_test = executable('sched_picker_iface_test',
|
||||
['test_picker_iface.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
c_args: ['-Werror=incompatible-pointer-types'],
|
||||
install: false)
|
||||
|
||||
# Register as meson tests so `meson test --suite scheduler` runs them all.
|
||||
# detect_leaks=0 matches the suite convention (fixtures are not torn down).
|
||||
test('scheduler test', scheduler_test, suite: 'scheduler',
|
||||
|
|
@ -68,3 +80,5 @@ test('scheduler job-array expand test', sched_job_array_test, suite: 'scheduler'
|
|||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
test('scheduler job-array wire e2e test', sched_job_array_wire_e2e_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
test('scheduler picker iface test', sched_picker_iface_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
|
|
|||
227
src/scheduler/testsuite/test_picker_iface.c
Normal file
227
src/scheduler/testsuite/test_picker_iface.c
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* test_picker_iface.c - standalone compile-check test for the job picker
|
||||
* plugin contract, xce_job_picker_iface_t (scheduler-core spec, task 1.1).
|
||||
*
|
||||
* This task only adds a header (plugin.h) -- there is no picker
|
||||
* implementation yet (that lands in task 2.x, builtin_fcfs). So the
|
||||
* red/green evidence for this task is a *compile-time* contract check, not
|
||||
* a runtime behavior check:
|
||||
*
|
||||
* RED: before xce_job_picker_iface_t existed in plugin.h, this file
|
||||
* failed to compile (unknown type xce_job_picker_iface_t).
|
||||
* GREEN: with the header in place, every hook category from Req 1 AC 2
|
||||
* (lifecycle, state-change, session, post-allocation, optional)
|
||||
* is present with the exact signature design.md specifies, and a
|
||||
* correctly-typed function pointer can be assigned to every
|
||||
* field without an incompatible-pointer-type diagnostic. This
|
||||
* test target builds with -Werror=incompatible-pointer-types
|
||||
* (see testsuite/meson.build) specifically so a signature drift
|
||||
* in plugin.h fails the build, not just this assertion file.
|
||||
*
|
||||
* Each stub below is intentionally typed via an explicit named function
|
||||
* pointer variable of the exact field type (copied from plugin.h) before
|
||||
* being stored into the struct literal -- that forces the compiler to
|
||||
* check the stub's signature against the interface's declared signature
|
||||
* at the point of assignment, which is the property this test exists to
|
||||
* verify.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
/* ---- lifecycle hooks ---- */
|
||||
static int stub_init(void* picker, xce_sched_core_t* core)
|
||||
{
|
||||
(void)picker;
|
||||
(void)core;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stub_fini(void* picker) { (void)picker; }
|
||||
|
||||
/* ---- session hooks ---- */
|
||||
static int stub_begin_session(void* picker)
|
||||
{
|
||||
(void)picker;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static xce_sched_job_t* stub_next_job(void* picker)
|
||||
{
|
||||
(void)picker;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int stub_end_session(void* picker)
|
||||
{
|
||||
(void)picker;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- post-allocation hooks ---- */
|
||||
static int stub_on_dispatch(void* picker, uint64_t jobid)
|
||||
{
|
||||
(void)picker;
|
||||
(void)jobid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_on_pend(void* picker, uint64_t jobid)
|
||||
{
|
||||
(void)picker;
|
||||
(void)jobid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- state-change hooks ---- */
|
||||
static int stub_on_queue_update(void* picker, xce_sched_queue_t* queue)
|
||||
{
|
||||
(void)picker;
|
||||
(void)queue;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_on_job_push(void* picker, xce_sched_job_t* job)
|
||||
{
|
||||
(void)picker;
|
||||
(void)job;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_on_job_state_change(void* picker, xce_sched_job_t* job,
|
||||
uint32_t state)
|
||||
{
|
||||
(void)picker;
|
||||
(void)job;
|
||||
(void)state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_on_job_update(void* picker, xce_sched_job_t* job)
|
||||
{
|
||||
(void)picker;
|
||||
(void)job;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- optional hooks (MAY be NULL) ---- */
|
||||
static int stub_on_config(void* picker, xce_conf4sched_t* config)
|
||||
{
|
||||
(void)picker;
|
||||
(void)config;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_on_job_rusage(void* picker, xce_sched_job_t* job,
|
||||
xce_job_rusages_t* rusages)
|
||||
{
|
||||
(void)picker;
|
||||
(void)job;
|
||||
(void)rusages;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
/* Assigns every hook through an explicitly-typed intermediate function
|
||||
* pointer -- see file header comment. If any field's declared signature in
|
||||
* plugin.h drifted from the stub below, this would fail to compile
|
||||
* (incompatible-pointer-types, -Werror'd for this target). */
|
||||
static void test_picker_iface_has_every_hook_with_correct_signature(void)
|
||||
{
|
||||
int (*init_fn)(void*, xce_sched_core_t*) = stub_init;
|
||||
void (*fini_fn)(void*) = stub_fini;
|
||||
int (*begin_session_fn)(void*) = stub_begin_session;
|
||||
xce_sched_job_t* (*next_job_fn)(void*) = stub_next_job;
|
||||
int (*end_session_fn)(void*) = stub_end_session;
|
||||
int (*on_dispatch_fn)(void*, uint64_t) = stub_on_dispatch;
|
||||
int (*on_pend_fn)(void*, uint64_t) = stub_on_pend;
|
||||
int (*on_queue_update_fn)(void*, xce_sched_queue_t*) = stub_on_queue_update;
|
||||
int (*on_job_push_fn)(void*, xce_sched_job_t*) = stub_on_job_push;
|
||||
int (*on_job_state_change_fn)(void*, xce_sched_job_t*, uint32_t)
|
||||
= stub_on_job_state_change;
|
||||
int (*on_job_update_fn)(void*, xce_sched_job_t*) = stub_on_job_update;
|
||||
int (*on_config_fn)(void*, xce_conf4sched_t*) = stub_on_config;
|
||||
int (*on_job_rusage_fn)(void*, xce_sched_job_t*, xce_job_rusages_t*)
|
||||
= stub_on_job_rusage;
|
||||
|
||||
xce_job_picker_iface_t iface = {
|
||||
.init = init_fn,
|
||||
.fini = fini_fn,
|
||||
.begin_session = begin_session_fn,
|
||||
.next_job = next_job_fn,
|
||||
.end_session = end_session_fn,
|
||||
.on_dispatch = on_dispatch_fn,
|
||||
.on_pend = on_pend_fn,
|
||||
.on_queue_update = on_queue_update_fn,
|
||||
.on_job_push = on_job_push_fn,
|
||||
.on_job_state_change = on_job_state_change_fn,
|
||||
.on_job_update = on_job_update_fn,
|
||||
.on_config = on_config_fn,
|
||||
.on_job_rusage = on_job_rusage_fn,
|
||||
};
|
||||
|
||||
/* MUST hooks are all non-NULL when populated. */
|
||||
TEST_ASSERT_NOT_NULL(iface.init);
|
||||
TEST_ASSERT_NOT_NULL(iface.fini);
|
||||
TEST_ASSERT_NOT_NULL(iface.begin_session);
|
||||
TEST_ASSERT_NOT_NULL(iface.next_job);
|
||||
TEST_ASSERT_NOT_NULL(iface.end_session);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_dispatch);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_pend);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_queue_update);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_job_push);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_job_state_change);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_job_update);
|
||||
|
||||
/* Optional hooks are populated here (proving the signature compiles),
|
||||
* but the contract (Req 1 AC 2) is that they MAY be NULL -- exercised
|
||||
* by the next test. */
|
||||
TEST_ASSERT_NOT_NULL(iface.on_config);
|
||||
TEST_ASSERT_NOT_NULL(iface.on_job_rusage);
|
||||
|
||||
/* Confirm the assignment actually stored the intended function, not
|
||||
* merely "a" function -- pins the test to the real symbols above. */
|
||||
TEST_ASSERT_EQUAL_PTR(stub_init, iface.init);
|
||||
TEST_ASSERT_EQUAL_PTR(stub_next_job, iface.next_job);
|
||||
TEST_ASSERT_EQUAL_PTR(stub_on_job_state_change, iface.on_job_state_change);
|
||||
}
|
||||
|
||||
/* Req 1 AC 2: on_config and on_job_rusage are the two hooks explicitly
|
||||
* documented "MAY be NULL". A picker that doesn't need them (e.g. FCFS)
|
||||
* must be able to leave them unset. */
|
||||
static void test_picker_iface_optional_hooks_may_be_null(void)
|
||||
{
|
||||
xce_job_picker_iface_t iface = {
|
||||
.init = stub_init,
|
||||
.fini = stub_fini,
|
||||
.begin_session = stub_begin_session,
|
||||
.next_job = stub_next_job,
|
||||
.end_session = stub_end_session,
|
||||
.on_dispatch = stub_on_dispatch,
|
||||
.on_pend = stub_on_pend,
|
||||
.on_queue_update = stub_on_queue_update,
|
||||
.on_job_push = stub_on_job_push,
|
||||
.on_job_state_change = stub_on_job_state_change,
|
||||
.on_job_update = stub_on_job_update,
|
||||
.on_config = NULL,
|
||||
.on_job_rusage = NULL,
|
||||
};
|
||||
|
||||
TEST_ASSERT_NULL(iface.on_config);
|
||||
TEST_ASSERT_NULL(iface.on_job_rusage);
|
||||
/* MUST hooks stay required even when the optional ones are absent. */
|
||||
TEST_ASSERT_NOT_NULL(iface.next_job);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_picker_iface_has_every_hook_with_correct_signature);
|
||||
RUN_TEST(test_picker_iface_optional_hooks_may_be_null);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue