feat(scheduler-core): implement clock virtualization and session timing (task 4.5)

Adds xce_core_now() (new core_clock.c, kept separate from core.c so
test_core_isolation.c's wall-clock scan still enforces zero direct
clock_gettime()/time() calls elsewhere in the core): the two-anchor
plus speed formula (virtual_clock_base + (elapsed * speed_num) /
speed_den), covering all four modes (normal, discrete/real-time/fast-
slow simulation) via four new atomic fields on xce_sched_core_t,
defaulted to normal-mode (1/1 speed) in xce_sched_core_init().

requirements.md's literal tlog_reg_format_func(fn, core_ptr) call
shape doesn't exist in the real vendored tlog library (registration
takes only the callback; userptr is per-call-site, not bindable at
registration) -- resolved with a module-level active-core pointer read
by a new core-scoped format function, not wired into mylog_init()/
main.c yet. xce_core_go()/xce_core_end_session() (task 4.4) now stamp
session_start/compute session_duration_ns via xce_core_now(), with
their existing session-active/reentrancy behavior untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dw5WnN3DXStaEaPYfACU1
This commit is contained in:
Rongsong Shen 2026-08-04 03:38:58 +00:00
parent de69b6288d
commit c5432f7786
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
9 changed files with 849 additions and 5 deletions

View file

@ -23,6 +23,19 @@
* testsuite/test_core_isolation.c (source-text scan + an nm(1) undefined-
* symbol audit of core.c's compiled object, the build-time proof this
* task's Observable requires).
*
* One documented exception (task 4.5, Req 5 AC 3): the core's single
* sanctioned wall-clock call -- clock_gettime(CLOCK_MONOTONIC) inside
* xce_core_now() -- and its tlog format-function integration live in
* core_clock.c, a SEPARATE file from core.c, specifically so
* test_core_isolation.c's core.c-scoped "zero wall-clock calls" text scan
* keeps proving what it has always proven (core.c itself never reads real
* time) without needing to special-case the one legitimate call site AC 3
* mandates. tlog (tinylog) is likewise an allowed core dependency as of
* this task -- AC 3's own "Third-party library constraint" section
* discusses the core registering a tlog format function -- distinct from
* the forbidden client.h/netio.h/xceapi.h/delay.h/idgen.h/xce/cluster.h
* list above, none of which tlog is part of.
*/
#include <pthread.h>
@ -102,6 +115,55 @@ struct xce_sched_core_s {
_Atomic int staging_merge_complete; /* 1 after staging merge completes */
select_eval_ctx_t* sel_eval_ctx; /* session-level memoisation context; NULL
between sessions */
/*
* Clock virtualization (Req 5 AC 3, scheduler-core spec, task 4.5): the
* core's ONLY sanctioned time source is xce_core_now() (core_clock.c),
* which computes virtual time from these four fields via the
* two-anchor + speed formula:
*
* wall_ns = clock_gettime(CLOCK_MONOTONIC) // the one wall-clock
* // call in the whole
* // core -- lives in
* // core_clock.c, NOT
* // core.c (core.c's own
* // zero-wall-clock
* // guarantee, enforced
* // by
* // test_core_isolation.c,
* // stays intact)
* elapsed = wall_ns - real_clock_base
* xce_core_now() = virtual_clock_base + (elapsed * speed_num) / speed_den
*
* Atomically updated by the driver AS A GROUP before each event
* dispatch (design.md's State Management note) -- xce_core_now() reads
* them lock-free, no mutex, safe from both the picker and allocator
* threads. xce_sched_core_init() seeds these to AC 3's Normal-mode row
* (virtual_clock_base=0, real_clock_base=0, speed_num=1, speed_den=1):
* degraded-to-real-wall-clock default, today's existing production
* behavior, preserved until a future simulation driver (Phase 3)
* explicitly reconfigures them.
*/
_Atomic uint64_t virtual_clock_base;
_Atomic uint64_t real_clock_base;
_Atomic uint64_t speed_num;
_Atomic uint64_t speed_den;
/*
* Session response-time diagnostics (Req 5 AC 12, task 4.5). Plain
* (non-atomic) fields -- touched only by the driver thread, inside
* xce_core_go()/xce_core_end_session() (core_session.c), the same
* single-writer discipline core->sel_eval_ctx above already follows.
* session_start is stamped at the top of xce_core_go(); the field name
* matches requirements.md's own pseudocode verbatim
* ("session_start = xce_core_now()"). session_duration_ns is computed
* at the top of xce_core_end_session() as xce_core_now() -
* session_start. Both are diagnostics only -- nothing in the
* scheduling/decision path reads them. The ReplaySession.summary
* SessionSummary wire struct these values eventually feed is explicitly
* OUT of this task's scope (Phase 3, recording/replay-format tasks
* 7.x) -- this struct only holds the plain numeric tracking.
*/
uint64_t session_start;
uint64_t session_duration_ns;
};
/*
@ -302,4 +364,40 @@ extern int xce_core_go(xce_sched_core_t*);
*/
extern int xce_core_end_session(xce_sched_core_t*);
/*
* xce_core_now - Req 5 AC 3 (scheduler-core spec, task 4.5): the core's
* virtualized clock accessor, and the ONLY place inside the scheduler-core
* module family that calls clock_gettime() (implemented in core_clock.c,
* deliberately NOT core.c -- see this header's own top-of-file boundary
* comment and xce_sched_core_t's clock-field doc comment above for why).
* Computes virtual time via the two-anchor + speed formula from
* virtual_clock_base/real_clock_base/speed_num/speed_den, reading all four
* atomically (lock-free, safe from any thread). Returns 0 if core is NULL.
*/
extern uint64_t xce_core_now(xce_sched_core_t*);
/*
* xce_core_set_active_for_logging / xce_core_register_tlog_format - Req 5
* AC 3's tlog integration (task 4.5). See core_clock.c's own file header
* comment for the full reasoning behind this exact mechanism, including why
* it deliberately does NOT match AC 3's literal
* `tlog_reg_format_func(fn, core_ptr)` pseudocode -- that call shape does
* not exist in the real vendored tlog.h (`tlog_reg_format_func()` takes
* only the callback, no userptr-binding parameter; userptr is a per-call-
* site argument that flows through tlog_ext()/tlog_vext(), not something
* bindable once at registration time).
*
* xce_core_set_active_for_logging(core) sets (or, passed NULL, clears) a
* module-level "active core for logging" pointer that core_clock.c's
* registered tlog_format_func reads via xce_core_now() instead of
* info->time. xce_core_register_tlog_format() registers that format
* function with tlog via the real tlog_reg_format_func(). Neither is
* called from mylog_init()/main.c by this task -- wiring the live
* scheduler process over to this mechanism is a later task's job,
* consistent with tasks 4.1-4.4's additive-only, unwired-until-a-driver-
* exists pattern.
*/
extern void xce_core_set_active_for_logging(xce_sched_core_t*);
extern int xce_core_register_tlog_format(void);
#endif