feat(scheduler-core): implement record start/stop lifecycle (task 9.2)

Adds recording-state fields directly to xce_scheduler_t (no new
xce_sched_driver_t type, per task 9.1's precedent). sched_record_start()/
sched_record_stop() implement AC 10: validate via task 9.1's path
validator, open with a TOCTOU-hardened sequence (O_NOFOLLOW, no O_TRUNC,
fstat()+st_nlink==1 check before ftruncate()), snapshot the core, write
ReplayHeader+SnapshotEntry on start, ReplayEvent{endSession} on stop.

driver.c gains boolean-gated recording hooks in
xce_sched_driver_translate_pending_events() (per-event-batch ReplayEvent
writes, new recording_events.c) and xce_sched_driver_run_normal_mode_round()
(per-session ReplaySession writes, new recording_session.c) -- zero I/O/
allocation when inactive.

Two real bugs found and fixed during implementation: bug-1498 (O_TRUNC
would have truncated a hard-linked victim file before the TOCTOU check
could reject it) and bug-1499 (a transient edit dropped
xce_core_signal_go(), causing a 3-way thread deadlock).

test_record_lifecycle.c decodes the full start->events->session->stop
cycle with the real capnp decoders and asserts the exact message
sequence; TOCTOU regression tests use real symlink(2)/link(2) syscalls.

25/25 scheduler suite, clang-format clean.
This commit is contained in:
Rongsong Shen 2026-08-05 09:07:51 +00:00
parent 4e5547ee55
commit ff8d4f416e
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
15 changed files with 2110 additions and 36 deletions

View file

@ -412,6 +412,28 @@ sched_recording_path_validation_test = executable(
dependencies: [scheduler_test_deps],
install: false)
# Contract test for the record start/stop lifecycle and the driver-loop
# recording-write hooks (scheduler-core spec, task 9.2, Req 5 AC 10): start
# writes a decodable ReplayHeader then SnapshotEntry; a full start -> submit
# event batch -> complete one real session (real picker + stand-in
# allocator threads, same rendezvous design as
# sched_driver_session_ordering_test) -> stop cycle produces exactly the
# expected message stream ending in ReplayEvent{endSession}; recording
# inactive costs zero file I/O; double-start/stop-when-not-recording fail
# cleanly; a TOCTOU-style symlink/hard-link swap between path validation
# and the eventual open() is rejected. Same white-box #include pattern as
# sched_driver_session_ordering_test (core.c/core_clock.c/core_events.c/
# core_session.c/driver.c/plugin.c) PLUS ../src/recording.c/
# recording_events.c/recording_session.c (the code under test) PLUS
# ../src/snapshot.c/snapshot_wire.c (task 8.1's real
# xce_sched_core_save_snapshot(), needed by sched_record_start()).
sched_record_lifecycle_test = executable(
'sched_record_lifecycle_test',
['test_record_lifecycle.c'],
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)
# Register as meson tests so `meson test --suite scheduler` runs them all.
# detect_leaks=0 matches the suite convention (fixtures are not torn down).
test('scheduler test', scheduler_test, suite: 'scheduler',
@ -475,3 +497,6 @@ test('scheduler snapshot save load test',
test('scheduler recording path validation test',
sched_recording_path_validation_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
test('scheduler record lifecycle test',
sched_record_lifecycle_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])