refactor(net)!: route everything through create_broadcast, gate announce on Route.live#2396
Merged
Merged
Conversation
…nce on Route.live Announce/unannounce is no longer a separate origin-level concern: a broadcast's liveness now lives on its route. broadcast::Route gains a live flag, the origin announces a path only while its best source's route is live, and a non-live broadcast stays reachable by exact path for subscribes and fetches. Toggling live via set_route announces or unannounces without touching the broadcast. origin::Producer::create_broadcast(path, route) is now the sole way content enters an origin, returning a plain broadcast::Producer that acts as a route source. Every source created at a path feeds one origin-owned spliced broadcast; the best route (live first, then cost, hops, deterministic tie-break) serves each track, and handovers resume at the first missing group. finish() detaches immediately (graceful unannounce); a bare drop lingers so a reconnecting session can splice in invisibly. Sessions attach announces through the same entry point and serve tracks via a dynamic handler, the same shape the IETF subscriber already used. Deleted: publish_broadcast, the Publish/Broadcast guards, attach_route, and the whole per-route Assignments/Assignment/Serving surface plus the served-track requeue machinery. The node tree now holds a single origin-owned broadcast per path instead of parallel face/front slots with identity checks, and the join-or-create attach race loop collapsed into one lock acquisition. Also fixes two bugs found while landing this: track::Consumer::poll_complete re-locked a kio channel whose closed guard (Err(Ref)) was still held, deadlocking the runtime; and the dispatcher burned its whole retry budget on a closed source before its watcher could detach it, aborting tracks as Unroutable instead of failing over. moq-ffi/libmoq reshape accordingly (create_broadcast + set_live replace announce/unannounce), with the hand-written py/swift/kt/go wrappers, cpp/obs, and docs updated in tow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @kixelated, your pull request is larger than the review limit of 150000 diff characters
…blish API Merged latest dev; #2389's new tests used the one-argument create_broadcast and relied on an abrupt producer drop propagating immediately, which now lingers for a reconnect by design. Pass a live route and finish() the broadcast at the deliberate teardown, keeping the unfinalized-live-edge assertion intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review fixes on top of the create_broadcast/Route.live redesign: - sync_front now snapshots and applies under the leaf lock. Two concurrent syncs could otherwise apply stale snapshots out of order, leaving the announce flag (or route advert) contradicting the current source table. - The dispatcher parks on a source that was deliberately ended (finish/abort marked but handles still alive), not just a fully closed one, so the teardown window can't burn the retry budget and abort a routable track. - attach_source reads the live flag from the route in hand instead of re-locking the just-built table. - The lite and IETF subscribers share one SourceGuard (finish = graceful detach, drop = abort + linger) instead of two hand-rolled copies of the same contract. - Gateways finish() their broadcast on deliberate teardown (RTMP clean stream end and dial, SRT finish, GStreamer stop, WHIP DELETE), restoring the prompt unannounce they had before the rework; a dying connection still lingers so a reconnect can splice in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kixelated
enabled auto-merge (squash)
July 18, 2026 14:51
kixelated
disabled auto-merge
July 18, 2026 14:51
The flag's effect is announcement, so name it that: `Route.announce` + `with_announce()` in moq-net, `MoqRoute.announce` / `set_announce` in moq-ffi, `moq_publish_set_announce` in libmoq, and the matching renames in the py/swift/kt/go wrappers and docs. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kixelated
enabled auto-merge (squash)
July 18, 2026 14:56
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What
Replaces the origin's announce/unannounce surface with liveness on the route, per the design discussion:
broadcast::Routegainsannounce: bool(defaultfalse, builder.with_announce(bool)). The origin announces a path only while its best source's route asks for it. A non-live broadcast stays in the tree:request_broadcast/consume_broadcast/ subscribes / fetches all still resolve it, it just never appears inannounced(). Togglingannounceviaset_routeannounces/unannounces without touching the broadcast, so an offline broadcast keeps serving cached or on-demand content.origin::Producer::create_broadcast(path, route)is the sole entry point, returning a plainbroadcast::Producerthat acts as a route source. Calling it again at the same path attaches another source; the origin owns the spliced broadcast consumers actually see and always serves from the best route (announced first, then cost, then shortest hops with the deterministic tie-break).set_routeis only needed to update the route afterwards.finish()detaches immediately (graceful unannounce, and a later create at the path is a fresh broadcast); a bare drop is treated as a failure, so the path lingers ~5s for a reconnecting session to splice back in invisibly. This replaces thePublish/Broadcastguards,Route::unannounce(), and the guard-vs-producer lifetime split.broadcast::Producer::dynamic(), the shape the IETF subscriber already had;attach_routeand the wholeAssignments/Assignment/Servingsurface are gone. Attach is deferred to the source watcher's firstroute_changedobservation, which gives every creator a synchronous window to register tracks/handlers before the broadcast becomes visible (this closes the handler-registration race for free).Deleted from the public surface:
origin::Publish,origin::Broadcast,publish_broadcast,attach_route,Assignments,Assignment,Serving.Semantic changes to be aware of
Unroutableafter 3 rejected attempts (same as the network path did) instead of an instantNotFound; local and remote consumers behave identically.Simplifications
The origin was carrying two mechanisms (direct publish with active/backup queues, and the route-fed front from #2241). It now has one. Concretely removed: the per-path backup queue and its promote/replace logic, the served-track map + requeue plumbing, per-route assignment queues with their parking/wake edge cases, three guard types, and the dual face/front node slots with cross-identity checks (now a single
OriginBroadcastslot; the join-or-create attach race loop collapsed into one lock acquisition). Net: -372 lines workspace-wide, -321 in moq-net alone, while adding the announce-gating and offline-routability features. Each spliced track is now one task that watches the source table, rather than state smeared across queues.Bugs found and fixed along the way
track::Consumer::poll_complete(new) re-locked a kio channel while theErr(Ref)closed-guard from the same poll was still alive; kio's sync API returns closure as a live lock guard, so this self-deadlocked the runtime (caught by the moq-ffi suite wedging). Fixed by reading through the guard.MAX_TRACK_RETRIESbudget instantly on a source that had closed but whose watcher hadn't detached it yet, aborting tracks asUnroutableinstead of failing over. A closed source is now parked out (no strike) until the table moves on; route ids are never reused, so this cannot wedge.The open question: offline-but-fetchable over the wire
Nothing on the wire signals "no longer live but still fetchable" yet, and this PR deliberately doesn't invent it. Two paths from here:
Origin::dynamic()(works now): an edge handler serves fetches for cold content by callingcreate_broadcast(path, Route::new())— announce=false — and filling it from storage. It's routable and cacheable in the tree without ever being announced..with_announce(flag)instead of hardcodedtrue, and everything downstream (gating, selection preferring announced sources, fetch serving) already works.Route.announceis exactly the model hook for it.Relatedly: with
announcein the route, a dynamic handler can now promote a broadcast to announced (announce=true) — dynamically accepted broadcasts are no longer condemned to invisibility. A possible follow-up is folding the dynamicservedweak-cache into announce=false tree residency entirely.Cross-package sync
rs/*: every crate migrated (gateways, cli, relay, stats, bench, examples); moq-ffi/libmoq reshaped (create_broadcast+set_announcereplaceannounce/MoqAnnounce/unannounce; libmoq'smoq_publish_createremoved since a standalone broadcast can no longer reach an origin); hand-written py/swift/kt/go wrapper code,cpp/obs, anddoc/lib/*updated.Route/guard surface, so nothing breaks; addingannouncethere is a follow-up.just rs checkgreen (moq-net 512 tests, moq-ffi 50, libmoq 35, plus all migrated crates),cargo doc -D warnings, andjust test smoke.(written by Fable 5)