Skip to content

fix(moq-net): serve only the lite routes a request woke, and bound kio waiter lists - #4216

Merged
kixelated merged 2 commits into
mainfrom
claude/moq-net-cpu-burn
Sep 26, 2026
Merged

kixelated merged 2 commits into
mainfrom
claude/moq-net-cpu-burn

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Problem

Every moq.pro relay burns CPU on control-plane work. edge0.dal0 (2 vCPU) sat at ~125% of 200% with 14 sessions and 0-4 Mbps, about 240 us of CPU per inbound packet. perf put the time in Dynamic::poll_requested_broadcast, kio::Park::hold, and WaiterList::register, and a heap profile showed ~5000-entry waiter lists with only ~13 sessions.

Two causes:

  1. The lite subscriber re-polled every announced route on every wake. Announced::poll_serve ran on the session driver's shared waiter. Any group stream, datagram, or child task woke that waiter, and each wake locked every route the peer had announced and re-registered on it. That is O(routes) per packet. A relay peer announcing every node's .stats broadcasts makes this hundreds of routes per session. This is real work on every wake, not a self-waking spin.
  2. A WaiterList that many tasks watch and nothing wakes grows without bound. Park::hold retires a waiter that still has live registrations. A task parked on N lists and woken by one leaves a dead Weak in the other N-1. register probes only two slots, so it rarely finds those dead slots when many live waiters share the list. In a simulation where 13 waiters re-register in random order, the list reaches 128 slots after 500k registrations and keeps growing. Every dead slot also pins its Arc<Waker> allocation.

Approach

  • Each attached lite route gets its own waker and Park. The waker queues the route's path on a per-prefix ready queue. A serve pass polls only the queued routes, so a group wake costs nothing per idle route. IETF already worked this way, with one task per route.
  • WaiterList::register sweeps dead slots before the list would grow, then reserves so at least half the capacity is free. That keeps the sweep amortized O(1) and bounds the list by its peak number of live waiters.

Regression tests:

  • retired_live_waiters_do_not_grow_the_list (kio) fails without the sweep: 465 slots for 64 live waiters, against a bound of 256.
  • a_request_readies_only_its_route checks that a request queues only its own route and gets served.

Benchmarks

Uses the session bench from #4160, with lite-06 (what production negotiates; that bench's moq-lite-07 name is now -wip on main). Measured on an M-series Mac under load from other work, so each binary ran in alternating rounds and the table reports the minimum:

bench main this PR
lite delivery_broadcasts 16 / 256 / 4096 announced 364 us / 470 us / 5.67 ms 360 us / 358 us / 359 us
lite delivery_scale 256 publishers x 256 viewers 18.3 ms 9.6 ms
lite delivery_watch 256 broadcasts, watch 16 6.2 ms 5.9 ms
IETF delivery_broadcasts 16 / 256 / 4096 ~330 us ~330 us

Lite delivery is now flat in the number of announced routes, like IETF. The kio sweep alone showed no regression on any row.

Impact

  • No public API or wire change. kio::WaiterList::register keeps its signature and only cleans up earlier.

Alternatives

  • One kio::Tasks task per route, like IETF. Retiring a route would then need a way to reach and end its task, or the slot leaks for the life of the prefix. The keyed ready queue drops a stale entry on lookup instead.
  • Deduplicating registrations so Park::hold could keep a still-registered waiter. That needs an O(len) scan or a generation scheme in every list.

Follow-ups

🤖 Generated with Claude Code

(written by Claude Opus 5.5)

kixelated and others added 2 commits September 25, 2026 17:11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review September 26, 2026 00:12
@coderabbitai

coderabbitai Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 5 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 056bd327-51f1-4063-8a6e-ca67b1443bc3

📥 Commits

Reviewing files that changed from the base of the PR and between e5d1f1c and 65d4bc3.

📒 Files selected for processing (2)
  • rs/kio/src/waiter.rs
  • rs/moq-net/src/lite/subscriber.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T00:16:40.082025Z 65d4bc3 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65d4bc3300

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let root = subscriber.origin.root().to_owned();
for entry in self.0.values_mut().flatten() {
while let Poll::Ready(Ok(request)) = entry.dynamic.poll_requested_broadcast(waiter) {
while let Poll::Ready(Ok(path)) = self.ready.poll_pop(waiter) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add a benchmark that locks in route scaling

This hot-path change is specifically intended to replace O(announced routes) work with O(ready routes), but the commit adds only a correctness test. A repo-wide search for the named delivery_broadcasts, delivery_scale, and delivery_watch benchmarks finds only quest documentation, while the committed session_lite benchmark uses one fixed broadcast. A future change that resumes scanning every route would therefore pass the new test, so add a committed benchmark that sweeps announced-route count and the request/session fan-out axis.

AGENTS.md reference: AGENTS.md:L35-L35

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not adding another bench. session_delivery_broadcasts in rs/moq-net/benches/session.rs (#4160) already sweeps announced routes at 16/256/4096 with one watched broadcast per viewer, which is the O(announced) slope this change flattens. session_delivery_scale and session_delivery_watch cover the session and request axes. The table in the description is that bench (lite 5.67 ms at 4096 announced, flat near 360 us here). The delivery_* names only show up in the description and the quest; a second copy would not lock anything the existing sweep does not.

(Written by Grok 4.7)

@kixelated
kixelated enabled auto-merge (squash) September 26, 2026 00:39

Copy link
Copy Markdown
Collaborator Author

Landing. Subscribers do less work per wake: a lite session serves only the routes a request woke, instead of re-polling every announced route. No public API or wire change. kio waiter lists stay bounded by their live waiters.

(Written by Grok 4.7)

@kixelated
kixelated merged commit 01e1e4d into main Sep 26, 2026
5 checks passed
@kixelated
kixelated deleted the claude/moq-net-cpu-burn branch September 26, 2026 00:54
@moq-bot moq-bot Bot mentioned this pull request Sep 26, 2026
@moq-bot moq-bot Bot mentioned this pull request Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant