Repository navigation
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An upstream SUBSCRIBE or FETCH now outlives its last reader by a one second linger, so a reader that re-subscribes, seeks, or blips rides the request still in flight instead of churning a cancel and a fresh request upstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Closing unmerged: the maintainer decided to abandon the request linger (2026-10-06). The linger lives in the session subscriber, so it also holds client subscriptions open after the app drops them, costing bandwidth on every rendition switch, and no relay churn has been measured that would justify it. The quest is deleted in #4930; revisit if relay logs show re-request churn. (Written by Claude Opus 5.5) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A session's upstream SUBSCRIBE is canceled the instant its last subscriber leaves, and an upstream FETCH is cut short (all the way to the publisher) the instant no fetcher waits and no reader holds its group. A reader that re-subscribes, seeks, or blips therefore churns a cancel and a fresh request upstream, hop by hop, for what is a sub-second gap.
Approach
Every "nobody wants this any more" point on the session side now waits out a fixed
track::REQUEST_LINGER(1 s) first, restarting whenever demand returns. A reader back within it rides the request still in flight. Per the quest's decisions, demand decides per request type, unsplit: a FETCH watches its fetch callers and group readers, a subscription watches its subscribers, and group demand is not divided between them.Fetching::droponly withdraws an attempt still queued (it has cost nothing). An attempt a handler took stays joinable, so a returningfetch_groupjoins it. The handler gives it up with the new crate-privategroup::Request::reject_unused, atomic with a join under the fetch lock (mirroringtrack::Request::reject_unused).poll_abandonwraps that with the linger.time::Linger(aDeadlinethat re-arms while demand is present) now drives every linger, including the existing 30 s copy linger in both subscribers, which drops their hand-rolled arm/disarm code.begin_subscriptionreturnsBegin::Idleinstead of canceling, andServeLoopcancels once the linger runs out. A returning subscriber turns into aSUBSCRIBE_UPDATEon the live subscription. The TRACK_INFO wait andFetchServeRun(before the answer and mid-ingest) linger too.SUBSCRIBE_OKwait, the established subscription, and the group FETCH (before and afterFETCH_OK) linger.js/net) has the same lifecycle, so it mirrors it withutil/linger.ts(REQUEST_LINGER_MS,abandoned(demand)) in both subscribers' serve loops, the IETF pre-SUBSCRIBE_OKwait, and the lite fetch setup and response pump. Its fetch entry already stayed joinable until the group closed.Impact
REQUEST_LINGER,time::Linger,group::Request::reject_unusedandpoll_abandon,Requests::is_queuedare crate-private, andREQUEST_LINGER_MS/abandonedare internal to@moq/net.group::Request::demand()can now go used again after going unused (its doc says so). A third-partyDynamichandler that drops a request the moment demand leaves still works, but a caller that joined in that gap readsDropped. Handlers should give up with the linger (crate-private for now; see follow-ups).Tests
rs/moq-net/tests/request_linger.rs: through a relay on lite-06, lite-07 and moq-transport-19, a re-subscribe and a re-fetch inside the linger ride the request already upstream (the publisher sees no new request and no cancel), and the subscription is canceled only once the linger runs out. All six fail withREQUEST_LINGERset to zero.reject_unusedracing a returning caller. Existing cancel tests now assert the request is still held inside the linger before advancing simulated time past it.moq-tokio'sbroadcast_rejoin_replays_a_current_warm_cacheruns on the wall clock and waits out the linger three rounds per version, which pushed it past nextest's 60 s timeout. Its versions now run concurrently (about 4 s).just checkpasses apart frommoq-uringworker tests, which fail locally with ENOMEM on RLIMIT_MEMLOCK. That limit is shared by every process of this user, other agents included, and the crate is untouched here.js/net/src/util/linger.test.tscovers the helper on fake timers. The JS teardown tests (integration, lite, IETF) now check the request is still held inside the linger, then run it out on fake timers instead of the wall clock.Alternatives
Fetching::drop's withdrawal with a timer there). The model has no clock today, and every handler would inherit a delay it did not ask for. The session already owns the clock and the upstream request.Follow-ups
<moq-watch>), and retuneREQUEST_LINGERif 1 s is off.Dynamichandlers (moq-ffi, moq-c) if any start canceling their own upstream work on demand.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code