Skip to content

feat(moq-net): expose broadcast demand on the read handle - #3330

Merged
kixelated merged 2 commits into
moq-dev:mainfrom
Frando:pr/broadcast-demand
Sep 2, 2026
Merged

feat(moq-net): expose broadcast demand on the read handle#3330
kixelated merged 2 commits into
moq-dev:mainfrom
Frando:pr/broadcast-demand

Conversation

@Frando

@Frando Frando commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

broadcast::Consumer::demand was pub(crate). Making it public gives a holder that has only a read handle the same answer Producer::demand gives a writer: whether anything downstream is still reading.

This PR is part of a series to update iroh-live to latest moq, see n0-computer/iroh-live#45. The code and below description was written by Claude Code

The case that wants it is a relay that pulls a broadcast from upstream. The ingesting session owns the producer, so the puller holds only a consumer, and the question it has to answer is whether any local subscriber is still reading what it mirrors. Without this it has no signal at all, and a transport-level one cannot substitute: keep-alives move every byte counter on a connection nobody reads.

For a route-fed front, demand reports whether any spliced logical track has a live consumer, which is exactly the question. Holding the handle, or the Consumer it came from, is not itself demand.

No behaviour changes. The visibility widens, the docs say what the consumer side is for, and the existing spliced-demand test now parks on the read handle for the unused edge, since that is the transition a puller acts on.

Verified

914 moq-net tests pass. Clippy clean for moq-net and moq-relay.

Downstream, this is what lets iroh-live's relay retire a pulled session once nothing reads it, with a test that fails when the call is stubbed out to return nothing.

Overlap with dev

dev already has pub fn demand(), but on Producer only. This is the consumer-side sibling, so the two do not collide, though it is worth knowing that the branches have converged on the same idea from different ends.

Review round

A relay watching a pulled broadcast has two endings to act on and one handle to learn both from: readers going away is unused resolving, while the upstream producer going away is Error::Dropped. That distinction is now on Consumer::demand rather than only on Demand, and in a test.

Cross-package sync

The table in AGENTS.md asks for a matching js/net update on an rs/moq-net API change, and this one skips that row deliberately.

Broadcast-level demand has no JavaScript surface today: js/net/src/broadcast.ts exports Producer and Consumer with no demand, used or unused on either, and the demand that does exist in JS is per-track and per-group (track.Producer.used, and the group mirror counting in group.ts). So this is not a Rust-only API being introduced; it is one method reaching the read handle of a concept that was already Rust-only on both sides. Adding a JS broadcast-demand observer is worth doing, but it is the producer half's job as much as this one's, and it belongs in a change that designs the whole surface rather than in a one-method PR.

doc/concept likewise documents demand at the track level, and nothing there becomes wrong.

`Producer::demand` is public, `Consumer::demand` is not, so the only way
to ask whether anything is reading a broadcast is to own the producer for
it. That is the wrong half for a relay that pulls a broadcast from
upstream: the session ingesting the remote announce owns the producer,
while the code that has to decide whether the upstream connection is
still worth holding open has a read handle and nothing else.

The handle is already what the publishers use to price an announcement
(`select_route` takes a `&Demand` derived from the consumer it is
forwarding), so this exposes an existing internal signal rather than
adding one. It is a weak handle: it neither keeps the broadcast alive nor
counts as demand, so watching a broadcast cannot make it look watched.

The spliced demand test now parks on the read handle for the unused edge,
which is the shape a relay watches: a route-fed front whose logical
tracks outlive any one session.
Frando added a commit to Frando/moq that referenced this pull request Sep 2, 2026
A publisher had no way to ask whether anyone was subscribed, so a capture pipeline ran whether or not it was being watched. The read handle now carries the demand the origin already tracks.

Squashed from `pr/broadcast-demand` at 950827b, open as moq-dev#3330.
@Frando
Frando marked this pull request as ready for review September 2, 2026 15:45

@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: 950827b47d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

/// producer for it (the ingesting session does), yet the question it has to
/// answer is whether anything downstream is still reading. Holding this
/// handle, or the [`Consumer`] it came from, is not itself demand.
pub fn demand(&self) -> Demand {

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 Mirror the public demand API in JavaScript

Making this method public changes the Rust moq-net API, but js/net/src/broadcast.ts::Consumer still has no broadcast-demand observer, so JavaScript consumers cannot implement the idle-pull lifecycle described here. The repository requires rs/moq-net API changes to be reconciled with js/net and doc/concept; add the corresponding JS surface and documentation, or explain why this API is intentionally Rust-only. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L199-L206

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 2e572231-0f25-4b12-b397-e4170fff578c

📥 Commits

Reviewing files that changed from the base of the PR and between 6b80dba and 950827b.

📒 Files selected for processing (1)
  • rs/moq-net/src/model/broadcast.rs

Included review availability: Your plan provides up to 4 included reviews per hour; 1 remains after this review.


Walkthrough

The change makes Consumer::demand public and updates the related Demand documentation. The demand_spliced test obtains a demand handle from the consumer, verifies used and unused transitions, and waits on the read handle when the last consumer drops.

Merge Risk: ⚪ Minimal · up to 95082

This change only exposes existing read-only demand information to holders of a broadcast consumer and does not change write, routing, subscription, or ownership behavior. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: exposing broadcast demand through the read handle.
Description check ✅ Passed The description directly explains the API visibility change, its relay use case, test coverage, and lack of behavior changes.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code

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.

A relay watching a pulled broadcast has two endings to act on and one handle to
learn both from. Readers going away means stop pulling; an upstream that
vanished means the pull is over and retrying is pointless. They arrive as
`unused` resolving and as `Error::Dropped`, which the docs now say on
`Consumer::demand` rather than only on `Demand` itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Frando added a commit to Frando/moq that referenced this pull request Sep 2, 2026
A publisher had no way to ask whether anyone was subscribed, so a capture pipeline ran whether or not it was being watched. The read handle now carries the demand the origin already tracks, and the docs say how a relay tells lost readers apart from a producer that went away.

Squashed from `pr/broadcast-demand` at ec836fe, open as moq-dev#3330.
@kixelated
kixelated merged commit 93bcf7b into moq-dev:main Sep 2, 2026
3 checks passed
@moq-bot moq-bot Bot mentioned this pull request Sep 2, 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.

2 participants