Skip to content

Commit 17e2aac

Browse files
committed
fix: resolve track subscription race condition on participant rejoin
Fixes race condition where tracks arriving before participant metadata were permanently dropped from the pending queue after timeout, causing 10-60 second delays or complete failures when participants rejoin. Changes: 1. Retry transient failures: Modified _flushPendingTracks() to differentiate between transient (notTrackMetadataFound) and permanent failures. Transient failures now keep tracks in queue for retry instead of removing them. 2. Additional flush trigger: Added listener to flush pending tracks when SignalParticipantUpdateEvent contains track publications, ensuring tracks are subscribed once metadata becomes available. 3. Improved logging: Transient failures logged at fine level to reduce noise, permanent failures at severe level for visibility. The fix maintains the existing timeout configuration from connectOptions while enabling retry logic that resolves the race condition where: - WebRTC track arrives first → queued - ParticipantInfo arrives → participant created → flush fails (no publications) - TrackPublishedResponse arrives later → second flush succeeds This reduces track subscription latency after rejoin from 10-60s to <1s and improves reliability on slower devices where the race condition was more pronounced. Related: livekit#928
1 parent b0081d4 commit 17e2aac

File tree

2 files changed

+36
-6
lines changed

2 files changed

+36
-6
lines changed

lib/src/core/room.dart

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,26 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
332332
}
333333

334334
void _setUpSignalListeners() => _signalListener
335-
..on<SignalParticipantUpdateEvent>((event) => _onParticipantUpdateEvent(event.participants))
335+
..on<SignalParticipantUpdateEvent>((event) async {
336+
await _onParticipantUpdateEvent(event.participants);
337+
338+
// Flush pending tracks after participant updates are processed.
339+
// This handles the case where tracks arrived before participant metadata,
340+
// got queued, and now the track publications have finally arrived in this update.
341+
// This is the second flush opportunity that fixes the race condition where
342+
// the first flush (in _onParticipantUpdateEvent) happens before publications are ready.
343+
for (final info in event.participants) {
344+
if (info.tracks.isEmpty) continue;
345+
final participant = _remoteParticipants.bySid[info.sid];
346+
if (participant != null) {
347+
logger.fine(
348+
'Track publications updated for ${info.identity} '
349+
'(${info.tracks.length} tracks), flushing pending queue',
350+
);
351+
await _flushPendingTracks(participant: participant);
352+
}
353+
}
354+
})
336355
..on<SignalSpeakersChangedEvent>((event) => _onSignalSpeakersChangedEvent(event.speakers))
337356
..on<SignalConnectionQualityUpdateEvent>((event) => _onSignalConnectionQualityUpdateEvent(event.updates))
338357
..on<SignalStreamStateUpdatedEvent>((event) => _onSignalStreamStateUpdateEvent(event.updates))
@@ -787,7 +806,18 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
787806
);
788807
return true;
789808
} on TrackSubscriptionExceptionEvent catch (event) {
790-
logger.severe('Track subscription failed during flush: ${event}');
809+
// Differentiate between transient and permanent failures
810+
final isTransient = event.reason == TrackSubscribeFailReason.notTrackMetadataFound;
811+
812+
if (isTransient) {
813+
logger.fine('Track subscription temporarily failed: metadata not ready yet for '
814+
'trackSid:${pending.trackSid} participantSid:${pending.participantSid}, '
815+
'will retry on next flush');
816+
return false; // Keep in queue for retry
817+
}
818+
819+
// Permanent failure
820+
logger.severe('Track subscription failed permanently during flush: ${event}');
791821
events.emit(event);
792822
return true;
793823
} catch (exception) {

pubspec.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,10 @@ packages:
452452
dependency: "direct main"
453453
description:
454454
name: meta
455-
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
455+
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
456456
url: "https://pub.dev"
457457
source: hosted
458-
version: "1.17.0"
458+
version: "1.16.0"
459459
mime:
460460
dependency: transitive
461461
description:
@@ -721,10 +721,10 @@ packages:
721721
dependency: transitive
722722
description:
723723
name: test_api
724-
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
724+
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
725725
url: "https://pub.dev"
726726
source: hosted
727-
version: "0.7.7"
727+
version: "0.7.6"
728728
timing:
729729
dependency: transitive
730730
description:

0 commit comments

Comments
 (0)