Conversation
The two tests that simulate a signal resume fake a resume in which the server sends no participant updates at all, which never happens on the wire: the server replays the full roster after the ReconnectResponse. Extract the inline TrackInfo into jakeTrackInfo() and add replayRosterDuringResume() so the simulation matches the real sequence. No assertions change.
A signal resume — unlike a full reconnect — never rebuilds the roster from a JoinResponse, and DISCONNECTED updates for participants who left while the link was down went to a socket we no longer had. Those participants stayed in room.remoteParticipants indefinitely. Migration now resumes rather than fully reconnecting, so this is no longer masked by handleRestarting unwinding every participant. Track the identities seen in participant updates for the duration of a resume and, once it settles, synthesize disconnects for any remote participant absent from that set. The set accumulates a union rather than trusting a single update as the snapshot: the server replays the roster after the ReconnectResponse but can interleave batched updates around it, so no one update is identifiable as the snapshot. Reconciliation runs before updateSubscriptions() and RoomEvent.Reconnected, so we neither resubscribe to departed tracks nor let consumers observe a reconnected room with a stale roster. Mirrors the mechanism in rust-sdks (rtc_session.rs::finish_resume, room/mod.rs::reconcile_absent_participants).
Three tests that fail without the reconciliation: a participant that left while the link was down is removed, identities are accumulated across updates interleaved during the resume rather than taken from a single snapshot, and ParticipantDisconnected is emitted before Reconnected.
Three tests that pass with or without the reconciliation, pinning down its blast radius: participants that join during the resume are kept, a routine participant update outside a resume evicts nobody, and a resume that escalates to a full reconnect does not later apply the abandoned resume partial roster.
🦋 Changeset detectedLatest commit: 4deb05b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
size-limit report 📦
|
| .on(EngineEvent.Resuming, () => { | ||
| this.clearConnectionReconcile(); | ||
| this.isResuming = true; | ||
| this.resumeSeenIdentities = new Set(); |
There was a problem hiding this comment.
🔴 Legacy resumes evict every participant
When an older server omits the roster replay, resumeSeenIdentities stays empty and disconnects every remote participant. Active tracks and application state disappear while those participants remain.
Learn more
A signal resume starts an empty identity set unconditionally. Older LiveKit servers can complete the same resume protocol without sending a full participant update, so an empty set cannot distinguish an empty room from a missing snapshot. The Resumed handler then treats every existing participant as absent and invokes reconcileParticipantsAfterResume, which unpublishes their tracks and emits disconnect events.
Example: Alice and Bob are connected through a server that does not replay participant updates after resume. A brief signal outage completes successfully, but the set remains empty. Alice's SDK disconnects Bob locally even though Bob never left.
Recommended fix: Gate reconciliation on a server capability or minimum server version that guarantees roster replay. If no reliable version boundary exists, add an explicit snapshot-complete signal or carry the authoritative roster in the reconnect response; do not interpret the absence of participant updates as an empty roster.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
This is a good point, I wonder if (since it sounds like this was a behavior change) this needs to be gated on the SFU version? Or applied wholesale for all SFUs irrespective of version?
Warning
This pull request was LLM generated and has only been lightly reviewed by Ryan. @xianshijing-lk had asked I port this fix to the web sdk but I haven't done a deep dive on the specific issue, and beyond running the LLM generated tests have not actually exercised this yet.
A more thorough review of this needs to occur before it could be merged.
The problem
The SDK keeps remote participants that are no longer in the room. This occurs after a signal resume. A node migration or a short signal failure causes a resume.
The server sends a
DISCONNECTEDupdate for each participant that leaves. The SDK does not get this update if the signal connection is down at that time. A full reconnect corrects this, because the SDK builds the roster again from theJoinResponse. A resume does not build the roster again. Therefore the participants stay inroom.remoteParticipantsfor an unlimited time.CLT-3322 changed the migration procedure. A migration now does a resume and not a full reconnect. Before that change, the full reconnect removed all participants and thus hid this defect.
The change
Roomkeeps a record of each identity in the participant updates during a resume. At the end of the resume,Roomremoves each remote participant that is not in that record.Roomsends aParticipantDisconnectedevent for each participant that it removes.The record is a union of all of the updates in the resume. The server sends the full participant list after the
ReconnectResponse. But the server can also send other updates immediately before and after that list. Therefore one update alone is not sufficient.Roomdoes this procedure before it callsupdateSubscriptions()and before it sends theReconnectedevent. Thus the SDK does not subscribe again to the tracks of a participant that left. Thus an application also does not see a stale roster in a reconnected room.Roomdiscards the record if the resume becomes a full reconnect, and also at a disconnect.This change is equivalent to the mechanism in the Rust SDK (
rtc_session.rs::finish_resumeandroom/mod.rs::reconcile_absent_participants).The tests
Room.test.tshas six new tests. Three of these tests fail before the change:ParticipantDisconnectedbeforeReconnected.Three more tests pass before and after the change. These tests show the limits of the new procedure:
subscriberBlackScreen.test.tssimulates a resume in two tests. These simulations did not include a participant update. A server always sends the participant list after a resume. The first commit corrects these two simulations. The assertions stay the same.Attention for the reviewer
The new procedure has no gate on the server version. If a server does not send the participant list after a resume, the SDK removes all remote participants. The Rust SDK has the same behavior. CLT-3323 records this behavior of the server.
The commits
Read the commits in this sequence:
test(e2ee)— correct the two resume simulations. Read this commit quickly.fix(room)— 42 lines. This is the full change in behavior. Give your full attention to this commit.test(room)— the three tests that fail before commit 2.test(room)— the three tests that show the limits.chore— the changeset.Verification
npx tsc --noEmitgives no error.npx vitest run --exclude 'smoke-tests/**'gives 818 tests that pass and 1 test that the runner skips. Thesmoke-tests/suite does not run, because@playwright/testis not available. This suite also does not run onmain.🤖 Generated with Claude Code