feat(libxce-alloc-preason-equality): record session begin from the session-start clock reading (task 3.1)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-28 09:59:56 +00:00
parent 62d0b8b686
commit 2d7ba66019
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
3 changed files with 85 additions and 6 deletions

View file

@ -29,7 +29,7 @@ Test-first throughout (red → green → refactor). Every sub-task leaves the bu
- _Depends: 2.1_ - _Depends: 2.1_
- [ ] 3. Core: reproducible reservation start time in the scheduler - [ ] 3. Core: reproducible reservation start time in the scheduler
- [ ] 3.1 (P) Record each session's start from the same clock reading as the session start - [x] 3.1 (P) Record each session's start from the same clock reading as the session start
- Write the test first, in its own test function (not shared with 3.2): with recording enabled, a recorded session's begin time equals the session's start time exactly. - Write the test first, in its own test function (not shared with 3.2): with recording enabled, a recorded session's begin time equals the session's start time exactly.
- Take the recorded begin time from the session start instead of a second clock reading (design.md, Components: Recording). - Take the recorded begin time from the session start instead of a second clock reading (design.md, Components: Recording).
- Done when the test passes and the existing recording tests still pass. - Done when the test passes and the existing recording tests still pass.
@ -88,3 +88,4 @@ Test-first throughout (red → green → refactor). Every sub-task leaves the bu
## Implementation Notes ## Implementation Notes
- `xce-build.sh` resolves a relative build dir under `src/`: `bash src/tools/xce-build.sh lib asan xce.build.alloceq` builds into `src/xce.build.alloceq`, so iterate with `meson test -C src/xce.build.alloceq`. - `xce-build.sh` resolves a relative build dir under `src/`: `bash src/tools/xce-build.sh lib asan xce.build.alloceq` builds into `src/xce.build.alloceq`, so iterate with `meson test -C src/xce.build.alloceq`.
- `xce-build.sh lib` runs only the `libxce test` binary; run `meson test -C src/xce.build.alloceq "libxce alloc OOM test"` (or plain `meson test`) for the allocation OOM binary, which also wraps `vasprintf`/`__vasprintf_chk` since 2.1. - `xce-build.sh lib` runs only the `libxce test` binary; run `meson test -C src/xce.build.alloceq "libxce alloc OOM test"` (or plain `meson test`) for the allocation OOM binary, which also wraps `vasprintf`/`__vasprintf_chk` since 2.1.
- Simulation mode (`sim_loop.c`) still takes its own session-begin clock reading before `xce_core_begin_session()`; by design that is left to spec scheduler-sim-discrete-clock (3.1 fixed only the normal-mode recorder).

View file

@ -1024,10 +1024,17 @@ void xce_sched_driver_run_normal_mode_round(void* driver_ctx)
* the single boolean check + zero-allocation gate (Req 5 AC 10, * the single boolean check + zero-allocation gate (Req 5 AC 10,
* "Performance") -- it returns NULL immediately when `sched->recording` * "Performance") -- it returns NULL immediately when `sched->recording`
* is false, and every hook below is a cheap `rec_ctx != NULL` pointer * is false, and every hook below is a cheap `rec_ctx != NULL` pointer
* check with no further cost on that path. */ * check with no further cost on that path.
*
* beginRound is core->session_start, the clock reading
* xce_core_begin_session() took above -- NOT a second xce_core_now()
* read (libxce-alloc-preason-equality design.md Decision 8): every
* allocation's `from` is that same session start, so the recorded
* beginRound and `from` must come from one reading or a replay can
* differ by one second. Proven by test_driver_session_ordering.c's
* test_recorded_begin_round_equals_session_start. */
if (sched->recording) { if (sched->recording) {
rec_ctx rec_ctx = sched_record_session_begin(sched, sched->core.session_start);
= sched_record_session_begin(sched, xce_core_now(&(sched->core)));
} }
xce_core_signal_go(&(sched->core)); xce_core_signal_go(&(sched->core));

View file

