fix(webtransport): buffer track objects that arrive before their SUBSCRIBE_OK binds an alias - #1
Conversation
…CRIBE_OK binds an alias The control stream (carrying SUBSCRIBE_OK) and unidirectional data streams (carrying Objects) are not ordered relative to each other (draft-ietf-moq-transport-16 section 10.4). A publisher can legitimately deliver a subscription data before the local subscriber has processed SUBSCRIBE_OK and bound the Track Alias in rawAliasMaps. routeToTrackSubscription previously returned false for any alias with no bound route yet, silently dropping the object. For tracks whose entire content is a single object at group 0 (MSF/CMAF catalogs, init segments) this loses the track permanently and the subscription hangs - nondeterministically, depending on relay/publisher timing. Buffer objects (and graceful subgroup-stream FINs) for an unbound alias while any subscribeTrack() call is still pending, capped at 256 per alias, and replay them in submission order once SUBSCRIBE_OK binds the alias. Buffers are cleared whenever no subscription is left pending to claim them (REQUEST_ERROR, subscribeTrack() send failure, or session teardown), so nothing lingers past its possible owner. Also adds an optional onSubgroupClosed callback to TrackSubscribeOptions - the only reliable end-of-subgroup signal available when a publisher does not set the subgroup header END_OF_GROUP flag, needed here so a graceful FIN racing SUBSCRIBE_OK can be buffered and replayed the same way as objects. Found and originally patched locally against v0.5.0 while integrating this library into a downstream player against a real relay; the race reproduces reliably enough in practice that it was carried as a submodule patch. Re-derived here against the current adapter.ts.
|
Thanks for the PR. That race is real and the regression test is useful, but I need some changes before merging: the current fallback can consume objects belonging to established subscribe() calls, replay before the mutable callback contract can be honored, buffer peer-controlled data without global byte/alias/time limits (and silently discard on overflow while still claiming the object), and retain stale data across several terminal paths and alias reuse. Please rework it so the buffer never claims aliases already bound to other subscriptions and fails closed when limits are hit, with cleanup centralized on every settlement and cancellation path, and replay deferred to where the callback contract holds (the pendingFetchStreams parking + tombstone pattern in adapter.ts is the in-house precedent to follow). Tests for each of those cases, please. :) |
Control and data streams aren't ordered relative to each other (draft-ietf-moq-transport-16 §10.4), so a publisher can deliver a subscription's objects before SUBSCRIBE_OK binds the Track Alias.
routeToTrackSubscriptiondropped these silently - for tracks that are a single object at group 0 (catalogs, init segments), this loses the track permanently and the subscription hangs.Buffers objects (and graceful subgroup FINs) per-alias while any
subscribeTrack()is pending, capped at 256, replayed once SUBSCRIBE_OK binds. Buffers clear once nothing is left pending to claim them. Also adds anonSubgroupClosedcallback toTrackSubscribeOptions, needed to buffer a FIN the same way as objects.Testing: new regression test in
adapter.test.ts(confirmed it failswithout the fix); full
packages/webtransportsuite passes;tsc --noEmitclean; verified live against a real draft-14 relay via a downstream integration.Originally patched locally against v0.5.0; re-derived here against current
adapter.ts.This change is