Main thread blocking during high-volume concurrent requests in ClojureScript
0 reputation · 04 Jan 2022, 12:59 UTC
0 reputation · 04 Jan 2022, 12:59 UTC
ClojureScript inherits the single-threaded event loop architecture of its host environment. When handling concurrent requests, operations are interleaved rather than executed in parallel. This design creates a potential bottleneck when CPU-intensive computations are performed within the main execution path.
While core.async utilizes go blocks to transform synchronous code into state machines that yield control, the underlying JavaScript engine still processes these blocks on a single thread. If a specific operation within a go block does not yield, it can delay the execution of all other pending callbacks and promises in the queue.
There is uncertainty regarding the optimal threshold for yielding control when processing large datasets to prevent UI freezes or server response delays without introducing excessive context-switching overhead.
core.async channel operations under heavy load?33775 reputation · 05 Jan 2022, 00:48 UTC
The main thread blocks because ClojureScript runs on the browser's single‑threaded event loop; any long‑running synchronous work prevents the loop from processing other callbacks, including UI updates and additional request handlers.
Heavy calculations, synchronous XHR/fetch, or blocking DOM operations inside request handlers (or inside go blocks) keep the JavaScript thread busy, so the event loop cannot interleave other pending tasks.
In a JavaScript environment, any code that does not yield control back to the event loop delays all subsequent callbacks, regardless of how many concurrent requests have been started.
js/XMLHttpRequest with fetch or cljs-http) and use .then or async/await.requestAnimationFrame or setTimeout.core.async, audit go blocks for blocking calls and ensure buffers are sized appropriately.Are you invoking any synchronous HTTP libraries or performing blocking JavaScript interop (e.g., alert, synchronous XMLHttpRequest) inside your request handlers? Knowing this determines whether the fix should focus on replacing those calls versus optimizing pure computation.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 04 Jan 2022, 16:20 UTC
To clarify the interaction between core.async and the host environment, it is important to distinguish between synchronous blocking and microtask starvation. While go blocks yield at channel operations, the underlying JavaScript engine prioritizes the microtask queue (where Promises reside) over the macrotask queue (where setTimeout or I/O callbacks reside).
If a high volume of concurrent requests triggers a continuous stream of resolved Promises, the event loop may starve macrotasks, delaying UI renders or core.async operations that rely on timers. To verify if this is occurring, check the Chrome DevTools Performance tab for "Long Tasks" exceeding 50ms. If the main thread is occupied by a dense sequence of small microtasks rather than one large synchronous block, the solution involves manually inserting a macrotask break—such as js/setTimeout—to force the engine to process the rest of the event loop queue.