-
Notifications
You must be signed in to change notification settings - Fork 143
Description
Summary
BroadcastConsumer::subscribe_track() in moq-lite/src/model/broadcast.rs returns cached TrackProducer objects without checking if they are closed/dead. If a publisher disconnects, the stale TrackProducer remains in the HashMap<String, TrackProducer> cache and is returned to new subscribers, who then receive stale data or hang.
Root Cause
In subscribe_track() (line ~195):
if let Some(producer) = state.producers.get(&track.name) {
return producer.consume(); // No liveness check before returning
}The existing unused() cleanup task (lines 218-223) only removes the producer when ALL consumers are dropped, which requires every consumer to release their handle. If even one consumer holds a reference, the dead producer stays cached permanently.
Impact
- Late-joining subscribers may receive stale/corrupt data from dead publisher connections
- Could cause silent corruption where frames appear to decode successfully but contain stale content
- Manifests as "works initially, breaks after publisher reconnect" pattern
Suggested Fix
This matches cloudflare/moq-rs PR #135 which fixed the equivalent bug in their implementation.
- Add
is_closed()toTrackProducer:
impl TrackProducer {
pub fn is_closed(&self) -> bool {
// Check if the track's state indicates closure
self.state.borrow().closed.is_some()
}
}- Check liveness before returning cached producer:
if let Some(producer) = state.producers.get(&track.name) {
if !producer.is_closed() {
return producer.consume();
}
// Stale — evict and fall through to create fresh
state.producers.remove(&track.name);
}Context
Found while investigating silent video pixelation in a MoQ subscriber client. The requested_unused test (line ~399) already documents related behavior with a sleep workaround.
Filed from egui-vid/lumina-video which uses moq-lite + hang for live H.264 streaming.