diff --git a/quest/m0/nightly/README.md b/quest/m0/nightly/README.md index 9b5b70d8b8d..f0dd5af4e5f 100644 --- a/quest/m0/nightly/README.md +++ b/quest/m0/nightly/README.md @@ -40,4 +40,4 @@ Public API: none. Wire: none. - [The relay's startup fits its stack under all features](/quest/m0/nightly/relay-stack.md) - `debug_relay_load_and_run_fit_windows_stack` passes under `--all-features`, fixed at the feature that grew the future - [moq-net's ingest fuzz target finds no stall](/quest/m0/nightly/ingest-fuzz.md) - the 13-byte stream that stalls ingest is fixed at its cause and kept as a regression test - [Nightly's shipping-size check passes](/quest/m0/nightly/shipping-size.md) - js/net, js/watch, and moq-relay growth over 5% is traced to its commits, then shrunk or accepted -- [Nightly's Miri job stays inside its time budget](/quest/m0/nightly/miri.md) - Miri gets `rust-src`, then finishes inside its 60-minute job and 15-minute-per-test budgets +- [Nightly's Miri job stays inside its time budget](/quest/m0/nightly/miri.md) - both Miri passes go green, with ARM timings inside the 60-minute job and 15-minute-per-test budgets diff --git a/quest/m0/nightly/miri.md b/quest/m0/nightly/miri.md index 2a2405fd837..b634711efee 100644 --- a/quest/m0/nightly/miri.md +++ b/quest/m0/nightly/miri.md @@ -1,9 +1,9 @@ -# [S] Nightly's Miri job stays inside its time budget +# [XS] Nightly's Miri job stays inside its time budget ## Goal -Nightly's `Tests (rs miri)` entry finishes well inside the job's 60-minute -timeout on the 4-core ARM runner, and no single Miri test comes near +Nightly's `Tests (rs miri)` entry passes and finishes well inside the job's +60-minute timeout on the 4-core ARM runner, and no single Miri test comes near nextest's 15-minute `miri` profile cap, with headroom recorded from a real Nightly run. @@ -11,45 +11,47 @@ Nightly run. Decided 2026-10-31 (user, /quest-plan of the follow-ups from the 2026-10-31 /quest-complete run), from [moq-dev/moq#7315](https://github.com/moq-dev/moq/pull/7315) (merged), which finished the Miri for in-memory unsafe quest. -Milestone m2 (user: "use recommended for any CI/flakes"). Decided 2026-11-03 (maintainer, /quest-plan of the follow-ups from [moq-dev/moq#7667](https://github.com/moq-dev/moq/pull/7667)): -moved from m2 under [Nightly passes on main](/quest/m0/nightly/README.md), -since the missing `rust-src` keeps the m0 Nightly red. The scheduled run of -2026-11-03 ([run 41656613559](https://github.com/moq-dev/moq/actions/runs/41656613559/job/125988511062)) -still fails the same way. - -The setup fix in [moq-dev/moq#7812](https://github.com/moq-dev/moq/pull/7812) -installs `miri, rust-src` together. The scheduled failure above is before -any test, so there is still no scheduled ARM timing evidence. Keep this -quest until a scheduled run containing that fix records the job duration, -strict and epoch pass durations, slowest test, and headroom against both -budgets. Do not dispatch Nightly by hand to obtain it. - -In the same log, confirm moq-net's `model::frame::` tests ran (the strict -pass selects them in `sh/rs/miri.sh`), including the lock-free single-delivery -path [moq-dev/moq#7508](https://github.com/moq-dev/moq/pull/7508) added; extend -the selection if any are missed. (Folded in from moq-dev/moq#7748's -`miri-frame.md`, 2026-11-04, whose `rust-src` half #7812 landed.) - -Act on those timings before optimizing. Earlier local measurements on 24 -cores took 11.5 min wall: strict pass 118 tests in 109 s, epoch pass 94 in -307 s. These are desktop measurements, not proof of the 4-core ARM budget. - -- `a_group_has_no_frame_cap` (`rs/moq-net/src/model/group.rs`) took 307 s - alone, writing 10,000 one-byte frames. If scheduled timings warrant a - smaller Miri workload, it must still exceed the former 8192-frame cap. -- `rs/moq-net/src/coding/reader.rs` builds its payload byte by byte - (`(0..SIZE).map(|i| (i % 251) as u8)`); consider a cheaper fill only if - Miri starts running that module and measurements justify it. -- If the job nears 60 minutes, split the strict and epoch passes into - separate matrix entries rather than raising the timeout. Preserve - nextest's 15-minute per-test cap. +moved under [Nightly passes on main](/quest/m0/nightly/README.md), since the +Miri job keeps the m0 Nightly red. Do not dispatch Nightly by hand for the +timings. + +The first scheduled run with `rust-src` ([moq-dev/moq#7812](https://github.com/moq-dev/moq/pull/7812)), +[run 41852517544](https://github.com/moq-dev/moq/actions/runs/41852517544/job/126652295876) +on 2026-11-04, ran only the strict pass: two kio `task::` tests failed, and +`sh/rs/miri.sh` stops before the epoch pass when one fails. +[moq-dev/moq#7895](https://github.com/moq-dev/moq/pull/7895) fixed them and +shrank the slowest Miri workloads. What that run recorded: + +- Job 19 min 22 s; tests start 6 min in (toolchain, cache, a 2 min 20 s build). +- Strict pass: 228 tests in 800 s (13.3 min). Slowest + `model::frame::tests::a_delivery_attaching_mid_write_publishes_one_payload` + 372 s (SLOW at 300 s; about half was its payload fill, now cheap), then kio's + `clock::sim::tests::a_large_task_set_counts_each_child_alone` 212 s (now one + child past the 4096 Ready limit instead of 10,000: 56 s locally, from 280 s) + and `task::tests::concurrent_wakes_are_never_lost` 188 s, against the 900 s cap. +- All 17 `model::frame::` tests ran and passed, including the two + single-delivery tests from [moq-dev/moq#7508](https://github.com/moq-dev/moq/pull/7508); + the selection needs no change. + +The epoch pass has no ARM timing yet. On a loaded 24-core desktop it took 370 s +for 97 tests, with `model::group::test::a_group_has_no_frame_cap` (10,000 +writes at about 30 ms each under Miri) past SLOW; #7895 ignores that test under +Miri, since it still runs natively in the merge gate. + +Remaining: read the next scheduled run's log and record the job duration, both +pass durations, the slowest test, and headroom against both budgets. If the job +nears 60 minutes, split the strict and epoch passes into separate matrix +entries rather than raising the timeout, and keep nextest's 15-minute per-test +cap. A test past SLOW gets a smaller Miri workload. Public API: none. Wire: none. ## Waiting -- moq-dev/moq: After the setup fix lands, the next scheduled Nightly on - `main` must run `Tests (rs miri)` on `ubuntu-24.04-arm`. Read its log and - record the timings and headroom above; local runs do not clear this wait. +- moq-dev/moq: After [moq-dev/moq#7895](https://github.com/moq-dev/moq/pull/7895) + lands, the next scheduled Nightly on `main` must pass `Tests (rs miri)` on + `ubuntu-24.04-arm` with both passes. Read its log + (`gh run view --repo moq-dev/moq --log`) and record the timings above; + local runs do not clear this wait. diff --git a/rs/kio/src/clock/sim.rs b/rs/kio/src/clock/sim.rs index 4cdb20b036d..9e5097f5fca 100644 --- a/rs/kio/src/clock/sim.rs +++ b/rs/kio/src/clock/sim.rs @@ -744,11 +744,12 @@ mod tests { run(async {}); } + /// One child more than the limit, so counting the set as one poll would trip it. #[crate::test] async fn a_large_task_set_counts_each_child_alone() { let producer = crate::Producer::new(()); let mut tasks = crate::Tasks::new(); - for _ in 0..10_000 { + for _ in 0..=crate::coop::READY_LIMIT { let consumer = producer.consume(); tasks.push(move |waiter: &crate::Waiter| consumer.poll(waiter, |_| Poll::Ready(())).map(drop)); } diff --git a/rs/kio/src/coop.rs b/rs/kio/src/coop.rs index 3b04ac7f69b..f8449aaa01c 100644 --- a/rs/kio/src/coop.rs +++ b/rs/kio/src/coop.rs @@ -9,6 +9,10 @@ use std::task::Poll; use crate::Waiter; +/// Ready kio polls one task poll may make under the simulator before it panics as a spin. +#[cfg(feature = "sim")] +pub(crate) const READY_LIMIT: usize = 4096; + #[cfg(feature = "sim")] thread_local! { static READY: std::cell::Cell> = const { std::cell::Cell::new(None) }; @@ -47,7 +51,7 @@ pub(crate) fn ready(value: T) -> Poll { let count = READY.get().map(|count| count + 1); READY.set(count); assert!( - count.is_none_or(|count| count <= 4096), + count.is_none_or(|count| count <= READY_LIMIT), "kio Ready poll limit exceeded at {}; add a coop::Budget to the loop", std::panic::Location::caller(), ); diff --git a/rs/kio/src/task.rs b/rs/kio/src/task.rs index 373cf6e239b..838da944839 100644 --- a/rs/kio/src/task.rs +++ b/rs/kio/src/task.rs @@ -53,7 +53,7 @@ use std::{ Arc, Mutex, atomic::{AtomicBool, AtomicU64, Ordering}, }, - task::{Context, Poll, RawWakerVTable, Wake, Waker}, + task::{Context, Poll, Wake, Waker}, }; use crate::waiter::{Park, Waiter}; @@ -169,11 +169,17 @@ std::thread_local! { static POLLING: Polling = const { Polling { open: RefCell::new(Vec::new()), held: RefCell::new(Vec::new()) } }; } -/// A waker's identity: what [`Waker::will_wake`] compares. -type Identity = (*const (), *const RawWakerVTable); +/// A waker's identity: its data pointer, the task it wakes. +/// +/// Not the vtable pointer [`Waker::will_wake`] also compares: a vtable is a promoted +/// constant with no unique address, so a clone of an `Arc`-backed waker may carry a +/// different one than the original (Miri varies it on purpose), and a batch would +/// miss its own task's wakes. A foreign waker sharing a batch's data pointer only +/// has its wake held until that batch returns. +type Identity = *const (); fn identity(waker: &Waker) -> Identity { - (waker.data(), waker.vtable()) + waker.data() } /// The [`Waiter::batch`]es running on this thread, and the wakes held for them. diff --git a/rs/moq-net/src/model/frame.rs b/rs/moq-net/src/model/frame.rs index e5a2c09a10d..8e25353d364 100644 --- a/rs/moq-net/src/model/frame.rs +++ b/rs/moq-net/src/model/frame.rs @@ -2020,7 +2020,10 @@ mod tests { #[test] fn a_delivery_attaching_mid_write_publishes_one_payload() { const SIZE: usize = SEGMENT + 4093; - let payload: Vec<_> = (0..SIZE).map(|i| (i % 251) as u8).collect(); + // Repeating one period spares Miri a fill it would step through byte by byte, + // half this test's time there. + let mut payload = (0..251).collect::>().repeat(SIZE.div_ceil(251)); + payload.truncate(SIZE); for grow in [false, true] { let budget = Budget::new(if grow { 0 } else { SEGMENT }); let (a, active) = FrameBuf::admitted(SIZE, &budget); diff --git a/rs/moq-net/src/model/group.rs b/rs/moq-net/src/model/group.rs index f5bfff14221..bf341b3a89c 100644 --- a/rs/moq-net/src/model/group.rs +++ b/rs/moq-net/src/model/group.rs @@ -5310,6 +5310,7 @@ mod test { /// A frame count past the old 8192-frame cap is held whole while it fits the tail. #[test] + #[cfg_attr(miri, ignore = "each write takes Miri ~30 ms, so 10,000 run past its SLOW mark")] fn a_group_has_no_frame_cap() { let mut producer = Info { sequence: 0 }.produce(); for _ in 0..10_000 {