Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ jobs:
nix develop --command bun install --frozen-lockfile
nix develop --command bun js/net/bench/broadcasts.ts

# Runs each moq-net Criterion routine once, so a bench that panics (an
# unparsable version, a shape that deadlocks) fails here instead of on the
# next manual run. Timing is not compared; nextest never runs a
# `harness = false` target. `fuzz` enables the announce bench, which the
# `'*'` glob otherwise refuses.
- name: moq-net benchmark smoke
if: ${{ !cancelled() }}
run: nix develop --command cargo bench --locked -p moq-net --features fuzz --bench '*' -- --test

- name: Audit
if: ${{ !cancelled() }}
run: nix develop --command just rs audit
Expand Down
2 changes: 1 addition & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Watch worker](/quest/m1/watch-worker.md) - watch playback runs in a worker onto an OffscreenCanvas, so main-thread jank never stalls video or audio
- [Closure counters](/quest/m1/closure-counters.md) - a departed node's return never regresses the closure counters a consumer already saw
- [RTMP interleaving](/quest/m1/rtmp-interleaving.md) - isolate partial messages before optimizing assembly copies
- [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - with the default pool, relay memory plateaus at the expiry window on moq-transport as on moq-lite
- [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - with the default pool, relay memory plateaus at the expiry window on every version
- [Relay memory](/quest/m1/relay-memory.md) - remeasure what an announcement costs after prefix routes
- [PoP skipping](/quest/m1/pop-skipping/README.md) - short cold paths for unpopular broadcasts without losing warm backhaul dedup
- [Route cost in the JS origin](/quest/m1/route-cost.md) - the browser origin ranks routes by cost and hops like Rust instead of newest-first
Expand Down
24 changes: 14 additions & 10 deletions quest/m1/cache-expiry-growth.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
# [S] Cached groups expire on the IETF path
# [S] Cached groups expire under the default pool

## Goal

With the default cache pool, a relay's memory plateaus once groups start
reaching the expiry window, on moq-transport as on moq-lite, or the cause of
continued growth is found and fixed.
reaching the expiry window, on every version, or the cause of continued
growth is found and fixed.

## Plan

In `session_delivery_broadcasts` with 4096 announced broadcasts and the
default unbounded pool (30 s expiry), IETF RSS reached 1.5 GB at 10 s, 3.5 GB at
30 s, and 4.9 GB at 50 s, still climbing past the expiry window. Lite
reached about 0.36 GB by 50 s. With a 16 MiB bounded pool both stay flat, so
the retained bytes are cached groups, not a leak elsewhere.
default unbounded pool (30 s expiry), RSS keeps climbing past the expiry
window on both wires (2026-09-25, Apple M4, sampled every 10 s): IETF
1.9 GB at 30 s and 4.4 GB at 120 s, lite-06 2.3 GB at 30 s and 3.5 GB at
120 s, both oscillating by a gigabyte between samples. With a 16 MiB bounded
pool both stay flat (0.5 GB IETF, 0.4 GB lite), so the retained bytes are
cached groups, not a leak elsewhere. An earlier run saw growth on IETF only,
but lite was then 30x slower per round, so it wrote far fewer groups; compare
by groups written, not by wall time.

Reproduce with a focused test first. Unexpired groups at higher throughput,
expiry not running on a path IETF uses, or groups held outside the pool's
accounting would each explain it. Fix what is actually wrong. Consider
whether an unbounded default is the right default for an origin at all.
expiry not running on some path, or groups held outside the pool's accounting
would each explain it. Fix what is actually wrong. Consider whether an
unbounded default is the right default for an origin at all.

## Related

Expand Down
1 change: 0 additions & 1 deletion quest/m1/perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ row per io_uring worker.

- [Open contract](/quest/m1/perf/uring-open-contract.md) - plan concurrent WebTransport opening and cancellation

- [Lite route rescan](/quest/m1/perf/lite-route-rescan.md) - a moq-lite session's per-group cost stops growing with the routes its peer announced
- [Announce replay](/quest/m1/perf/announce-replay.md) - the initial announce set replays in linear time, so joins don't slow with the route count
- [Group cost](/quest/m1/perf/group-cost.md) - count and cut the allocations and time spent relaying one small group to one viewer
- [One enter per turn](/quest/m1/perf/uring-one-enter.md) - a parking turn pays one io_uring_enter, submits flush deferred completions, and SQEs per enter is a counter
Expand Down
12 changes: 7 additions & 5 deletions quest/m1/perf/announce-replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ The moq-lite publisher's initial replay (`AnnounceRun::init` in
for every route it drains: `initial.retain` on Lite05+, `init.contains` on
the Lite01/02 init. That is quadratic in the replay size.

Measured with `session_join_broadcasts` (2026-09, one relay): lite join takes
199 µs with 1 announced broadcast, 517 µs with 64, and 8.9 ms with 1024,
about 8.7 µs per route at the top. IETF is 7.2 ms at 1024 with no quadratic
scan; its profile is SipHash hashing and `Path` comparison, so check whether
a faster hasher for path-keyed maps pays off on both.
Measured with `session_join_broadcasts` (2026-09-25, one relay, Apple M4):
lite-06 join takes 128 µs with 1 announced broadcast, 317 µs with 64, and
5.0 ms with 1024, about 4.9 µs per route at the top; lite-07-wip matches.
IETF is 95 µs, 311 µs, and 4.05 ms, with no quadratic scan, so the scan is
the ~1 ms gap at 1024. An earlier Linux profile of the IETF path was SipHash
hashing and `Path` comparison, so check whether a faster hasher for
path-keyed maps pays off on both.

Keep the replay's order and its last-update-wins semantics.
9 changes: 5 additions & 4 deletions quest/m1/perf/group-cost.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ change to what is delivered.

## Plan

`session_delivery_viewers` (2026-09) spends about 32 µs per viewer per
4-frame, 64-byte group over the in-memory transport, through a publisher
session, a relay origin, and a viewer session. No single function dominates.
The profile spreads it over `kio` waiter registration and parking,
`session_delivery_viewers` spends about 26 µs per viewer per 4-frame,
64-byte group over the in-memory transport, through a publisher session, a
relay origin, and a viewer session: ~420 µs at 16 viewers on every version
(2026-09-25, Apple M4; the 256-viewer point is too noisy on that machine to
quote). No single function dominates. An earlier Linux profile spread it over `kio` waiter registration and parking,
`TrackState::evict_expired_scan` (4-5%), `Waiter`'s lazily allocated shared
waker (`Once::call`, about 3%, so waiters are created per poll), and
malloc/free (10-12%).
Expand Down
29 changes: 0 additions & 29 deletions quest/m1/perf/lite-route-rescan.md

This file was deleted.

5 changes: 3 additions & 2 deletions rs/moq-net/benches/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_m
use moq_net::{Hop, Timestamp, Version, broadcast, cache, origin, track};
use support::harness::{MockConnectOptions, MockPair, connect_mock};

/// Newest lite draft (`moq-lite-07-wip`, opt-in) and newest IETF draft.
const VERSIONS: [&str; 2] = ["moq-lite-07-wip", "moq-transport-22"];
/// The lite draft production negotiates, the next one (opt-in), and the newest
/// IETF draft.
const VERSIONS: [&str; 3] = ["moq-lite-06", "moq-lite-07-wip", "moq-transport-22"];

/// Frames per group, so per-frame and per-group costs both appear.
const FRAMES: usize = 4;
Expand Down
Loading