fix: prevent ghost Room memory leak by stripping listeners before disconnect
LiveKit's Room.disconnect() tears down WebRTC but leaves .on() handlers attached. Every connect() registers ~15 event handlers, which accumulate on orphaned Room instances during rapid channel switches or HMR, causing MaxListenersExceededWarning. Added destroyRoom() helper that calls removeAllListeners() before disconnect() at all four teardown sites.
This commit is contained in:
@@ -107,6 +107,12 @@ function parseIdentity(identity: string): { userId: string; username: string } {
|
|||||||
|
|
||||||
let _connectGeneration = 0;
|
let _connectGeneration = 0;
|
||||||
|
|
||||||
|
/** Strip all listeners then disconnect — prevents ghost Room memory leaks. */
|
||||||
|
function destroyRoom(room: Room | null): Promise<void> | void {
|
||||||
|
if (!room) return;
|
||||||
|
room.removeAllListeners();
|
||||||
|
return room.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
export function useLiveKit() {
|
export function useLiveKit() {
|
||||||
const [room, setRoom] = useState<Room | null>(null);
|
const [room, setRoom] = useState<Room | null>(null);
|
||||||
@@ -314,8 +320,8 @@ export function useLiveKit() {
|
|||||||
|
|
||||||
if (roomToDisconnect) {
|
if (roomToDisconnect) {
|
||||||
try {
|
try {
|
||||||
console.log('[LiveKit] Disconnecting previous room:', roomToDisconnect.name);
|
console.log('[LiveKit] Destroying previous room:', roomToDisconnect.name);
|
||||||
await roomToDisconnect.disconnect();
|
await destroyRoom(roomToDisconnect);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Error disconnecting from previous room:', err);
|
console.warn('Error disconnecting from previous room:', err);
|
||||||
}
|
}
|
||||||
@@ -428,7 +434,7 @@ export function useLiveKit() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await newRoom.connect(url, token);
|
await newRoom.connect(url, token);
|
||||||
if (gen !== _connectGeneration) { newRoom.disconnect(); return; }
|
if (gen !== _connectGeneration) { destroyRoom(newRoom); return; }
|
||||||
_activeRoom = newRoom;
|
_activeRoom = newRoom;
|
||||||
connectedChannelRef.current = storedId;
|
connectedChannelRef.current = storedId;
|
||||||
setConnectedChannelId(storedId);
|
setConnectedChannelId(storedId);
|
||||||
@@ -469,7 +475,7 @@ export function useLiveKit() {
|
|||||||
connectedChannelRef.current = null;
|
connectedChannelRef.current = null;
|
||||||
setConnectedChannelId(null);
|
setConnectedChannelId(null);
|
||||||
if (roomRef.current) {
|
if (roomRef.current) {
|
||||||
await roomRef.current.disconnect();
|
await destroyRoom(roomRef.current);
|
||||||
roomRef.current = null;
|
roomRef.current = null;
|
||||||
_activeRoom = null;
|
_activeRoom = null;
|
||||||
setRoom(null);
|
setRoom(null);
|
||||||
@@ -555,7 +561,7 @@ export function useLiveKit() {
|
|||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => { _connectGeneration++; SpeakingDetector.getInstance().clear(); if (roomRef.current) { roomRef.current.disconnect(); roomRef.current = null; _activeRoom = null; } };
|
return () => { _connectGeneration++; SpeakingDetector.getInstance().clear(); if (roomRef.current) { destroyRoom(roomRef.current); roomRef.current = null; _activeRoom = null; } };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user