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:
parent
4e5547ee55
commit
ff8d4f416e
15 changed files with 2110 additions and 36 deletions
|
|
@ -90,6 +90,53 @@ int sched_publish_pend_reason(xce_scheduler_t* sched, xce_preason_set_t* r)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Link-satisfying stubs for recording.c/recording_events.c/
|
||||
* recording_session.c's real functions -- as of scheduler-core spec task
|
||||
* 9.2, driver.c's (real, #included above)
|
||||
* xce_sched_driver_translate_pending_events() calls sched_record_write_events()
|
||||
* unconditionally (behind a NULL `record_ctx` check, not a call this test ever
|
||||
* takes since every call site below passes NULL), and
|
||||
* xce_sched_driver_run_normal_mode_round() (out of THIS file's scope, same as
|
||||
* the core_session.c/plugin.c/ scheduler.c stubs above) calls the
|
||||
* sched_record_session_* trio. This file never links recording.c's real
|
||||
* implementations (they need xce_sched_core_save_snapshot(), snapshot.c, an
|
||||
* even bigger dependency surface this file's own header comment already avoids
|
||||
* pulling in for sched_publish_dispatch()/sched_publish_pend_reason()). Never
|
||||
* actually invoked at runtime by any test in this file. */
|
||||
int sched_record_write_events(xce_scheduler_t* sched, xce_core_event_t* events,
|
||||
int n)
|
||||
{
|
||||
(void)sched;
|
||||
(void)events;
|
||||
(void)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sched_record_session_ctx_t* sched_record_session_begin(xce_scheduler_t* sched,
|
||||
uint64_t begin_round_ts)
|
||||
{
|
||||
(void)sched;
|
||||
(void)begin_round_ts;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sched_record_session_add_decision(sched_record_session_ctx_t* ctx,
|
||||
uint64_t ts, xce_decision_t* decision)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)ts;
|
||||
(void)decision;
|
||||
}
|
||||
|
||||
int sched_record_session_end(xce_scheduler_t* sched,
|
||||
sched_record_session_ctx_t* ctx, uint64_t end_ts)
|
||||
{
|
||||
(void)sched;
|
||||
(void)ctx;
|
||||
(void)end_ts;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Link-satisfying stubs: as of scheduler-core spec task 5.2,
|
||||
* xce_sched_driver_run_normal_mode_round() (driver.c, real, #included
|
||||
* above, alongside xce_sched_driver_translate_pending_events() this file
|
||||
|
|
@ -696,7 +743,7 @@ static void test_drain_translates_and_pushes_all_ten_op_types(void)
|
|||
r10->u.config = config;
|
||||
enqueue(&core, r10);
|
||||
|
||||
rc = xce_sched_driver_translate_pending_events(&core);
|
||||
rc = xce_sched_driver_translate_pending_events(&core, NULL);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(10, rc);
|
||||
|
||||
|
|
@ -746,7 +793,7 @@ static void test_accumulates_between_drains_and_empty_drain_is_noop(void)
|
|||
fixture_init(&core);
|
||||
|
||||
/* Nothing pending yet. */
|
||||
rc = xce_sched_driver_translate_pending_events(&core);
|
||||
rc = xce_sched_driver_translate_pending_events(&core, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* One record enqueued -- drained on the next call. Heap-allocated (see
|
||||
|
|
@ -764,13 +811,13 @@ static void test_accumulates_between_drains_and_empty_drain_is_noop(void)
|
|||
req->u.workerupdate.workers = workers;
|
||||
enqueue(&core, req);
|
||||
|
||||
rc = xce_sched_driver_translate_pending_events(&core);
|
||||
rc = xce_sched_driver_translate_pending_events(&core, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
TEST_ASSERT_NOT_NULL(sched_get_worker(&core, "hostB"));
|
||||
}
|
||||
|
||||
/* Nothing new pending -- must not re-process anything. */
|
||||
rc = xce_sched_driver_translate_pending_events(&core);
|
||||
rc = xce_sched_driver_translate_pending_events(&core, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
}
|
||||
|
||||
|
|
@ -780,7 +827,7 @@ static void test_accumulates_between_drains_and_empty_drain_is_noop(void)
|
|||
|
||||
static void test_null_core_returns_einval(void)
|
||||
{
|
||||
int rc = xce_sched_driver_translate_pending_events(NULL);
|
||||
int rc = xce_sched_driver_translate_pending_events(NULL, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
|
|
@ -790,7 +837,7 @@ static void test_uninitialized_pend_updates_returns_einval(void)
|
|||
int rc;
|
||||
|
||||
memset(&core, 0, sizeof(core));
|
||||
rc = xce_sched_driver_translate_pending_events(&core);
|
||||
rc = xce_sched_driver_translate_pending_events(&core, NULL);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue