Skip to content

Commit 3a8c8cd

Browse files
kixelatedclaude
andcommitted
moq-net: collapse the consume_track handle to one TrackConsumer
Per review, drop the pending-vs-resolved split: consume_track() returns a single TrackConsumer (Clone) that Derefs to a name-only Track (so `.name` works) and exposes async info() / subscribe(). Every other track property is unknown until info() resolves it (for a relay it comes from upstream), so there's no separate "resolved" handle and no ok()/poll_ok ceremony. The previous subscribe-future TrackPending is renamed SubscribePending (one moq-mux reference). Existing callers (consume_track(x).subscribe(None).await / .info().await) are unchanged. This is the consumer-side of the model redesign; the demand-side (TrackState shared by TrackRequest/TrackProducer, async subscription(), accept upgrading the request into the producer) is the follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a109347 commit 3a8c8cd

3 files changed

Lines changed: 49 additions & 39 deletions

File tree

rs/moq-mux/src/container/source.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl VideoTransform {
4949
/// A subscription that resolves on first poll, then the live consumer.
5050
enum SourceState {
5151
/// Waiting for the subscription to resolve (blocks on the publisher's SUBSCRIBE_OK).
52-
Subscribing(moq_net::TrackPending),
52+
Subscribing(moq_net::SubscribePending),
5353
/// The resolved consumer, reading frames.
5454
Active(Consumer<HangContainer>),
5555
}

rs/moq-net/src/model/broadcast.rs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -484,40 +484,40 @@ impl std::future::Future for InfoPending {
484484
/// SUBSCRIBE_OK). It implements [`Future`], so `.await` it to get the
485485
/// [`TrackSubscriber`] (or an error). Poll-based callers can instead drive it
486486
/// with [`Self::poll_ok`] inside a `kio` poll loop.
487-
pub struct TrackPending {
488-
inner: TrackPendingInner,
487+
pub struct SubscribePending {
488+
inner: SubscribePendingInner,
489489
/// Kept alive between `Future::poll` calls so its registration in the
490490
/// resolver channel stays valid until the next poll replaces it.
491491
waiter: Option<kio::Waiter>,
492492
}
493493

494-
enum TrackPendingInner {
494+
enum SubscribePendingInner {
495495
/// Resolved synchronously: the track already existed, or it failed immediately.
496496
Ready(Result<TrackSubscriber, Error>),
497497
/// Waiting for the publisher to accept or deny via the dynamic handler.
498498
Waiting(kio::Consumer<PendingSlot>),
499499
}
500500

501-
impl TrackPending {
501+
impl SubscribePending {
502502
fn ready(result: Result<TrackSubscriber, Error>) -> Self {
503503
Self {
504-
inner: TrackPendingInner::Ready(result),
504+
inner: SubscribePendingInner::Ready(result),
505505
waiter: None,
506506
}
507507
}
508508

509509
fn waiting(consumer: kio::Consumer<PendingSlot>) -> Self {
510510
Self {
511-
inner: TrackPendingInner::Waiting(consumer),
511+
inner: SubscribePendingInner::Waiting(consumer),
512512
waiter: None,
513513
}
514514
}
515515

516516
/// Poll for the resolved [`TrackSubscriber`], without blocking.
517517
pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<TrackSubscriber, Error>> {
518518
match &self.inner {
519-
TrackPendingInner::Ready(result) => Poll::Ready(result.clone()),
520-
TrackPendingInner::Waiting(consumer) => match consumer.poll(waiter, |slot| match &**slot {
519+
SubscribePendingInner::Ready(result) => Poll::Ready(result.clone()),
520+
SubscribePendingInner::Waiting(consumer) => match consumer.poll(waiter, |slot| match &**slot {
521521
Some(result) => Poll::Ready(result.clone()),
522522
None => Poll::Pending,
523523
}) {
@@ -533,7 +533,7 @@ impl TrackPending {
533533
}
534534
}
535535

536-
impl std::future::Future for TrackPending {
536+
impl std::future::Future for SubscribePending {
537537
type Output = Result<TrackSubscriber, Error>;
538538

539539
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
@@ -727,38 +727,38 @@ impl BroadcastConsumer {
727727
///
728728
/// This is a cheap, synchronous lookup that returns a [`TrackConsumer`] bound
729729
/// to `name`. Nothing is sent to the publisher yet: call
730-
/// [`TrackConsumer::subscribe`] to open a live subscription (blocking on
731-
/// SUBSCRIBE_OK), or hold the handle and subscribe later.
730+
/// [`TrackConsumer::subscribe`] to open a live subscription, [`TrackConsumer::info`]
731+
/// to resolve its properties, or hold the handle and act later.
732732
pub fn consume_track(&self, name: &str) -> TrackConsumer {
733733
TrackConsumer {
734734
broadcast: self.clone(),
735-
name: name.to_string(),
735+
info: Track::new(name),
736736
}
737737
}
738738

739-
/// Register a subscription for `name` and return a [`TrackPending`] that
739+
/// Register a subscription for `name` and return a [`SubscribePending`] that
740740
/// resolves once the publisher accepts it.
741741
///
742742
/// Reuses a live producer if one is already publishing the track (the pending
743743
/// resolves right away), otherwise queues a dynamic request served via
744744
/// [`BroadcastDynamic::requested_track`] and [`TrackRequest::accept`] (for the
745745
/// wire this is SUBSCRIBE_OK). Resolves to [`Error::NotFound`] if no dynamic
746746
/// producer exists to handle the request.
747-
fn request_subscribe(&self, name: &str, subscription: Subscription) -> TrackPending {
747+
fn request_subscribe(&self, name: &str, subscription: Subscription) -> SubscribePending {
748748
// Upgrade to a temporary producer so we can modify the state.
749749
let Some(producer) = self.state.produce() else {
750750
let err = self.state.read().abort.clone().unwrap_or(Error::Dropped);
751-
return TrackPending::ready(Err(err));
751+
return SubscribePending::ready(Err(err));
752752
};
753753
let mut state = match modify(&producer) {
754754
Ok(state) => state,
755-
Err(err) => return TrackPending::ready(Err(err)),
755+
Err(err) => return SubscribePending::ready(Err(err)),
756756
};
757757

758758
// Reuse a live producer if one is already publishing the track.
759759
if let Some(weak) = state.tracks.get(name) {
760760
if !weak.is_closed() {
761-
return TrackPending::ready(Ok(weak.subscribe(subscription)));
761+
return SubscribePending::ready(Ok(weak.subscribe(subscription)));
762762
}
763763
// Drop the stale entry and fall through to a fresh request.
764764
state.tracks.remove(name);
@@ -771,7 +771,7 @@ impl BroadcastConsumer {
771771
// Coalesce onto an in-flight request for the same name.
772772
pending.resolvers.push((subscription, slot));
773773
} else if state.dynamic == 0 {
774-
return TrackPending::ready(Err(Error::NotFound));
774+
return SubscribePending::ready(Err(Error::NotFound));
775775
} else {
776776
state.requests.insert(
777777
name.to_string(),
@@ -783,7 +783,7 @@ impl BroadcastConsumer {
783783
state.request_order.push_back(name.to_string());
784784
}
785785

786-
TrackPending::waiting(consumer)
786+
SubscribePending::waiting(consumer)
787787
}
788788

789789
/// Resolve a track's immutable info, returning an [`InfoPending`].
@@ -872,47 +872,57 @@ impl BroadcastConsumer {
872872

873873
/// A handle to a single track within a broadcast.
874874
///
875-
/// Obtained from [`BroadcastConsumer::consume_track`]. Holding it sends nothing
876-
/// to the publisher; it just names a track you can [`subscribe`](Self::subscribe)
877-
/// to (a live, ongoing stream of groups) later. The same handle can be subscribed
878-
/// to multiple times, and clones are cheap.
875+
/// Obtained from [`BroadcastConsumer::consume_track`]. Holding it sends nothing to
876+
/// the publisher; it just names a track you can [`subscribe`](Self::subscribe) to
877+
/// (a live, ongoing stream of groups) or resolve [`info`](Self::info) for later.
878+
/// Clones are cheap and concurrent operations on the same name coalesce onto one
879+
/// request.
880+
///
881+
/// Only the track's `name` is known up front (available via `Deref`); every other
882+
/// property is resolved on demand via [`info`](Self::info), since for a relay it
883+
/// comes from upstream.
879884
// TODO: add `fetch` for one-shot retrieval of a past group range.
880885
#[derive(Clone)]
881886
pub struct TrackConsumer {
882887
broadcast: BroadcastConsumer,
883-
name: String,
888+
/// Name-only [`Track`]: `Deref` exposes `name`; the other fields are
889+
/// placeholders until [`Self::info`] resolves the real values.
890+
info: Track,
884891
}
885892

886-
impl TrackConsumer {
887-
/// The track name this handle is bound to.
888-
pub fn name(&self) -> &str {
889-
&self.name
893+
impl Deref for TrackConsumer {
894+
type Target = Track;
895+
896+
fn deref(&self) -> &Self::Target {
897+
&self.info
890898
}
899+
}
891900

901+
impl TrackConsumer {
892902
/// Open a live subscription.
893903
///
894-
/// Returns a [`TrackPending`] that resolves once the publisher accepts the
895-
/// subscription (SUBSCRIBE_OK on the wire). `.await` it for the
896-
/// [`TrackSubscriber`], which carries the publisher's [`Track`] and reads its
897-
/// groups; or drive it with [`TrackPending::poll_ok`] from a poll loop.
904+
/// Returns a [`SubscribePending`] that resolves once the publisher accepts the
905+
/// subscription. `.await` it for the [`TrackSubscriber`], which carries the
906+
/// publisher's [`Track`] and reads its groups; or drive it with
907+
/// [`SubscribePending::poll_ok`] from a poll loop.
898908
///
899909
/// `subscription` is this subscriber's preferences and feeds the producer's
900910
/// [`TrackProducer::subscription`] aggregate; pass `None` for
901911
/// [`Subscription::default`]. Concurrent subscribers to the same name coalesce
902912
/// onto one request.
903-
pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> TrackPending {
913+
pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> SubscribePending {
904914
self.broadcast
905-
.request_subscribe(&self.name, subscription.into().unwrap_or_default())
915+
.request_subscribe(&self.info.name, subscription.into().unwrap_or_default())
906916
}
907917

908-
/// Resolve this track's immutable [`Track`] info without subscribing.
918+
/// Resolve this track's immutable [`Track`] info.
909919
///
910920
/// Returns an [`InfoPending`] that resolves to the publisher's properties
911921
/// (timescale, compression, cache). Warm (cached or a live producer exists) it
912922
/// resolves with no round trip; cold it triggers a single TRACK lookup. Reused
913923
/// across every subscribe and fetch of the track.
914924
pub fn info(&self) -> InfoPending {
915-
self.broadcast.request_info(&self.name)
925+
self.broadcast.request_info(&self.info.name)
916926
}
917927
}
918928

@@ -932,7 +942,7 @@ mod test {
932942
use super::*;
933943

934944
/// Subscribe and assert the result hasn't resolved yet (it stays pending until
935-
/// a publisher accepts). Returns the [`TrackPending`] to resolve after accepting.
945+
/// a publisher accepts). Returns the [`SubscribePending`] to resolve after accepting.
936946
macro_rules! subscribe_pending {
937947
($consumer:expr, $name:expr) => {{
938948
let pending = $consumer.consume_track($name).subscribe(None);

rs/moq-net/src/model/track.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ impl TrackWeak {
754754

755755
/// A live subscription to a track, used to read its groups.
756756
///
757-
/// Created via [`TrackConsumer::subscribe`](crate::TrackConsumer::subscribe), or
757+
/// Created via [`TrackPending::subscribe`](crate::TrackPending::subscribe), or
758758
/// directly from a [`TrackProducer`] for an in-process track. Carries this
759759
/// subscriber's [`Subscription`] preferences, which feed the producer's aggregate.
760760
#[derive(Clone)]

0 commit comments

Comments
 (0)