Skip to content

Commit 86a24a3

Browse files
committed
fix: Prevent memory leaks in user disconnect handling
Address PR feedback regarding potential memory leaks with debounced user disconnect notifications. Implement proper timeout reference tracking and cleanup to ensure all resources are properly released when component unmounts.
1 parent cdb4b40 commit 86a24a3

File tree

1 file changed

+23
-4
lines changed

1 file changed

+23
-4
lines changed

frontend/src/collaboration/hooks/useCollabEditor.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const getRandomName = (): string => {
2222
return `${adjective} ${animal}`;
2323
};
2424

25+
26+
2527
const handleUsersAdded = (
2628
added: number[],
2729
currentUserId: number,
@@ -54,7 +56,8 @@ const handleUsersRemoved = (
5456
removed: number[],
5557
currentUserId: number,
5658
userNamesMap: Map<number, string>,
57-
recentlyRemovedSet: Set<number>
59+
recentlyRemovedSet: Set<number>,
60+
timeoutRefsMap: Map<number, NodeJS.Timeout>
5861
): void => {
5962
removed.forEach((clientId) => {
6063
if (clientId === currentUserId) {
@@ -74,10 +77,18 @@ const handleUsersRemoved = (
7477
icon: '👋',
7578
});
7679

80+
// Clears any existing timeout for this client (prevents duplicates)
81+
if (timeoutRefsMap.has(clientId)) {
82+
clearTimeout(timeoutRefsMap.get(clientId));
83+
}
84+
7785
// Clears debounce flag after timeout to allow future notifications
78-
setTimeout(() => {
86+
const timeoutId = setTimeout(() => {
7987
recentlyRemovedSet.delete(clientId);
88+
timeoutRefsMap.delete(clientId);
8089
}, USER_CONFIG.DUPLICATE_TOAST_DEBOUNCE_MS);
90+
91+
timeoutRefsMap.set(clientId, timeoutId);
8192
});
8293
};
8394

@@ -90,7 +101,7 @@ export default function useCollabEditor({roomId}: {roomId: string}) {
90101
const isFirstChangeRef = useRef<boolean>(true);
91102
const userNamesRef = useRef<Map<number, string>>(new Map());
92103
const recentlyRemovedRef = useRef<Set<number>>(new Set());
93-
104+
const timeoutRefs = useRef(new Map<number, NodeJS.Timeout>());
94105

95106
useEffect(() => {
96107
console.log('useEffect RUNNING for room:', roomId);
@@ -157,7 +168,10 @@ export default function useCollabEditor({roomId}: {roomId: string}) {
157168
console.log('Processing awareness change (not initial sync), added:', added, 'removed:', removed);
158169

159170
handleUsersAdded(added, currUserId, provider.awareness, userNamesRef.current);
160-
handleUsersRemoved(removed, currUserId, userNamesRef.current, recentlyRemovedRef.current);
171+
console.log(timeoutRefs.current);
172+
handleUsersRemoved(removed, currUserId, userNamesRef.current, recentlyRemovedRef.current, timeoutRefs.current);
173+
console.log(timeoutRefs.current);
174+
161175
};
162176

163177
provider.awareness.on('change', awarenessChangeHandler);
@@ -170,6 +184,11 @@ export default function useCollabEditor({roomId}: {roomId: string}) {
170184
userNamesRef.current.clear();
171185
recentlyRemovedRef.current.clear();
172186

187+
timeoutRefs.current.forEach((timeoutId) => {
188+
clearTimeout(timeoutId);
189+
});
190+
timeoutRefs.current.clear();
191+
173192
setIsReady(false);
174193
if (provider.awareness) {
175194
provider.awareness.setLocalState(null);

0 commit comments

Comments
 (0)