Default to a single peer connection - #1113
Open
Conversation
Matches client-sdk-js (`defaults.ts:45`) and rust-sdks (`room/mod.rs:471`), which have both defaulted to single PC for some time. Swift was the only one of the three still opening two peer connections by default, paying two ICE and DTLS handshakes and unable to use any of the v1-only connect optimizations. Measured against LiveKit Cloud staging, 25 iterations per configuration: the post-signal connect phase drops from 291ms to 242ms p50 (-17%) once the publisher offer rides with the JOIN request. Single PC alone accounts for none of that -- 291ms versus 298ms for dual PC -- so this change is only worth anything stacked on offer-with-join, and offer-with-join reaches no one without it. Servers older than LiveKit OSS 1.9.2 do not serve /rtc/v1; the existing serviceNotFound fallback re-runs the connect against the legacy path, so the cost there is one failed request rather than a failed connect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xianshijing-lk
requested review from
hiroshihorie and
pblazej
as code owners
September 4, 2026 23:15
| encryptionOptions = nil | ||
| reportRemoteTrackStatistics = false | ||
| singlePeerConnection = false | ||
| singlePeerConnection = true |
Contributor
There was a problem hiding this comment.
🔴 Default connections intermittently lose data
Default singlePeerConnection enables a mode with reproducible data-channel timeouts. Data messages, RPC calls, and streams can fail for every default connection.
Prompt for agents
Keep the default on the stable dual-peer mode until the intermittent single-peer data-channel timeout is fixed. The failure reproduces in PeerConnectionSignalingTests.dataChannel when singlePeerConnection is true, and this PR makes that path the default for RoomOptions() and the parameterized initializer. Diagnose the channel-open or initial-offer race, add a repeatable regression test, then enable the default only after data messages reliably arrive.
Was this helpful? React with 👍 or 👎 to provide feedback.
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.
Ticket 4 of the connect-latency series. Stacked on #1111 — retarget to
mainonce #1110 and #1111 merge.RoomOptions.singlePeerConnectionhas defaulted tofalsein Swift while client-sdk-js (defaults.ts:45) and rust-sdks (room/mod.rs:471) have both defaulted totrue. Swift was the only one of the three opening two peer connections by default — two ICE and DTLS handshakes, and no access to any v1-only connect optimization.Why this is the change that matters
Benchmarked on LiveKit Cloud staging,
BM-CONN-001/BM-CONN-003, 25 iterations + 5 warmups each:main+ dual PC (ships today)main+ single PCRead
D_TRANSPORT_MS(join_recv → pc_connected);D_WS_MSis pure TLS/WS handshake and is flat across all four, so it is the noise floor.~50 ms (−17%) off the transport phase. That is one round trip, matching the RTT implied by
D_WS_MS≈ 206 ms spanning ~3–4 RTTs. The saving is one RTT, so it scales with client-to-region latency — larger on the high-RTT paths that motivated this work.The row that justifies the stack: single PC alone buys nothing (291 vs 298). The gain is entirely offer-with-join from #1111 — and #1111 only runs on the single-PC path, so without this flip it reaches nobody. Neither PR is worth much without the other.
Compatibility
/rtc/v1requires LiveKit Cloud or LiveKit OSS ≥ 1.9.2. Older servers 404 the endpoint, and the existingserviceNotFoundfallback (Room+Engine.swift) re-runs the connect on the legacy dual-PC path — verified byv1LocalhostFallback, which passes. The cost on an old server is one failed request, not a failed connect.Opt out with
RoomOptions(singlePeerConnection: false).Testing
Against LiveKit Cloud staging:
Against local
livekit-server1.13.1: same suites green.Builds verified on macOS, Mac Catalyst, iOS Simulator.
swiftlintandswiftformat --lintclean. No API signature change — only default values, soapi-checkshould be unaffected.dataChannel(mode: V1 Single PC)intermittently times out atPeerConnectionSignalingTests.swift:281. Measured rate against staging:So this PR does not cause it — it is a pre-existing single-PC data-channel race. But it is the reason to think twice: today almost nobody runs single PC, and this change makes it the path every user takes, so a mode-specific flake becomes a mode-specific production issue.
My recommendation is to root-cause that timeout before or alongside merging this, rather than after. Happy to take it as a follow-up ticket — it looks like a data channel open race on the single-PC path, but I have not diagnosed it.
🤖 Generated with Claude Code