diff --git a/quest/m0/nightly/README.md b/quest/m0/nightly/README.md index fac16fe2dc5..d32fff1b557 100644 --- a/quest/m0/nightly/README.md +++ b/quest/m0/nightly/README.md @@ -33,7 +33,6 @@ Public API: none. Wire: none. - [The software renderer tests both capability paths](/quest/m0/nightly/render-capabilities.md) - the software-renderer lane verifies output selection with and without DMA-BUF import support without requiring hardware the runner lacks - [The browser backend baseline completes on IETF22](/quest/m0/nightly/browser-baseline.md) - the WASM and UniFFI backend baseline completes every supported transport case without an internal error -- [Resolve the paired catalog fanout regressions](/quest/m0/nightly/paired-shared.md) - the reported catalog fanout regressions are attributed and genuine extra costs are fixed with paired measurements - [Probing beside Cubic](/quest/m0/nightly/competing-cubic.md) - the `competing_cubic` probe test stays within its deadline budget - [Gate stops hanging](/quest/m0/nightly/gate-hang.md) - `just gate` finishes instead of hanging to the timeout, and a hang names its module - [moq-quic rustdoc](/quest/m0/nightly/quic-rustdoc.md) - `--all-features` rustdoc for moq-quic builds and a lane checks it diff --git a/quest/m0/nightly/paired-shared.md b/quest/m0/nightly/paired-shared.md deleted file mode 100644 index 5163d0aacaf..00000000000 --- a/quest/m0/nightly/paired-shared.md +++ /dev/null @@ -1,20 +0,0 @@ -# [M] Resolve the paired catalog fanout regressions - -## Goal - -The reported catalog fanout regressions are attributed and genuine extra costs are fixed with paired measurements. - -## Plan - -[Rust shared bench targets](https://github.com/moq-dev/moq/actions/runs/40698329026/job/122851001460) compares -`270395a337` against `22808bbb2f` and flags four catalog-estimate fanout cases: -Hang one track/four readers +11.2%, one track/sixteen readers +20.1%, four -tracks/sixteen readers +12.2%, and MSF references one track/sixteen readers -+16.4%. The `bench-rust-shared` artifact retains paired samples. - -Determine whether the cost is production work, a changed workload, or an -invalid comparison. Fix genuine regressions and measure the publisher and -subscriber axes rather than only one fanout point. Coordinate comparison -calibration with [bench CI](/quest/m1/bench-ci.md). - -Public API: none expected. Wire: none expected. diff --git a/rs/moq-net/src/model/track/miss.rs b/rs/moq-net/src/model/track/miss.rs index 9094b4d0b35..6bc2dc064ec 100644 --- a/rs/moq-net/src/model/track/miss.rs +++ b/rs/moq-net/src/model/track/miss.rs @@ -384,27 +384,37 @@ impl Misses { state.start_sequence, state.drops_revision, ); - let now = state.cache.pool().now_wall(); - let grace = state.arrival_grace(); let ended = state.abort.is_some() || state.is_complete(); - let terms = self.at.is_none_or(|previous| { - previous.1 != at.1 || previous.2 != at.2 || previous.4 != at.4 || previous.5 != at.5 || at.0 < previous.0 - }) || self.budget != Some(drift.budget) - || self.grace != Some(grace) - || self.ended != ended; let ledger = state.arrivals.changes(); let drops = &state.drops_changes; - if !terms - && self.at == Some(at) + // Every reader probes on every poll, mostly with nothing changed since the last, so + // that answer costs neither the subscriptions' lock nor a clock read. + let unchanged = self.at == Some(at) + && self.budget == Some(drift.budget) + && self.ended == ended && self.ledger_revision == ledger.revision() - && self.drops_revision == drops.revision() - && self.deferred.deadline().is_none_or(|at| at > now) - { - if let Some(at) = self.deferred.deadline() { - state.cache.watch_wall(at, waiter); - } + && self.drops_revision == drops.revision(); + // The grace only times deferred gaps, so with none a new one has nothing to move. + // It is judged once something else changes. + if unchanged && self.deferred.runs.is_empty() { return asks; } + let grace = state.arrival_grace(); + let unchanged = unchanged && self.grace == Some(grace); + let deadline = self.deferred.deadline(); + if unchanged && deadline.is_none() { + return asks; + } + let now = state.cache.pool().now_wall(); + if unchanged && let Some(at) = deadline.filter(|at| *at > now) { + state.cache.watch_wall(at, waiter); + return asks; + } + let terms = self.at.is_none_or(|previous| { + previous.1 != at.1 || previous.2 != at.2 || previous.4 != at.4 || previous.5 != at.5 || at.0 < previous.0 + }) || self.budget != Some(drift.budget) + || self.grace != Some(grace) + || self.ended != ended; let low = state.live_floor.unwrap_or(0).max(key.0); let top = newest.min(u64::MAX - 1); let mut candidates = RangeSet::new(); @@ -1080,6 +1090,78 @@ mod tests { assert!(misses.deferred.runs.is_empty()); } + /// A grace change with nothing deferred has no deadline to move, so an unchanged probe + /// skips it; the next gap is still timed by the new grace. + #[moq_clock::test] + async fn a_grace_change_with_nothing_deferred_times_the_next_gap() { + let broadcast = crate::broadcast::Info::default().produce(); + let track = broadcast.create_track("grace", crate::track::Info::default()).unwrap(); + let budget = Duration::from_secs(5); + let _reader = track.subscribe(Subscription::default().with_max_delay(budget)); + for sequence in 0..3 { + track + .create_group(crate::group::Info { sequence }) + .unwrap() + .finish() + .unwrap(); + } + let mut misses = Misses::default(); + let mut given_up = Vec::new(); + let waiter = kio::Waiter::noop(); + let state = track.state.read(); + let drift = Drift { + budget, + bounded: false, + edge: state.drift_edge(&Servable::ALL), + }; + let full = |span| [span].into_iter().collect(); + assert!( + misses + .judge(&state, (0, 0, None), None, full, &drift, &mut given_up, &waiter) + .is_empty() + ); + assert!(misses.deferred.runs.is_empty(), "a contiguous feed defers nothing"); + drop(state); + + let wider = Duration::from_secs(20); + let _wide = track.subscribe(Subscription::default().with_max_delay(wider)); + let state = track.state.read(); + assert_eq!(state.arrival_grace(), wider); + assert!( + misses + .judge( + &state, + (0, 0, None), + None, + |_| panic!("nothing deferred: the new grace waits for a change"), + &drift, + &mut given_up, + &waiter + ) + .is_empty() + ); + drop(state); + + track + .create_group(crate::group::Info { sequence: 4 }) + .unwrap() + .finish() + .unwrap(); + let state = track.state.read(); + let opened = state.cache.pool().now_wall(); + assert!( + misses + .judge(&state, (0, 0, None), None, full, &drift, &mut given_up, &waiter) + .is_empty() + ); + assert_eq!(misses.deferred.overlapping(0..5).collect::>(), vec![3..4]); + assert_eq!( + misses.deferred.deadline(), + Some(opened + wider), + "timed by the new grace" + ); + } + #[test] fn deferred_splits_and_replacements_retire_the_old_age_entries() { let mut deferred = Deferred::default();