Repository navigation
Fix stale and overlapping remote participant updates - #1253
Open
hiroshihorie wants to merge 3 commits into
Open
hiroshihorie wants to merge 3 commits into
hiroshihorie wants to merge 3 commits into
Conversation
Participant.updateFromInfo returns false when the incoming info has an older version for the same participant sid, but the remote override had its early return commented out. The stale info then went on to reconcile tracks and removed every publication that only the newer info contained. Return early like LocalParticipant and the JS SDK already do. The server only increases the version for a given sid and a rejoin gets a new sid, so this never drops a legitimate update.
The room signal listener is not synchronized, so each ParticipantUpdate runs _onParticipantUpdateEvent concurrently with the ones before it. Right after joining this is the common case: the server sends the JoinResponse and then flushes the updates it queued, and every one of them parks on the RoomConnectedEvent wait and resumes at once. Their track reconciliation then interleaves across awaits, and an older update removes the publication a newer one added. The same window lets two updates for a new participant both miss the lookup in _getOrCreateRemoteParticipant and create two instances, each with its own ParticipantConnectedEvent. Run JoinResponse participant creation and ParticipantUpdate handling under one lock. Pending track flushing moves out of the locked section because addSubscribedMediaTrack can wait for a later update to publish the track, which would otherwise block on the lock until the publish timeout.
hiroshihorie
marked this pull request as ready for review
October 22, 2026 21:14
hiroshihorie
requested review from
changt,
cloudwebrtc and
xianshijing-lk
as code owners
October 22, 2026 21:14
cloudwebrtc
approved these changes
Oct 23, 2026
This branch has not been deployed
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.
Fixes #1248.
Two races let a remote participant end up without tracks the server says it has.
Stale updates were applied.
Participant.updateFromInforejects an info with an older version for the same sid, butRemoteParticipanthas had its early return commented out since #435. A stale info still reconciled tracks and removed every publication that only the newer info had. It now returns early, likeLocalParticipantand the JS SDK (livekit/client-sdk-js#663). The server only raises the version for a given sid and a rejoin gets a new sid, so no legitimate update is dropped.Updates were applied concurrently. The room signal listener is not synchronized, so
ParticipantUpdatehandlers overlap across awaits. Right after joining, every update that arrives while the engine sets up its peer connections waits forRoomConnectedEvent, and they all resume together. Later on, frames read from the socket together are dispatched before either handler finishes. This has two outcomes:RemoteParticipants, each with its ownParticipantConnectedEvent. The app keeps the first instance, which the room no longer updates.Participant creation from the JoinResponse and
ParticipantUpdatehandling now run under one lock. Pending track flushing runs after the lock is released, becauseaddSubscribedMediaTrackcan wait for a later update to publish the track.Testing
test/core/participant_update_test.dartdrives the room through the mock websocket. It covers four cases:[], then v3[A], v4[A], v5[A, B]queued behind the JoinResponseThe first three fail on main. The last one fails if flushing moves inside the lock.
flutter testpasses andflutter analyzeis clean.E2E against
livekit-server --dev1.13.8. Threelkpublishers keep joining and publishing two tracks. Meanwhile a subscriber joins 60 times and checks every remote participant against the newest ParticipantInfo the server sent.ParticipantConnectedEvents, and 9 times the room holds a different instance than the event delivered.