fix(watch): recover from a reset catalog and a same-name republish#2188
Open
fperex wants to merge 2 commits into
Open
fix(watch): recover from a reset catalog and a same-name republish#2188fperex wants to merge 2 commits into
fperex wants to merge 2 commits into
Conversation
Every run loop logged any error from a stream as a warning, so a normal unsubscribe, a publisher handover or a peer closing the session produced scary warnings on every viewer that stopped watching. isStreamAbort() decides on the application code the relay encodes (WebTransportError.streamErrorCode natively, or the trailing number in qmux's RESET_STREAM/STOP_SENDING and Connection closed messages on the WebSocket fallback). Coded faults are a denylist, so an unknown code is treated as routine teardown while a client-actionable fault (auth, not-found, protocol, unroutable) still surfaces at warn. Call sites in the lite/ietf publishers, the subscriber and the connection run loop now pick their log level from it. The bandwidth probe streams no longer swallow genuine faults at debug.
Two ways a tile could go permanently offline while the relay was perfectly healthy: A catalog subscription that ended was terminal. #runCatalog subscribed once and wrote status=offline whenever its fetch loop ended, but the tracked signals (enabled, name, catalogFormat, active) do not change when only the catalog stream resets, so the effect never re-ran. Any transient reset (a slow-consumer drop, a relay bounce, a publisher handover) stranded the tile offline until a reload. Reopen it on a bounded backoff, but only when the loop ended in a stream reset: a clean end (the publisher stopped) or a coded fault still reports offline immediately. A republish under the same name was invisible. The announce Set only tracks presence, so when a publisher's unannounce and the new instance's announce coalesce, the Set never observably flips and the watcher stays bound to the dead route. Track a per-path announce generation that bumps on every (re-)announce, so a same-name republish is a real change the watcher re-consumes.
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.
Split out of #2163.
Two ways a tile could go permanently offline while the relay was perfectly healthy:
1. A catalog subscription that ended was terminal.
#runCatalogsubscribed once and wrotestatus = "offline"whenever its fetch loop ended. But the effect's tracked signals (enabled,name,catalogFormat,active) do not change when only the catalog stream resets, so the effect never re-ran and nothing ever resubscribed. Any transient reset (a slow-consumer drop, a relay bounce, a publisher handover) stranded the tile offline until a page reload.It now reopens on a bounded backoff, but only when the loop ended in a stream reset. A clean end (the publisher genuinely stopped) or a coded fault still reports offline immediately, which is why this depends on the
isStreamAbortclassification rather than retrying on any error.2. A republish under the same name was invisible. The announce
Setonly tracks presence, so when a publisher's unannounce and the new instance's announce coalesce, the set never observably flips and the watcher stays bound to the dead route.Reloadnow tracks a per-path announce generation that bumps on every (re-)announce, so a same-name republish is a real change the watcher re-consumes.The generation map deliberately never deletes on unannounce: resetting a path to 1 would make a coalesced republish look unchanged (1 -> 1) and reintroduce the bug. It is reset on reconnect, so it grows by one entry per distinct path ever announced, which is an accepted tradeoff.
Public API changes:
@moq/netgainsReload.announcedGenerations;@moq/watchgains an optionalannouncedGenerationsin theBroadcastoptions bag (both additive).Test plan:
just js checkgreen. Verified in the demo: killing and immediately restarting a publisher under the same name re-attaches the viewer, and bouncing the relay no longer strands a tile offline.