feat(core): mid-stream clock re-sync — live offset survives wall-clock steps and drift
Networking-audit deferred plan §2. The host↔client offset was measured once at connect; an NTP step or slow drift silently corrupted the clock-based jump-to-live signal, the ABR one-way-delay signal, and every latency stat — 4a3b1ae2's disarm backstop stopped the IDR storm but lost the detector for the session. Now the client re-estimates mid-stream and recovers it. - quic: ClockResync — the connect-time 8-round probe/echo estimate as a select!-driven state machine (rounds matched by echoed t1, stale batches ignored), plus accept_resync (batch min-RTT ≤ max(2 ms, 1.5× connect RTT) so a congested window can never bias the offset). No wire change: the host has always answered ClockProbe at any time on the control stream. - client: the offset lives in an Arc<AtomicI64> seeded at connect; the control task re-probes every 60 s and immediately after the pump's FIRST no-op clock flush (the "clock stepped under me" signal, sent on the next report tick). On apply: store, reset stale_frames/noop_clock_flushes, re-arm the clock detector if a step had disarmed it. The disarm heuristic stays as the final backstop. Public NativeClient::clock_offset_ns keeps the connect-time value (ABI untouched); new clock_offset_now_ns() / clock_offset_shared() expose the live value. - consumers migrated to the live offset: pf-client-core session stats, the pf-presenter e2e stamp, Windows session/render, Android feeder/drain/ DisplayTracker (the tracker holds the shared handle, not the client, so the leaked render-callback refcount can't pin the session). - probe: --clock-resync runs a second full handshake mid-connection and asserts a sane, consistent estimate. Live against the local canary host: offsets 8646/2139 ns, disagreement 6 µs, 8/8 rounds — OK. Unit tests cover the round collection, stale-echo rejection, batch restart, min-RTT selection, and the acceptance guard. cargo ndk check green. Remaining manual validation: `sudo date -s "+2 sec"` on a live streaming client → expect one no-op flush, a re-sync, re-armed detector, no IDR pulse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ impl PartialEq for StreamProps {
|
||||
thread_local! {
|
||||
/// Frames + host clock offset, stashed by the mount effect for `on_mounted` (which fires
|
||||
/// later, once the native panel exists).
|
||||
static PENDING: RefCell<Option<(crate::session::FrameRx, i64)>> = const { RefCell::new(None) };
|
||||
static PENDING: RefCell<Option<(crate::session::FrameRx, std::sync::Arc<std::sync::atomic::AtomicI64>)>> = const { RefCell::new(None) };
|
||||
/// The live render thread; stopped + joined by the unmount cleanup (before panel teardown).
|
||||
static RENDER: RefCell<Option<RenderThread>> = const { RefCell::new(None) };
|
||||
}
|
||||
@@ -88,7 +88,7 @@ pub(crate) fn stream_page(props: &StreamProps, cx: &mut RenderCx) -> Element {
|
||||
move || {
|
||||
if let Some((connector, frames, stop)) = shared.handoff.lock().unwrap().take() {
|
||||
let mode = connector.mode();
|
||||
let clock_offset = connector.clock_offset_ns;
|
||||
let clock_offset = connector.clock_offset_shared();
|
||||
connector_ref.set(Some(connector.clone()));
|
||||
PENDING.with(|c| *c.borrow_mut() = Some((frames, clock_offset)));
|
||||
crate::input::install(connector, mode, inhibit, show_stats, stop);
|
||||
|
||||
Reference in New Issue
Block a user