Skip to content

Commit 99e79c7

Browse files
authored
Fix data channel close race condition (#1978)
1 parent 4d13aff commit 99e79c7

2 files changed

Lines changed: 35 additions & 8 deletions

File tree

.changeset/gold-candies-switch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'livekit-client': patch
3+
---
4+
5+
Fix data channel close race condition

src/room/RTCEngine.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -456,18 +456,25 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
456456
}
457457

458458
async cleanupPeerConnections() {
459-
await this.pcManager?.close();
460-
this.pcManager = undefined;
461-
462459
const dcCleanup = (dc: RTCDataChannel | undefined) => {
463-
if (!dc) return;
464-
dc.close();
460+
if (!dc) {
461+
return;
462+
}
463+
464+
// Detach the data channel handlers before closing anything. Closing a peer connection tears
465+
// down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
466+
// channels; if our handlers are still attached at that point, handleDataError logs a spurious
467+
// "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
468+
// before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
469+
// browser dispatches those teardown events. See livekit/client-sdk-js#1953.
465470
dc.onbufferedamountlow = null;
466471
dc.onclose = null;
467472
dc.onclosing = null;
468473
dc.onerror = null;
469474
dc.onmessage = null;
470475
dc.onopen = null;
476+
477+
dc.close();
471478
};
472479
dcCleanup(this.lossyDC);
473480
dcCleanup(this.lossyDCSub);
@@ -476,6 +483,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
476483
dcCleanup(this.dataTrackDC);
477484
dcCleanup(this.dataTrackDCSub);
478485

486+
await this.pcManager?.close();
487+
this.pcManager = undefined;
488+
479489
this.lossyDC = undefined;
480490
this.lossyDCSub = undefined;
481491
this.reliableDC = undefined;
@@ -1038,12 +1048,24 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
10381048
};
10391049

10401050
private handleDataError = (event: Event) => {
1051+
// Errors fired while we're tearing the connection down (e.g. the SCTP transport aborting as
1052+
// the peer connection closes) carry no actionable information — the channel is going away
1053+
// regardless. Suppress them so a graceful disconnect doesn't surface spurious errors.
1054+
// See livekit/client-sdk-js#1953.
1055+
if (this._isClosed) {
1056+
return;
1057+
}
1058+
10411059
const channel = event.currentTarget as RTCDataChannel;
10421060
const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
10431061

1044-
if (event instanceof ErrorEvent && event.error) {
1045-
const { error } = event.error;
1046-
this.log.error(`DataChannel error on ${channelKind}: ${event.message}`, { error });
1062+
if (typeof RTCErrorEvent !== 'undefined' && event instanceof RTCErrorEvent && event.error) {
1063+
const { error } = event;
1064+
this.log.error(`DataChannel error on ${channelKind}: ${error.message}`, {
1065+
error,
1066+
errorDetail: error.errorDetail,
1067+
sctpCauseCode: error.sctpCauseCode,
1068+
});
10471069
} else {
10481070
this.log.error(`Unknown DataChannel error on ${channelKind}`, { event });
10491071
}

0 commit comments

Comments
 (0)