feat(scheduler-sim-live-allocation): thread core state through allocation context and host search (task 2.1)

Retypes xce_sched_ctx_t.sched (sched/types.h) from void* to
xce_sched_core_t* outright, and sched_try_alloc_on_host_() (resource.c)
to take xce_sched_core_t* directly -- completing what tasks 1.2/1.3
left as temporary &(sched->core) call-site wraps. Removes the two
resulting struct-prefix casts entirely (schedobj.c's
sched_ctx_set_primary(), host_iter.c's sched_host_iter_next()): both
now pass core state through honestly, with no cast.

Updates every sched_ctx_init()/sched_try_alloc_on_host_() call site
tree-wide (scheduler.c, parjob_allocator.c, seqjob_allocator.c) with
zero change to host_iter/parjob/seqjob's own host-iteration/
bucket-construction algorithm.

Adds a driver-parity test for sched_try_alloc_on_host_() proving
identical grant/deny results from normal-mode-shaped and
simulation-mode-shaped core state.

Flags (for task 2.3) a confirmed-dead, type-confused write at
plugins/limit.c:319 (ctx.sched = plugin->sched) -- never read today,
left untouched since limit.c is task 2.3's boundary, not this one's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
Rongsong Shen 2026-09-18 13:52:14 +00:00
parent 55173fca27
commit d52c78e4be
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
14 changed files with 229 additions and 54 deletions

View file

@ -49,6 +49,14 @@
#include "sched/alloc_iter.h"
#include "scheduler.h"
/* scheduler-sim-live-allocation spec, task 2.1: sim.h only depends on
* core.h (+ stdbool/stdint/stdio, per its own header comment), so it is
* safe to pull in here just for xce_sched_sim_driver_t -- needed to build
* a simulation-mode-shaped container for the driver-parity test added by
* this task (mirrors test.c's own test_core_safe_allocation_chain_driver_
* parity(), task 1.3). */
#include "sim.h"
/* Pull in the real xce_sched_core_init()/sched_add_worker() (core.c),
* sched_meet_host_selection()/sched_check_host_satisfy()/put_resvals()
* (resource.c, the evaluator + availability check under test), the real
@ -178,7 +186,9 @@ static void make_rack_eq_select(xce_select_node_t* resname,
static void init_ctx(xce_sched_ctx_t* ctx, xce_scheduler_t* sched,
xce_sched_obj_t* obj, xce_primary_resreq_t* primary)
{
int rc = sched_ctx_init(ctx, sched, obj);
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched->core), not sched. */
int rc = sched_ctx_init(ctx, &(sched->core), obj);
TEST_ASSERT_EQUAL_INT(0, rc);
ctx->primary = primary;
}
@ -610,6 +620,112 @@ static void test_init_rejects_missing_ctx(void)
TEST_ASSERT_EQUAL_INT(-EINVAL, host_iter->init(host_iter, NULL, NULL));
}
/*
* scheduler-sim-live-allocation spec, task 2.1 (Core-Safe Allocation
* Chain): sched_try_alloc_on_host_() (resource.c) -- the per-host
* allocation-attempt leaf that both host_iter.c's own capacity checks
* ultimately mirror and that parjob_allocator.c's/seqjob_allocator.c's
* my_alloc() call once per candidate -- is now core-typed. This drives it
* end to end (real sched_ctx_get_uncomit_host()/sched_check_host_satisfy(),
* schedobj.c+resource.c, this file's own plugin_prepare_allocation() no-op
* stub above) against BOTH a normal-mode-shaped (xce_scheduler_t) and a
* simulation-mode-shaped (xce_sched_sim_driver_t) core container, proving
* identical grant/deny results regardless of which driver's core state is
* passed in -- mirrors test.c's own
* test_core_safe_allocation_chain_driver_parity() (task 1.3) for the one
* function that test doesn't itself cover.
*
* RED (before task 2.1): fails to compile -- sched_try_alloc_on_host_()
* still takes xce_scheduler_t*, so passing xce_sched_core_t* directly is
* an incompatible-pointer-type error (this toolchain treats
* -Wincompatible-pointer-types as fatal, not a warning).
*/
static int try_alloc_once(xce_sched_core_t* core, xce_sched_worker_t* w,
float host_wanted)
{
xce_sched_ctx_t ctx;
xce_sched_obj_t obj;
xce_primary_resreq_t primary;
int rc;
memset(&primary, 0, sizeof(primary));
primary.minTasks = 1;
primary.maxTasks = 1;
primary.select = NULL;
init_primary_obj(&obj);
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, core, &obj));
ctx.primary = &primary;
/* sched_try_alloc_on_host_()'s resource-unmet branch assumes
* ctx->reason already exists (unlike its select-mismatch branch, which
* lazily creates one) -- my_alloc() always creates one before ever
* reaching this leaf in production; this test calls the leaf directly,
* bypassing that step, so it must set one up itself. Same pattern
* test_parjob_combinatorial.c's own direct parjob_try_plan_() call
* already established for the identical reason. */
ctx.reason = make_pendreason(0);
TEST_ASSERT_NOT_NULL(ctx.reason);
set_wanted_slots(&ctx, host_wanted);
rc = sched_try_alloc_on_host_(w, core, &ctx);
sched_ctx_fini(&ctx);
return rc;
}
static void
test_try_alloc_on_host_grants_and_denies_identically_for_both_driver_shapes(
void)
{
xce_scheduler_t sched_normal;
xce_sched_sim_driver_t sim_shape;
xce_sched_core_t* core_normal = &(sched_normal.core);
xce_sched_core_t* core_sim = &(sim_shape.core);
xce_sched_worker_t* w_normal;
xce_sched_worker_t* w_sim;
int rc_normal, rc_sim;
memset(&sched_normal, 0, sizeof(sched_normal));
memset(&sim_shape, 0, sizeof(sim_shape));
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(core_normal));
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(core_sim));
add_worker(core_normal, "only", 4.0f, 0.0f, 0.0);
add_worker(core_sim, "only", 4.0f, 0.0f, 0.0);
w_normal = sched_get_worker(core_normal, "only");
w_sim = sched_get_worker(core_sim, "only");
TEST_ASSERT_NOT_NULL(w_normal);
TEST_ASSERT_NOT_NULL(w_sim);
/* Grant: wants 3 of 4 free slots. */
rc_normal = try_alloc_once(core_normal, w_normal, 3.0f);
rc_sim = try_alloc_once(core_sim, w_sim, 3.0f);
TEST_ASSERT_EQUAL_INT(1, rc_normal);
TEST_ASSERT_EQUAL_INT_MESSAGE(
rc_normal, rc_sim,
"sched_try_alloc_on_host_() must grant identically for a "
"normal-mode-shaped and a simulation-mode-shaped core");
/* Deny: wants 8, only 4 free -- the per-ctx uncommitted debit tracked
* by the grant above never touches the worker's own resInUse, so this
* second, fresh-ctx attempt against the SAME worker still sees all 4
* free, exactly like test.c's own driver-parity test reuses one
* worker across a grant then a deny sub-case. */
rc_normal = try_alloc_once(core_normal, w_normal, 8.0f);
rc_sim = try_alloc_once(core_sim, w_sim, 8.0f);
TEST_ASSERT_EQUAL_INT(0, rc_normal);
TEST_ASSERT_EQUAL_INT_MESSAGE(
rc_normal, rc_sim,
"sched_try_alloc_on_host_() must deny identically for a "
"normal-mode-shaped and a simulation-mode-shaped core");
}
int main(void)
{
UNITY_BEGIN();
@ -620,5 +736,7 @@ int main(void)
test_host_too_small_for_larger_search_remains_candidate_for_smaller_search);
RUN_TEST(test_next_has_a_real_cancellation_point);
RUN_TEST(test_init_rejects_missing_ctx);
RUN_TEST(
test_try_alloc_on_host_grants_and_denies_identically_for_both_driver_shapes);
return UNITY_END();
}

