Skip to content

Refuse addTransceiver on a closed Transport - #1143

Merged
pblazej merged 3 commits into
mainfrom
blaze/addtransceiver-after-close
Oct 6, 2026
Merged

pblazej merged 3 commits into
mainfrom
blaze/addtransceiver-after-close

Conversation

@pblazej

@pblazej pblazej commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Publishing a track while a disconnect or full reconnect closes the publisher peer connection can call addTransceiver on the already-closed connection. Since LiveKitWebRTC m150, PeerConnection::AddTransceiver has no closed-state check and eagerly builds the new transceiver's media channels from the Call object that close() already freed, so the app crashes intermittently on WebRTC's signaling_thread, depending on whether that memory has been reused.

This is an SDK-side workaround for the WebRTC bug, which is fixed at the root in webrtc-sdk/webrtc#301: Transport.close() now sets a closed flag before anything else, and both addTransceiver variants throw LiveKitError(.invalidState) once it is set, so the call never reaches the closed peer connection.

TransportCloseTests adds a transceiver (track and media-type variants, audio and video) to a closed publisher transport and expects that error. It fails without the fix (first commit) and, on m150, crashes the process at the same signaling-thread frame under ASan or Guard Malloc; with the fix it passes 20/20, including under both.

Crash stack (LiveKitWebRTC 150.7871.02, iOS arm64, re-symbolicated from image offsets)
Exception Type: EXC_BAD_ACCESS (SIGSEGV) KERN_INVALID_ADDRESS
Thread "signaling_thread" crashed:
0  webrtc::WebRtcVoiceSendChannel::WebRtcVoiceSendChannel       +0x3c4fc8   call->network_thread() via freed Call vtable
1  webrtc::WebRtcVoiceEngine::CreateSendChannel                 +0x3c4be0
2  webrtc::(anonymous namespace)::CreateMediaContentChannels    +0x81e1cc
3  webrtc::RtpTransceiver::RtpTransceiver                       +0x81da48
4  webrtc::RtpTransmissionManager::CreateAndAddTransceiver      +0x844190
5  webrtc::PeerConnection::AddTransceiver(MediaType, track, init, bool)   +0x7d63e8
6  webrtc::PeerConnection::AddTransceiver(track, init)          +0x7d4b28
7  webrtc::MethodCall<PeerConnectionInterface, ...>::Invoke     +0x7853e4   (proxy)
8  webrtc::MethodCall<PeerConnectionInterface, ...>::Marshal    +0x785460   (proxy lambda)
9  webrtc::Thread::Dispatch                                     +0x1cada4

Blocked caller thread:
   webrtc::Event::Wait                                          (proxy BlockingCall)
   -[LKRTCPeerConnection addTransceiverWithTrack:init:]
   Transport.addTransceiver(with:transceiverInit:)
   LocalParticipant._publish (negotiateFunc)

pblazej and others added 2 commits September 30, 2026 22:53
libwebrtc accepts addTransceiver on a closed peer connection. Since
m150 it builds the transceiver's media channels immediately, reading
the Call object that close() already freed, which crashes on the
signaling thread when that memory has been reused.

The test adds a transceiver (track and media-type variants, audio and
video) to a closed publisher Transport and expects
LiveKitError(.invalidState). It fails as an assertion on a plain run
and crashes the process under Address Sanitizer or Guard Malloc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: omnigent <noreply@omnigent.ai>
Transport.close() now marks the transport closed before anything else,
and both addTransceiver variants throw LiveKitError(.invalidState)
from then on instead of reaching the closed peer connection. A publish
racing a disconnect or reconnect could otherwise add a transceiver
after close(), which since WebRTC m150 reads the peer connection's
freed Call on the signaling thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: omnigent <noreply@omnigent.ai>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@pblazej
pblazej merged commit 5ed5e40 into main Oct 6, 2026
56 of 57 checks passed
@pblazej
pblazej deleted the blaze/addtransceiver-after-close branch October 6, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants