feat(scheduler-sim-live-allocation): add unsatisfiable-cache callback seam (task 1.1)
Adds a NULL-by-default lookup/populate callback seam on xce_sched_core_t, mirroring cache_clear_cb's proven shape, so the allocation dispatch chokepoint can consult the unsatisfiable-resreq cache without a normal-mode-only field access in shared code. Adapters live in alloc_cache.c and are registered only during normal-mode startup; simulation mode leaves both callbacks NULL and always computes fresh. Adds the scheduler-sim-live-allocation spec (requirements/design/ research/tasks) as the planning artifact this and subsequent tasks implement against. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
487e7b9419
commit
89297dc825
10 changed files with 1130 additions and 0 deletions
|
|
@ -420,6 +420,140 @@ static void test_argument_validation(void)
|
|||
fini_sched(&sched);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Task 1.1 (scheduler-sim-live-allocation spec, Req 5.2): the */
|
||||
/* unsatisfiable-cache callback seam. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* A freshly-zeroed xce_sched_core_t (the same memset() default
|
||||
* xce_sched_core_init() applies -- core.c is not linked into this
|
||||
* white-box TU, so this test zeroes a bare instance directly rather than
|
||||
* calling that function) leaves BOTH new callback fields NULL. This is
|
||||
* the exact state every simulation-mode xce_sched_core_t is in forever --
|
||||
* sim_loop.c never registers either callback (Req 5 AC 17 "Simulation
|
||||
* isolation").
|
||||
*/
|
||||
static void test_seam_fields_default_null_on_zeroed_core(void)
|
||||
{
|
||||
xce_sched_core_t core;
|
||||
|
||||
memset(&core, 0, sizeof(core));
|
||||
|
||||
TEST_ASSERT_NULL(core.unsatisfiable_lookup_cb);
|
||||
TEST_ASSERT_NULL(core.unsatisfiable_populate_cb);
|
||||
}
|
||||
|
||||
/*
|
||||
* THE Observable this task's own TDD note names: "with both callbacks
|
||||
* left NULL, the allocation dispatch chokepoint always computes fresh
|
||||
* with no crash and no incorrect skip." The real chokepoint
|
||||
* (sched_make_primary_alloc_(), scheduler.c) is migrated to actually
|
||||
* consult this seam by a LATER task (tasks.md 2.2, boundary "Core-Safe
|
||||
* Allocation Chain, Unsatisfiable-Cache Callback Seam") -- this task adds
|
||||
* only the seam itself, not that call site. This test proves the seam's
|
||||
* own NULL-safety contract in isolation, using the identical NULL-check
|
||||
* shape design.md's own invariant specifies for that future call site
|
||||
* ("the allocation chain ... only ever NULL-checks and calls these"):
|
||||
* with both fields NULL, a NULL-checked consult never dereferences either
|
||||
* function pointer (no crash), is never mistaken for a cache HIT (no
|
||||
* incorrect skip -- `hit` stays 0, meaning "compute fresh"), and a
|
||||
* NULL-checked populate attempt is silently skipped, not a fatal error.
|
||||
*/
|
||||
static void test_null_callbacks_mean_always_compute_fresh(void)
|
||||
{
|
||||
xce_sched_core_t core;
|
||||
xce_sched_obj_t obj;
|
||||
xce_primary_schedobj_t primary;
|
||||
int category = -1;
|
||||
int hit = -1; /* poison: neither 0 nor 1 yet */
|
||||
|
||||
memset(&core, 0, sizeof(core));
|
||||
init_primary_obj(&obj, &primary, BASE_MIN_TASKS, BASE_MAX_TASKS,
|
||||
BASE_SELECT_NODE_ID, BASE_RUSAGE_HASH, BASE_USER,
|
||||
BASE_QUEUE, BASE_PROJECT);
|
||||
|
||||
/* Prove the guard's PRECONDITION explicitly, immediately before relying
|
||||
* on it below -- not just that the end result happens to be "compute
|
||||
* fresh", but that the NULL-ness this test's two guarded calls depend
|
||||
* on genuinely holds at the exact point each guard is evaluated. A
|
||||
* design that accidentally called through a non-NULL stale/garbage
|
||||
* pointer here (e.g. an uninitialized field instead of a true NULL)
|
||||
* could still coincidentally return 0/not crash on some platforms;
|
||||
* asserting NULL directly, right where each guard fires, rules that
|
||||
* out. */
|
||||
TEST_ASSERT_NULL(core.unsatisfiable_lookup_cb);
|
||||
if (core.unsatisfiable_lookup_cb != NULL) {
|
||||
hit = core.unsatisfiable_lookup_cb(&core, &obj, &category);
|
||||
} else {
|
||||
hit = 0; /* no cache registered => always a miss => compute fresh */
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(0, hit);
|
||||
|
||||
/* A NULL-checked populate attempt must be a true no-op, not a crash --
|
||||
* same "assert the guard's own precondition, right at the guard"
|
||||
* discipline as the lookup check just above. */
|
||||
TEST_ASSERT_NULL(core.unsatisfiable_populate_cb);
|
||||
if (core.unsatisfiable_populate_cb != NULL) {
|
||||
core.unsatisfiable_populate_cb(&core, &obj, 7);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Normal-mode observable (this task's own note): "a normal-mode test
|
||||
* confirming the cache is still consulted/populated exactly as before
|
||||
* this change." xce_alloc_cache_lookup_cb()/xce_alloc_cache_populate_cb()
|
||||
* (alloc_cache.c) are the two adapters main.c registers on the real
|
||||
* xce_scheduler_t's core (xce_sched_core_t core is xce_scheduler_t's
|
||||
* first member, scheduler.h -- the same pointer-to-first-member idiom
|
||||
* xce_alloc_cache_clear_cb() already uses). This test calls them through
|
||||
* core.unsatisfiable_lookup_cb/unsatisfiable_populate_cb exactly as a
|
||||
* normal-mode driver would, and confirms the result is identical to
|
||||
* calling cache_lookup()/cache_insert() directly -- proving the adapters
|
||||
* are a transparent passthrough, not a behavior change.
|
||||
*/
|
||||
static void test_lookup_and_populate_cb_adapters_match_direct_cache_calls(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_obj_t obj;
|
||||
xce_primary_schedobj_t primary;
|
||||
int out_direct = -1;
|
||||
int out_via_cb = -1;
|
||||
int rc;
|
||||
|
||||
init_sched(&sched);
|
||||
sched.core.unsatisfiable_lookup_cb = xce_alloc_cache_lookup_cb;
|
||||
sched.core.unsatisfiable_populate_cb = xce_alloc_cache_populate_cb;
|
||||
|
||||
init_primary_obj(&obj, &primary, BASE_MIN_TASKS, BASE_MAX_TASKS,
|
||||
BASE_SELECT_NODE_ID, BASE_RUSAGE_HASH, BASE_USER,
|
||||
BASE_QUEUE, BASE_PROJECT);
|
||||
|
||||
/* Miss before any insert, via the callback -- same as a direct
|
||||
* cache_lookup() call (test_lookup_before_any_insert_is_a_miss
|
||||
* above). */
|
||||
TEST_ASSERT_NOT_NULL(sched.core.unsatisfiable_lookup_cb);
|
||||
rc = sched.core.unsatisfiable_lookup_cb(&(sched.core), &obj, &out_via_cb);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* Populate via the callback... */
|
||||
TEST_ASSERT_NOT_NULL(sched.core.unsatisfiable_populate_cb);
|
||||
sched.core.unsatisfiable_populate_cb(&(sched.core), &obj, 55);
|
||||
|
||||
/* ...then confirm a DIRECT cache_lookup() call sees the SAME entry --
|
||||
* the adapter genuinely populated the real cache, not a shadow copy. */
|
||||
rc = cache_lookup(&sched, &obj, &out_direct);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
TEST_ASSERT_EQUAL_INT(55, out_direct);
|
||||
|
||||
/* And the callback-based lookup now also reports the same hit. */
|
||||
rc = sched.core.unsatisfiable_lookup_cb(&(sched.core), &obj, &out_via_cb);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
TEST_ASSERT_EQUAL_INT(55, out_via_cb);
|
||||
|
||||
fini_sched(&sched);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
|
@ -436,5 +570,8 @@ int main(void)
|
|||
RUN_TEST(test_clear_empties_all_entries);
|
||||
RUN_TEST(test_insert_duplicate_key_returns_eexist);
|
||||
RUN_TEST(test_argument_validation);
|
||||
RUN_TEST(test_seam_fields_default_null_on_zeroed_core);
|
||||
RUN_TEST(test_null_callbacks_mean_always_compute_fresh);
|
||||
RUN_TEST(test_lookup_and_populate_cb_adapters_match_direct_cache_calls);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue