From ba03446e62f35821c18bb0a3440a4d877f1e2c9f Mon Sep 17 00:00:00 2001 From: swarnendusahu10-dotcom Date: Tue, 27 Oct 2026 18:45:34 +0530 Subject: [PATCH] fix(api): eliminate typingHandlerLock spinlock and main-thread deadlock Removes the module-scoped typingHandlerLock variable, the synchronous while (typingHandlerLock) {} spinlock loop, and the 2000ms setTimeout fallback. In single-threaded JavaScript runtimes, a synchronous while loop blocks the call stack indefinitely. Because the event loop cannot cycle while the main thread spins, the setTimeout macrotask can never execute to clear the lock. If the lock was ever set or an event arrived re-entrantly, the browser tab entered an unrecoverable freeze with 100% CPU usage. JavaScript event handlers are already executed sequentially on the event loop, making the spinlock both completely redundant and actively dangerous. Also added defensive validation for typingUser to prevent null/empty entries from polluting the typing status array. Fixes #1354 --- packages/api/src/EmbeddedChatApi.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index 6f2408414..d0f1e7314 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -7,8 +7,6 @@ import { ApiError, } from "@embeddedchat/auth"; -// mutliple typing status can come at the same time they should be processed in order. -let typingHandlerLock = 0; export default class EmbeddedChatApi { host: string; rid: string; @@ -448,13 +446,9 @@ export default class EmbeddedChatApi { typingUser: string; isTyping: boolean; }) { - // don't wait for more than 2 seconds. Though in practical, the waiting time is insignificant. - setTimeout(() => { - typingHandlerLock = 0; - }, 2000); - // eslint-disable-next-line no-empty - while (typingHandlerLock) {} - typingHandlerLock = 1; + if (!typingUser) { + return; + } // move user to front if typing else remove it. const idx = this.typingUsers.indexOf(typingUser); if (idx !== -1) { @@ -463,7 +457,6 @@ export default class EmbeddedChatApi { if (isTyping) { this.typingUsers.unshift(typingUser); } - typingHandlerLock = 0; const newTypingStatus = cloneArray(this.typingUsers); this.onTypingStatusCallbacks.forEach((callback) => callback(newTypingStatus)