View file

@ -394,7 +394,9 @@ test_ctx_init_defaults_alloc_mode_to_normal(void)
obj.tag = XCE_RESREQ_PRIMARY;
xce_sched_ctx_t ctx;
int rc = sched_ctx_init(&ctx, &sched, &obj);
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched.core), not &sched. */
int rc = sched_ctx_init(&ctx, &(sched.core), &obj);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(XCE_ALLOC_NORMAL, ctx.alloc_mode);

View file

@ -304,7 +304,9 @@ static int drive_my_alloc(xce_scheduler_t* sched, xce_sched_obj_t* obj,
{
xce_sched_plugin_t plugin;
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(ctx, sched, obj));
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched->core), not sched. */
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(ctx, &(sched->core), obj));
sched_ctx_set_primary(ctx, primary);
memset(&plugin, 0, sizeof(plugin));
@ -790,7 +792,9 @@ static void test_descending_bucket_order_succeeds_where_ascending_fails(void)
primary.select = NULL;
init_primary_obj(&obj, &primary_obj, &primary);
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &sched, &obj));
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched.core), not &sched. */
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &(sched.core), &obj));
sched_ctx_set_primary(&ctx, &primary);
/* my_alloc() always lazily creates ctx->reason before ever reaching

View file

@ -270,7 +270,9 @@ test_my_alloc_allocates_single_matching_host_through_shared_iterators(void)
init_primary_obj(&obj, &primary_obj, &primary);
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &sched, &obj));
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched.core), not &sched. */
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &(sched.core), &obj));
/* Real call -- exercises plugin_prepare_allocation()'s ctx->worker ==
* NULL branch exactly as sched_make_primary_allocation() does today,
* populating ctx->alloc_req.shared before my_alloc() ever runs. */
@ -325,7 +327,9 @@ test_seqjob_prepare_allocation_guards_against_maxtasks_gt_1_fanout(void)
init_primary_obj(&obj, &primary_obj, &primary);
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &sched, &obj));
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched.core), not &sched. */
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&ctx, &(sched.core), &obj));
ctx.primary = &primary;
/* Simulate parjob_prepare_allocation() having already staged this

View file

@ -307,7 +307,9 @@ static int drive_my_alloc(xce_scheduler_t* sched, xce_sched_obj_t* obj,
{
xce_sched_plugin_t plugin;
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(ctx, sched, obj));
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
* honestly typed xce_sched_core_t* -- pass &(sched->core), not sched. */
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(ctx, &(sched->core), obj));
sched_ctx_set_primary(ctx, primary);
memset(&plugin, 0, sizeof(plugin));