Repository navigation
Stay closed when disconnect() lands during a full reconnect - #1264
Open
hiroshihorie wants to merge 2 commits into
Open
hiroshihorie wants to merge 2 commits into
hiroshihorie wants to merge 2 commits into
Conversation
restartConnection() checked _isClosed only on entry, then awaited the old peer connections' dispose and the signal cleanup before calling connect(), which cleared _isClosed. A disconnect() in that window was undone: the engine joined again with the old token and kept retrying up to the attempt limit, and a join that landed after the app reconnected the same identity removed the new session with DUPLICATE_IDENTITY. Check _isClosed again once the teardown is done, and connect through a helper that leaves the flag alone, so only a caller's connect() reopens the engine. Fixes #1245
hiroshihorie
requested review from
changt,
cloudwebrtc and
xianshijing-lk
as code owners
October 27, 2026 12:08
disconnect() cancels the signal listeners when it stops a pending reconnect, and only restartConnection() recreated them. A room that disconnected during a reconnect delay, or now during a full reconnect's teardown, could not connect again: the next connect() opened a socket but nothing turned the join response into EngineJoinResponseEvent, so it timed out. connect() now cancels and recreates them before joining.
This was referenced Oct 29, 2026
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
A pending full restart can outlive disconnect and interfere with an immediate reconnect, canceling the new signal listeners and starting another join.
1 open issue
What changed in this PR
This PR addresses #1245 by preventing full reconnect teardown from reopening a disconnected engine, while restoring signal listeners for later explicit connections.
Changes:
- Separates explicit connection setup from the shared connection logic and adds a closed-state check after reconnect teardown.
- Adds regression tests for disconnecting during full reconnects and pending retry delays.
- Adds a patch changeset documenting the fix.
| File | Description |
|---|---|
test/core/disconnect_during_reconnect_test.dart |
Tests disconnect behavior during reconnect and subsequent room reuse. |
lib/src/core/engine.dart |
Separates connection paths, restores listeners, and guards reconnect after teardown. |
.changes/disconnect-during-full-reconnect |
Documents the reconnect fix for release. |
Review effort: Balanced
| _setUpSignalListeners(); | ||
| // disconnect() can run during the teardown above. It closes the engine | ||
| // for good, so do not connect again. | ||
| if (_isClosed) { |
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 #1245.
Problem
restartConnection()checks_isClosedonly on entry. It then awaits the old peer connections' dispose, the signal cleanup and the listener cancel before callingconnect(), andconnect()sets_isClosed = falseunconditionally.A
disconnect()that runs in that window is undone. The engine joins again with the old token and identity, and if that join fails it keeps retrying up to the attempt limit. A join that lands after the app has reconnected the same identity from a newRoommakes the server remove the new, live session withDUPLICATE_IDENTITY.Fix
connect()still reopens the engine for a connect the caller asks for. The body moves into a private_connect()that leaves_isClosedalone, andrestartConnection()uses that. A disconnect that lands while the reconnect is joining therefore stays in effect, and the retry loop stops at its existing_isClosedchecks.restartConnection()checks_isClosedagain once its teardown is done, and returns before opening a new signal connection.connect()cancels and recreates the signal listeners before joining.disconnect()cancels them when it stops a pending reconnect, and onlyrestartConnection()recreated them, so a room that disconnected during a reconnect delay could not connect again. Its nextconnect()opened a socket, but nothing handled the join response and it timed out. The early return above made the same state reachable from a full reconnect.Verification
test/core/disconnect_during_reconnect_test.dart, over the mock transport:disconnect()fromRoomReconnectingEventduring a full reconnect opens no new signal socket, and the same room then connects again. The first part fails on main.disconnect()fromRoomAttemptReconnectEvent, while the retry is still pending, and the same room then connects again. This fails on main, where the connect times out waiting for the join response.livekit-server. It triggers a full reconnect withsendSimulateScenario(fullReconnect: true), disconnects fromRoomReconnectingEvent, and watches the server's participant list for 15 s. Only a session with a new participant SID counts as a rejoin.flutter analyze,flutter test, format and import order are clean.