[JSC][WASM][Debugger] Fix STW deadlocks when VM blocks in memory.atomic.wait or WebCore operations
Source/WebCore/workers/WorkerSTWParticipation.h
Source/JavaScriptCore/runtime/WaiterListManager.cpp
The WASM debugger halts all VMs using a Stop-The-World protocol: a global NeedStopTheWorld flag is set and the debugger thread waits until every participating VM decrements an active count by calling notifyVMStop(). JSC normally achieves this through trap check points embedded in the interpreter loop. Worker threads blocked inside memory.atomic.wait or synchronous WebCore operations (which drive their own run loop internally via BinarySemaphore::wait()) never reach a trap check point, so they never call notifyVMStop() — the STW count never reaches zero and the debugger hangs forever.
This commit adds polling-based STW participation at each blocking site via a new waitWithSTWParticipation() helper and modifies WaiterListManager::waitForSync() to poll every 50ms, calling notifyVMStop() when NeedStopTheWorld is set. A new WasmAtomicsWaitBlocked callback type preserves stop state across multiple STW cycles rather than clearing it on each check-in, because the atomics-wait site may be entered across multiple STW cycles before the wait completes.
Significance
The WasmAtomicsWaitBlocked callback deliberately skips clearStop() so stop data persists across multiple STW cycles; any error path that fails to call the compensating clearStop() on exit leaves the debugger looking at stale PC/CFR/stack data for a live, running thread.
Audit directions
WasmAtomicsWaitBlockedexit-path coverage. Every error path or early-exit inwaitForSync()must reach the compensatingclearStop(). A missed branch lets stop data persist stale into the next STW cycle, corrupting the debugger's view of thread state.- STW-epoch race at each polling site. Between the
NeedStopTheWorldcheck and the return fromwaitWithSTWParticipation()/waitForSync(), a new STW request can arrive. If the blocking operation completes and the VM re-enters JS before the next poll, it may miss an STW cycle entirely or callnotifyVMStop()against the wrong epoch counter. resumeAll()instepAtBytecode()for the atomics-wait case. Stepping over an atomics-wait usesresumeAll()so notifier threads can run. If the step target and notifier threads interact incorrectly (e.g., notifier wakes the waiter before the debugger finishes setting up the next breakpoint), the thread may execute past the intended stop point.- Assembly slow-path stop-data threading in
InPlaceInterpreter64.asm. Stop data (callee, CFR, PC, MC, stack) is now threaded through the IPInt slow-path stack frame for the atomics-wait case. A mismatch between what the asm pushes and what the C++ slow path reads as stop data could expose the wrong PC/stack to the debugger. DebugServer::start()idempotency. The bug allowed re-entrantstart()calls to corruptm_serverSocket. The guard isisInService(); verify it has no TOCTOU window reachable from a worker VM that races through per-VM init.