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:
parent
62d0b8b686
commit
2d7ba66019
3 changed files with 85 additions and 6 deletions
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue