fix: set stopReconnection before closeRTCPeer in stop() to prevent race condition (OPTI-2440) - #517
Open
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
…ce condition OPTI-2440: Move stopReconnection = true before closeRTCPeer() call in BaseWebRTC.stop() so that any synchronous connectionStateChange events emitted during peer teardown see the guard flag and do not trigger an unintended reconnection attempt. Co-Authored-By: craig.johnston <cjohn@dolby.com>
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
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.
Summary
Reorders
BaseWebRTC.stop()so thatthis.stopReconnection = trueis set before callingthis.webRTCPeer.closeRTCPeer(), rather than after.closeRTCPeer()can synchronously emitconnectionStateChangeevents (PeerConnection.js line 83 explicitly emits'closed', and the nativepeer.close()may fireonconnectionstatechangewith'disconnected'depending on browser implementation). ThesetReconnect()listener processes these events and can callreconnect(), which checks!this.stopReconnectionas a guard. With the old ordering,stopReconnectionwas stillfalsewhen these events fired, potentially allowing an unintended reconnect attempt after an explicitstop().The trigger requires:
autoReconnect=true, a prior'disconnected'event having setalreadyDisconnected=true, andfirstReconnection=true— a narrow but realistic scenario (e.g., user callsstop()shortly after a network flap).Jira: OPTI-2440
Teardown Safety Verification
All code paths triggered during
stop()were traced to confirm nothing readsstopReconnectionduring teardown and expects it to befalse:stopReconnection?closeRTCPeer()→peer?.close()onconnectionstatechangesynchronously on the PeerConnection EventEmittercloseRTCPeer()→this.peer = nullcloseRTCPeer()→this.stopStats()peerConnectionStatscloseRTCPeer()→emit('closed')'closed'— does NOT match'failed'or'disconnected'conditions (line 104/107), falls toelsebranch which just setsalreadyDisconnected = falsesignaling.close()WebSocket.close(), which firesconnectionClose— the reconnect listener only listens forconnectionError, notconnectionCloseView.stop()/Publish.stop()overridessuper.stop()first, then clear DRM maps / terminate workers — none referencestopReconnectionAll 6 references to
stopReconnectionacross the codebase were checked:false) — not during teardownstop()setstrue— this is our fixreconnect()guard:!this.stopReconnection— the only consumer;trueearlier is strictly saferinitConnection()resets tofalse— runs duringconnect(), not teardowntrueon 401 error — not during teardownConclusion: The reorder is safe. No downstream teardown code depends on
stopReconnectionbeingfalse.Review & Testing Checklist for Human
stopReconnectionbeingfalse— Traced above. All teardown paths throughcloseRTCPeer(),signaling.close(), and subclass overrides confirmed safe.PeerConnection.closeRTCPeer()→addPeerEventslisteners →BaseWebRTC.setReconnect()→BaseWebRTC.reconnect()is confirmed. Whether browsers fire'disconnected'synchronously duringpeer.close()is implementation-dependent, but the SDK's ownemit('closed')on line 83 is unconditionally synchronous.RTCPeerConnection.close()does not fireonconnectionstatechange, so this race is not covered by the test suite. A test that makes the mock emit'disconnected'synchronously duringclose()would guard against future regressions.connectionStateChangehandlers registered outside ofsetReconnect()(e.g., the migration handler in Publish.js:271-277, which only fires on'connected'/'disconnected'/'failed'/'closed'and just closes old instances — safe).Notes
PeerConnection.closeRTCPeer()→addPeerEventslisteners →BaseWebRTC.setReconnect()→BaseWebRTC.reconnect().asynckeyword oncloseRTCPeer()is misleading — the method body is entirely synchronous. This doesn't affect the fix but is worth noting.stop()call insidereconnect()(line 127) is harmless with the new ordering — settingstopReconnection = trueagain is idempotent.Link to Devin session: https://dolby.devinenterprise.com/sessions/56aeae174c444e3ab952f7b1b8257c91
Requested by: @craig-johnston