Clear the cache inside core_begin_session_internal() so a job cached as unsatisfiable in one session is re-evaluated fresh in the next (Req 7.2). An initial weak-symbol cast-based approach was rejected: it corrupted xce_sched_sim_driver_t.alt_events_file, since core_begin_session_internal() is shared between driver.c (real xce_scheduler_t) and sim_loop.c (a structurally different, deliberately isolated sim driver struct with no cache field at the same byte offset) -- the strong cache_clear() symbol won unconditionally for both, invisible to ASAN and untested by any test_sim_*.c file. Fixed via a driver-registered callback: xce_sched_core_t gains a cache_clear_cb hook (NULL by default, per xce_sched_core_init()'s full-struct memset), set only in main.c's driver-mode branch -- mirroring the existing on_decision/xce_core_set_decision_cb() pattern. --sim mode returns before that registration is ever reached, so its hook stays NULL and core_begin_session_internal()'s guarded call is a no-op. The real xce_scheduler_t-casting adapter lives in alloc_cache.c rather than driver.c, since ~11 tests white-box-include driver.c without linking alloc_cache.c. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
140 lines
6.8 KiB
C
140 lines
6.8 KiB
C
/*
|
|
* test_sim_cache_clear_isolation.c - scheduler-allocation spec, task 5.2
|
|
* round 2 (Req 7.2 regression, mandated by the round-1 review that
|
|
* REJECTED this task for a CRITICAL memory-corruption defect).
|
|
*
|
|
* ── The bug this guards against ──────────────────────────────────────────
|
|
* core_begin_session_internal() (core_session.c) is reached from BOTH
|
|
* driver.c's xce_sched_driver_run_normal_mode_round() (a real
|
|
* xce_scheduler_t*, which HAS an unsatisfiable_cache field, scheduler.h)
|
|
* and sim_loop.c's sched_sim_driver_run() (a structurally different
|
|
* xce_sched_sim_driver_t*, sim.h -- Req 5 AC 17 "Simulation isolation" --
|
|
* which has NO unsatisfiable_cache field at all). Round 1 called
|
|
* alloc_cache.c's real, strong cache_clear(xce_scheduler_t*) via a
|
|
* same-named WEAK fallback declared in core_session.c, relying on
|
|
* link-time weak/strong symbol resolution. Since that resolution picks
|
|
* ONE winner for the whole binary (not per call site), BOTH the driver.c
|
|
* call (correct) and the sim_loop.c call (WRONG -- there is no cache to
|
|
* clear) resolved to the same real cache_clear(xce_scheduler_t*), which
|
|
* reinterpreted whatever field sits at unsatisfiable_cache's byte offset
|
|
* in xce_sched_sim_driver_t as a kvtab_t* and clobbered it. That offset is
|
|
* `alt_events_file` (sim.h) -- a live FILE* every --sim-alt-events run
|
|
* depends on. Empirically confirmed by this task's round-1 reviewer, and
|
|
* reproduced by this exact test file before round 2's fix (see this
|
|
* task's own Status Report for the before/after run).
|
|
*
|
|
* ── The fix this proves ──────────────────────────────────────────────────
|
|
* core_begin_session_internal() now calls a driver-registered hook
|
|
* (core->cache_clear_cb, core.h's xce_core_cache_clear_cb_t) ONLY if
|
|
* non-NULL, instead of calling cache_clear() unconditionally.
|
|
* sim_loop.c never calls xce_core_set_cache_clear_cb() -- a simulation-
|
|
* mode xce_sched_core_t's hook therefore stays NULL (xce_sched_core_
|
|
* init()'s memset() default), and core_begin_session_internal() treats
|
|
* NULL as "this driver has no cache to clear" and skips the call
|
|
* entirely. This test constructs a real xce_sched_sim_driver_t, points
|
|
* alt_events_file at a real, open FILE*, deliberately does NOT register
|
|
* any cache_clear_cb (mirroring production sim_loop.c exactly), calls the
|
|
* REAL xce_core_begin_session() (core_session.c's public entry point),
|
|
* and asserts alt_events_file is bit-for-bit unchanged across the call.
|
|
*
|
|
* ── Link technique ───────────────────────────────────────────────────────
|
|
* Same technique as the sibling test_core_session_cache_clear.c (round 1):
|
|
* this fixture's meson.build entry lists core.c/core_clock.c/
|
|
* core_session.c/decision.c/alloc_cache.c as genuinely SEPARATE compiled
|
|
* sources rather than white-box #include-ing them, specifically so
|
|
* alloc_cache.c's real, strong cache_clear(xce_scheduler_t*) IS present
|
|
* in this binary -- if a future regression reintroduced an unconditional
|
|
* call to it, this test would still catch it (a white-box #include of
|
|
* core_session.c alone, without also linking alloc_cache.c, could not
|
|
* have caught round 1's bug at all -- that is exactly why round 1's bug
|
|
* was invisible to the existing suite).
|
|
*
|
|
* kill_sched_* / plugin_unregister / sched_worker_release_host_ctx stubs
|
|
* below are identical, for identical reasons, to test_core_session_
|
|
* cache_clear.c's own (see that file's header comment).
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "sched/alloc_cache.h"
|
|
#include "sim.h"
|
|
|
|
void kill_sched_job(void* arg) { (void)arg; }
|
|
void kill_sched_queue(void* arg) { (void)arg; }
|
|
void kill_sched_worker(void* arg) { (void)arg; }
|
|
void kill_sched_user(void* arg) { (void)arg; }
|
|
void kill_sched_project(void* arg) { (void)arg; }
|
|
void kill_sched_resval(xce_val_t* val) { (void)val; }
|
|
void plugin_unregister(void* arg) { (void)arg; }
|
|
|
|
/* sched_worker_release_host_ctx (resource.c) is outside this test's linked
|
|
* source set (only core.c/core_clock.c/core_session.c/decision.c/
|
|
* alloc_cache.c, meson.build) -- this fixture never adds a worker, so it
|
|
* is never actually invoked at runtime either. */
|
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
|
|
|
void setUp(void) { return; }
|
|
void tearDown(void) { return; }
|
|
|
|
/*
|
|
* THE Observable this regression guards (Req 7.2, round-2 review
|
|
* requirement): a simulation-mode xce_sched_core_t, which never registers
|
|
* a cache_clear_cb, must never have any of its sibling xce_sched_sim_
|
|
* driver_t fields touched by xce_core_begin_session() -- even with the
|
|
* real, strong alloc_cache.c cache_clear(xce_scheduler_t*) linked into
|
|
* the very same binary.
|
|
*/
|
|
static void test_sim_begin_session_never_corrupts_alt_events_file(void)
|
|
{
|
|
xce_sched_sim_driver_t sim;
|
|
FILE* f;
|
|
int rc;
|
|
|
|
memset(&sim, 0, sizeof(sim));
|
|
|
|
rc = xce_sched_core_init(&(sim.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
/* A real, live FILE* -- exactly what a --sim-alt-events run holds
|
|
* here in production. tmpfile() needs no cleanup path/permissions
|
|
* setup and is auto-removed on fclose()/process exit. */
|
|
f = tmpfile();
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
sim.alt_events_file = f;
|
|
|
|
/* Deliberately NOT calling xce_core_set_cache_clear_cb() here --
|
|
* mirrors sim_loop.c exactly, which never registers this hook (Req 5
|
|
* AC 17 "Simulation isolation": sim mode has no cache to clear). This
|
|
* is the crux of the regression: before round 2's fix,
|
|
* core_begin_session_internal() called cache_clear() unconditionally,
|
|
* so linking alloc_cache.c's real, strong cache_clear(xce_scheduler_t*)
|
|
* into this binary was enough to corrupt alt_events_file below even
|
|
* though nothing here ever asked for a cache to be cleared. */
|
|
rc = xce_core_begin_session(&(sim.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
TEST_ASSERT_EQUAL_PTR_MESSAGE(
|
|
f, sim.alt_events_file,
|
|
"xce_core_begin_session() corrupted xce_sched_sim_driver_t's "
|
|
"alt_events_file -- core_begin_session_internal() must treat a "
|
|
"NULL cache_clear_cb as a true no-op (Req 7.2 round 2), never "
|
|
"fall through to alloc_cache.c's xce_scheduler_t*-typed "
|
|
"cache_clear() for a simulation-mode core");
|
|
|
|
rc = xce_core_end_session(&(sim.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_sim_begin_session_never_corrupts_alt_events_file);
|
|
return UNITY_END();
|
|
}
|