@ -267,7 +267,10 @@ int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
* xce_sched_driver_run_normal_mode_round() never calls * xce_sched_driver_run_normal_mode_round() never calls
* sched_record_session_begin() with `sched.recording == true` -- either * sched_record_session_begin() with `sched.recording == true` -- either
* way, these stubs never need to do real work for this file's ordering * way, these stubs never need to do real work for this file's ordering
* proof, which is not about recording. Never links recording.c's real * proof, which is not about recording. (Exception:
* test_recorded_begin_round_equals_session_start sets `sched.recording`
* so the sched_record_session_begin() stub below captures its begin
* time.) Never links recording.c's real
* implementations (they need xce_sched_core_save_snapshot(), snapshot.c, * implementations (they need xce_sched_core_save_snapshot(), snapshot.c,
* an even bigger dependency surface this file's own fixture doesn't * an even bigger dependency surface this file's own fixture doesn't
* otherwise need). */ * otherwise need). */
@ -305,11 +308,20 @@ int sched_record_stop(xce_scheduler_t* sched)
return 0; return 0;
} }
/* libxce-alloc-preason-equality task 3.1: records the begin time
* xce_sched_driver_run_normal_mode_round() hands the recorder, so
* test_recorded_begin_round_equals_session_start can compare it with
* core->session_start. Returns NULL (no real recording context), so the
* round's per-decision/end hooks stay on their `rec_ctx == NULL` path. */
static int g_record_begin_calls;
static uint64_t g_record_begin_ts;
sched_record_session_ctx_t* sched_record_session_begin(xce_scheduler_t* sched, sched_record_session_ctx_t* sched_record_session_begin(xce_scheduler_t* sched,
uint64_t begin_round_ts) uint64_t begin_round_ts)
{ {
(void)sched; (void)sched;
(void)begin_round_ts; g_record_begin_calls++;
g_record_begin_ts = begin_round_ts;
return NULL; return NULL;
} }
@ -868,11 +880,70 @@ static void test_null_driver_ctx_is_safe_noop(void)
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_observer_end_calls)); TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_observer_end_calls));
} }
/*
* test_recorded_begin_round_equals_session_start - libxce-alloc-preason-
* equality task 3.1 (Req 4.2, 6.5; design.md Decision 8): with recording
* enabled, the begin time a normal-mode round hands
* sched_record_session_begin() (the recorded beginRound) is exactly
* core->session_start, the one clock reading xce_core_begin_session()
* took -- not a second xce_core_now() read after plugin_begin_session()/
* sched_account_jobs(). The same session start later becomes every
* allocation's `from` (task 3.2), so a replay reproduces both from one
* value.
*/
static void test_recorded_begin_round_equals_session_start(void)
{
xce_scheduler_t sched;
pthread_t picker_tid;
pthread_t allocator_tid;
test_driver_ctx_t driver_ctx;
int rc;
memset(&g_events, 0, sizeof(g_events));
memset(&sched, 0, sizeof(sched));
memset(&driver_ctx, 0, sizeof(driver_ctx));
g_record_begin_calls = 0;
g_record_begin_ts = 0;
rc = xce_sched_core_init(&(sched.core));
TEST_ASSERT_EQUAL_INT(0, rc);
lock(sched.core.plugins, RTTI_LOCK_WRITE);
rc = key_add(sched.core.plugins, (void*)g_stub_picker_plugin.name,
(void*)&g_stub_picker_plugin);
TEST_ASSERT_GREATER_OR_EQUAL(0, rc);
unlock(sched.core.plugins);
sched.core.job_pick_plugin = g_stub_picker_plugin.name;
sched.recording = true;
rc = xce_core_set_decision_cb(&(sched.core), test_on_decision, &driver_ctx);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = pthread_create(&picker_tid, NULL, picker_thread_fn, &(sched.core));
TEST_ASSERT_EQUAL_INT(0, rc);
rc = pthread_create(&allocator_tid, NULL, stand_in_allocator_thread_fn,
&(sched.core));
TEST_ASSERT_EQUAL_INT(0, rc);
xce_sched_driver_run_normal_mode_round(&sched);
rc = pthread_join(picker_tid, NULL);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = pthread_join(allocator_tid, NULL);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(1, g_record_begin_calls);
TEST_ASSERT_NOT_EQUAL(0, sched.core.session_start);
TEST_ASSERT_EQUAL_UINT64(sched.core.session_start, g_record_begin_ts);
}
int main(void) int main(void)
{ {
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST( RUN_TEST(
test_round_runs_plugin_begin_and_account_jobs_before_picker_signaled); test_round_runs_plugin_begin_and_account_jobs_before_picker_signaled);
RUN_TEST(test_null_driver_ctx_is_safe_noop); RUN_TEST(test_null_driver_ctx_is_safe_noop);
RUN_TEST(test_recorded_begin_round_equals_session_start);
return UNITY_END(); return UNITY_END();
} }