diff --git a/.github/homebrew/Formula/moq-gst.rb.tmpl b/.github/homebrew/Formula/moq-gst.rb.tmpl index 59987c954b8..d12c610532e 100644 --- a/.github/homebrew/Formula/moq-gst.rb.tmpl +++ b/.github/homebrew/Formula/moq-gst.rb.tmpl @@ -42,13 +42,13 @@ class MoqGst < Formula EOS end + # gst-inspect-1.0 hangs at startup inside the test sandbox on a headless Mac, even for + # --version, so check the plugin links GStreamer rather than loading it. The release + # build loads it into GStreamer outside the sandbox. test do - plugin = OS.mac? ? "libgstmoq.dylib" : "libgstmoq.so" - assert_predicate lib/"gstreamer-1.0"/plugin, :exist? - ENV["GST_REGISTRY"] = (testpath/"registry.bin").to_s - ENV["GST_PLUGIN_PATH_1_0"] = (lib/"gstreamer-1.0").to_s - ENV["GST_PLUGIN_SYSTEM_PATH_1_0"] = "" - output = shell_output("#{Formula["gstreamer"].opt_bin}/gst-inspect-1.0 moq") - assert_match (lib/"gstreamer-1.0"/plugin).to_s, output + plugin = lib/"gstreamer-1.0"/(OS.mac? ? "libgstmoq.dylib" : "libgstmoq.so") + assert_predicate plugin, :exist? + linked = plugin.dynamically_linked_libraries.map { |library| File.basename(library) } + assert linked.any? { |library| library.start_with?("libgstreamer-1.0.") }, linked.join(", ") end end diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c1d2303e6b4..db6990dae70 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -52,13 +52,13 @@ jobs: # nextest's ignored benchmark tests and cargo's bench targets share few builds. nightly: name: Nightly (${{ matrix.name }}) - runs-on: ubuntu-24.04-arm + runs-on: ${{ matrix.runner || 'ubuntu-24.04-arm' }} timeout-minutes: 60 strategy: fail-fast: false matrix: - # Unset keys default to the plain dev shell, the Rust cache, no perf - # sampling, no Chromium, and nextest's default profile. + # Unset keys default to the ARM runner, the plain dev shell, the Rust + # cache, no perf sampling, no Chromium, and nextest's default profile. include: - name: Features recipe: rs features @@ -72,9 +72,12 @@ jobs: - name: Relay shard sweep recipe: bench-shard # Smoke-tests the capture path: exact off-CPU waits, resolved worker stacks and CPU. + # x86: on the SVE-capable ARM runner, perf's DWARF unwinding asks for the SVE VG + # register, which the kernel refuses for the software context-switches event. - name: Relay lock-wait profile recipe: bench-lock fanout-heavy 4 perf: true + runner: ubuntu-24.04 - name: Rust benchmark tests recipe: gh nightly rust-test - name: Rust bench targets @@ -228,6 +231,10 @@ jobs: run: nix develop --command just test media --media-engines ${{ matrix.engine }} env: MOQ_TEST_RUNS: ${{ runner.temp }}/moq-test-runs + # Playwright checks a browser's libraries with the `ldd` on PATH, which in the + # dev shell is Nix glibc's and can't see the ones `--with-deps` installed, so + # it refuses a Firefox or WebKit that launches fine. + PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS: 1 - name: Preserve measurements and traces if: ${{ failure() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/Cargo.lock b/Cargo.lock index f0d7965c7d9..118cde752b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1771,7 +1771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c54e03a951783e8b327515db3f2a2fd0e3bed362a96b066f341ce66ed49b4ead" dependencies = [ "data-encoding", - "syn 3.0.6", + "syn 1.0.109", ] [[package]] @@ -7654,9 +7654,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "aws-lc-rs", "log", @@ -8906,7 +8906,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.5", "windows-sys 0.61.2", diff --git a/bench/README.md b/bench/README.md index a6424ad7ba4..150cc55562d 100644 --- a/bench/README.md +++ b/bench/README.md @@ -116,7 +116,9 @@ waited, in flamegraph folded format. Linux only. A switch-out is sampled in kernel context, and 16 workers need a larger ring buffer than the unprivileged locked-memory limit allows, so the recipe refuses before building until `perf_event_paranoid` is -1. A recording -that still drops events is refused rather than reported. +that still drops events is refused rather than reported. On an arm64 host with +SVE, perf's DWARF unwinding requests the SVE `VG` register, which the kernel +refuses for a software event, so the recording fails to open; profile on x86. ```bash sudo sysctl -w kernel.perf_event_paranoid=-1 diff --git a/deny.toml b/deny.toml index d33efb88640..58d6697962b 100644 --- a/deny.toml +++ b/deny.toml @@ -17,9 +17,6 @@ ignore = [ # when rustcrypto/RSA publishes a constant-time fix. "RUSTSEC-2023-0071", - # foundations (Cloudflare) pins serde_yaml 0.8 which uses yaml-rust 0.4. - "RUSTSEC-2024-0320", - # gstreamer 0.23 pulls paste (proc-macro). gstreamer 0.25 drops it but # needs Rust 1.92, above the 1.91 workspace MSRV. Build-time only. "RUSTSEC-2024-0436", @@ -69,3 +66,5 @@ allow-wildcard-paths = true unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# uniffi-bindgen's generator-only UniFFI fork. Unpublished, so it never ships in a crate. +allow-git = ["https://github.com/kixelated/uniffi-rs"] diff --git a/js/net/bench/forward.ts b/js/net/bench/forward.ts index 49c73788bdc..a7d06f90905 100644 --- a/js/net/bench/forward.ts +++ b/js/net/bench/forward.ts @@ -3,6 +3,7 @@ * a watch coming and going over held watches and attached sessions. */ +import { Once } from "@moq/signals"; import { announcedView } from "../src/announced.test-util"; import * as Announce from "../src/announced.ts"; import { Producer as BroadcastProducer } from "../src/broadcast.ts"; @@ -28,7 +29,7 @@ class FakeSession { die!: () => void; constructor() { - registerWire(this, { consume: () => new BroadcastProducer().consume() }); + registerWire(this, { consume: () => new BroadcastProducer().consume(), goaway: new Once() }); this.closed = new Promise((resolve) => { this.die = () => resolve(null); }); diff --git a/js/net/bench/lite-varint.ts b/js/net/bench/lite-varint.ts index 148fb937ff7..4f335e79355 100644 --- a/js/net/bench/lite-varint.ts +++ b/js/net/bench/lite-varint.ts @@ -79,7 +79,7 @@ const subscribe: Sample = { const datagram: Sample = { name: "Datagram", encode: (w, version) => w.write(new Datagram(3n, 1_234, 1_234_567_890, new Uint8Array()).encode(version)), - decode: (r, version) => r.readAll().then((data) => Datagram.decode(data, version)), + decode: (r, version) => r.readAll().then((data) => Datagram.decode(data, version, () => true)), }; const hop = randomHop(); diff --git a/js/net/bench/tsconfig.json b/js/net/bench/tsconfig.json index c2957a1cf69..127a2e04490 100644 --- a/js/net/bench/tsconfig.json +++ b/js/net/bench/tsconfig.json @@ -4,13 +4,5 @@ "rootDir": "..", "noEmit": true }, - "include": [ - "requests.ts", - "serve-browser.ts", - "serve-browser-entry.ts", - "serve-browser.test.ts", - "qmux.ts", - "qmux-eager.ts", - "qmux-lazy.ts" - ] + "include": ["*.ts"] } diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index dd99831f2cb..54451fb5dda 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -67,5 +67,4 @@ The historical samples locate candidates; they are not controlled benchmarks. - [Require platform gates](/quest/m0/ci/platform-queue.md) - activate the consolidated platform checks in the main and release rulesets after the workflow lands - [mbx on R2](/quest/m0/ci/mbx-r2.md) - Rust builds on every OS read an R2 cache that only `main` writes, escaping the 10 GB Actions cache - [Nix cache](/quest/m0/ci/nix-cache.md) - `nix.moq.dev` on R2 replaces Cachix for CI and releases -- [Nightly workloads pass](/quest/m0/ci/nightly-green.md) - the Nightly failures found on 2026-10-16 are fixed - [First shared FFI release](/quest/m0/ci/nightly-ffi-reuse.md) - condition: the first `moq-ffi-v*` tag that runs `Release FFI`, and a `Shipping sizes` run; verify the wrappers and size keys diff --git a/quest/m0/ci/nightly-ffi-reuse.md b/quest/m0/ci/nightly-ffi-reuse.md index f8d6c0aa079..a2c990f8427 100644 --- a/quest/m0/ci/nightly-ffi-reuse.md +++ b/quest/m0/ci/nightly-ffi-reuse.md @@ -39,4 +39,3 @@ From the first `Shipping sizes` run: ## Related - [Release builds link again](/quest/m0/release-link.md) - unblocks `Shipping sizes` -- [Nightly workloads pass](/quest/m0/ci/nightly-green.md) - the Nightly failures this check found diff --git a/quest/m0/ci/nightly-green.md b/quest/m0/ci/nightly-green.md deleted file mode 100644 index d2c994cecd4..00000000000 --- a/quest/m0/ci/nightly-green.md +++ /dev/null @@ -1,41 +0,0 @@ -# [S] Nightly workloads pass - -## Goal - -Every Nightly workload that failed on 2026-10-16 (run 38897397428, commit -b4eab0db1) passes on the next scheduled run, except the Linux release builds, -which [Release builds link again](/quest/m0/release-link.md) owns. - -## Plan - -The first four still fail on `main` as of 2aa7a95d7, by source inspection -or a local `cargo deny`. The last two depend on the hosted runner and were -not reproduced locally. - -- **Audit**: `cargo deny` fails on four rustls advisories - (RUSTSEC-2026-0346 to 0349, fixed in rustls 0.23.46; `main` locks 0.23.45) - and on the `kixelated/uniffi-rs` git source, which `deny.toml` doesn't - allow. Bump rustls, and allow that one git source. -- **Features**: `moq-hls` tests call `build_inits`, which is - `#[cfg(feature = "server")]`, so `--no-default-features --all-targets` - fails to compile. Gate it the way its siblings are - (`cfg_attr(not(feature = "server"), allow(dead_code))`) or gate the tests. -- **JS benchmarks**: `js/net/bench/lite-varint.ts` calls `Datagram.decode` - without the `timed` callback it now takes. -- **Browser backend baseline**: `test/justfile`'s `wasm-baseline` defaults - `MOQ_TEST_RUNS` to a path relative to `test/`, and `test/wasm/run.sh` - changes into `test/wasm` before the relay writes its log there. Make the - default absolute; the Nightly artifact path also expects `target/test`. -- **Relay lock-wait profile**: `perf record -e context-switches` fails on the - hosted ARM runner ("PMU Hardware doesn't support sampling"). Decide whether - the smoke test uses an event the runner supports or leaves hosted Nightly. -- **moq-gst macOS formula**: `brew test` times out in `gst-inspect-1.0 moq` - after LaunchServices errors on the headless runner. Reproduce and find - whether the plugin or plugin scanning hangs. - -New workloads since that run (relay shard sweep, media engines, `rs send-depth`) -have not run yet; triage them from the next Nightly too. - -## Related - -- [First shared FFI release](/quest/m0/ci/nightly-ffi-reuse.md) - the check that found these diff --git a/rs/moq-hls/src/export/mod.rs b/rs/moq-hls/src/export/mod.rs index b2b3c63e35a..006c5cf4ec7 100644 --- a/rs/moq-hls/src/export/mod.rs +++ b/rs/moq-hls/src/export/mod.rs @@ -327,7 +327,7 @@ impl Broadcaster { /// Build every rendition's init segment, so the manifest can name each by its hash. A /// rendition whose init can't be built yet is left out of the render. - #[cfg(feature = "server")] + #[cfg_attr(not(feature = "server"), allow(dead_code))] pub(crate) async fn build_inits(&self) { for rendition in self.renditions.snapshot() { if let Err(err) = rendition.init().await { diff --git a/rs/moq-mux/src/catalog/consumer.rs b/rs/moq-mux/src/catalog/consumer.rs index 528c7878bd8..f2109f029a4 100644 --- a/rs/moq-mux/src/catalog/consumer.rs +++ b/rs/moq-mux/src/catalog/consumer.rs @@ -28,6 +28,9 @@ pub struct Consumer { inner: State, } +// One per catalog subscription, and the live variant is the working state, so boxing +// it would only add an allocation. +#[allow(clippy::large_enum_variant)] enum State { Hang(super::hang::Consumer), Msf(super::msf::Consumer), diff --git a/rs/moq-uring/src/transport.rs b/rs/moq-uring/src/transport.rs index e9142e658dd..b77abb15754 100644 --- a/rs/moq-uring/src/transport.rs +++ b/rs/moq-uring/src/transport.rs @@ -1,10 +1,13 @@ //! Adapt backend poll sessions to moq-net without changing thread affinity. mod adapter; pub use adapter::{Error, RecvStream, SendStream, Session}; +#[cfg(feature = "quic")] use adapter::{Grouped, GroupedSend}; +#[cfg(feature = "quic")] use crate::quic; +#[cfg(feature = "quic")] impl Grouped for quic::web::Session { fn send_group(&mut self, priority: i64) -> Option { Some(quic::web::Session::send_group(self, priority)) @@ -20,6 +23,7 @@ impl Grouped for quic::web::Session { } } +#[cfg(feature = "quic")] impl GroupedSend for quic::web::SendStream { type Group = quic::SendGroup; @@ -32,6 +36,7 @@ impl GroupedSend for quic::web::SendStream { } } +#[cfg(feature = "quic")] impl moq_net::transport::poll::SendGroup for quic::SendGroup { fn set_priority(&mut self, priority: i64) { quic::SendGroup::set_priority(self, priority) diff --git a/rs/moq-uring/src/udp.rs b/rs/moq-uring/src/udp.rs index 6f0fe55a74b..149852f31d4 100644 --- a/rs/moq-uring/src/udp.rs +++ b/rs/moq-uring/src/udp.rs @@ -413,6 +413,7 @@ pub(crate) struct SockShared { /// The worker, as the I/O built on this socket carries it. owner: Owner, /// This socket's slot in a steered reuseport group, if it is in one. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] shard: Option, /// The worker's counters, held directly rather than reached through /// `owner`, so counting a datagram is not a `Weak::upgrade`. @@ -665,11 +666,13 @@ impl Socket { } /// The worker driving this socket, which everything built on it runs on. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] pub(crate) fn owner(&self) -> Owner { self.shared.owner.clone() } /// This socket's slot in a steered reuseport group, if it is in one. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] pub(crate) fn shard(&self) -> Option { self.shared.shard } diff --git a/rs/moq-uring/src/worker.rs b/rs/moq-uring/src/worker.rs index 6755e083e48..b3e025d6a76 100644 --- a/rs/moq-uring/src/worker.rs +++ b/rs/moq-uring/src/worker.rs @@ -592,6 +592,7 @@ pub(crate) struct Owner { /// Held directly: a timer on a dropped worker still has to exist, since /// the driver that owns it is torn down by the same drop that would need /// it. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] timers: Rc>, } @@ -615,6 +616,7 @@ impl Owner { } /// Run a `!Send` future on the worker, or drop it if the worker is gone. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] pub fn spawn(&self, future: impl Future + 'static) { if let Some(handle) = self.handle() { handle.spawn(future); @@ -622,11 +624,13 @@ impl Owner { } /// A disarmed timer on the worker. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] pub fn timer(&self) -> crate::Timer { crate::Timer::from_heap(self.timers.clone()) } /// A timer that expires after `duration`. + #[cfg_attr(not(feature = "quic"), allow(dead_code))] pub fn after(&self, duration: Duration) -> crate::Timer { let mut timer = self.timer(); timer.set(Instant::now().checked_add(duration)); diff --git a/test/lib/harness.sh b/test/lib/harness.sh index de6c3e729ac..40621e70006 100644 --- a/test/lib/harness.sh +++ b/test/lib/harness.sh @@ -81,6 +81,8 @@ harness_begin() { root="${root%/}" [[ -n "${MOQ_TEST_RUNS:-}" ]] || root="$root/moq-test-$(id -u)" mkdir -p "$root" + # Harnesses change directory after this, so a relative root would move under them. + root=$(CDPATH='' cd -- "$root" && pwd) HARNESS_RUN=$(mktemp -d "$root/$name-XXXXXXXX") # A run directory holds relay configs and generated keys, so keep it to the # owner even where the temp root itself is world-readable. diff --git a/test/lib/harness.test.sh b/test/lib/harness.test.sh index e1bb411478d..b934a3f97a6 100755 --- a/test/lib/harness.test.sh +++ b/test/lib/harness.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Independent shells must reserve different ports even with private temp roots. +# Independent shells must reserve different ports even with private temp roots, +# and a relative MOQ_TEST_RUNS must survive a harness changing directory. set -euo pipefail lib=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) @@ -49,4 +50,13 @@ MOQ_TEST_PORTS="$scratch/ports-link" bash -euo pipefail -c ' [[ "$status" == 2 ]] fi ' bash "$lib" +# A relative root must still name the same directory after the harness changes directory. +(cd "$scratch" && MOQ_TEST_RUNS=relative bash -euo pipefail -c ' + source "$1/harness.sh" + harness_begin relative >&2 + [[ "$HARNESS_RUN" == "$2"/relative/relative-* ]] || { + echo "FAIL: relative MOQ_TEST_RUNS gave run directory $HARNESS_RUN" >&2 + exit 1 + } +' bash "$lib" "$(cd "$scratch" && pwd)") echo "cross-shell port reservations passed"