← All issues

[JSC][WASM][Debugger] Fix STW deadlocks when VM blocks in memory.atomic.wait or WebCore operations

da1f31b

Source/WebCore/workers/WorkerSTWParticipation.h

+void waitWithSTWParticipation(BinarySemaphore& semaphore, VM& vm)
+{
+ static constexpr auto kPollInterval = 50_ms;
+ while (!semaphore.waitFor(kPollInterval)) {
+ if (vm.needStopTheWorld())
+ notifyVMStop(vm, VMStopped);
+ }
+}

Source/JavaScriptCore/runtime/WaiterListManager.cpp

- result = waiter.wait(timeout);
+ while (!waiter.waitFor(kDebuggerSTWCheckInterval)) {
+ if (vm.needStopTheWorld())
+ notifyVMStop(vm, WasmAtomicsWaitBlocked); // clearStop() 건너뜀
+ if (MonotonicTime::now() >= deadline)
+ break;
+ }
+ clearStop(vm); // waitForSync() 종료 시에만 초기화됨

WASM debugger는 Stop-The-World(STW) 프로토콜을 통해 모든 VM을 중단시킵니다. 전역 NeedStopTheWorld 플래그가 설정되면, debugger 스레드는 각 VM이 notifyVMStop()을 호출하여 active count를 감소시킬 때까지 대기합니다. JSC는 일반적으로 interpreter loop 안의 trap check point를 통해 이 과정에 참여하게 됩니다.

그런데 memory.atomic.wait 또는 동기 WebCore 작업 내부에서 블로킹된 worker 스레드는 trap check point에 도달하지 못합니다. WebCore 작업은 내부적으로 BinarySemaphore::wait()를 통해 자체 run loop를 구동하기 때문입니다. 결과적으로 이들 스레드는 notifyVMStop()을 호출하지 않고, STW count는 0이 되지 않으며, debugger는 무한히 중단 상태에 빠지게 됩니다.

이 commit은 각 블로킹 지점에 polling 기반의 STW 참여 방식을 도입합니다. 먼저 신규 헬퍼 waitWithSTWParticipation()을 추가하여, 블로킹 중인 지점도 STW에 참여할 수 있도록 했습니다. 아울러 WaiterListManager::waitForSync()도 수정되었습니다. 수정 후에는 50ms 간격으로 polling하며, NeedStopTheWorld가 설정된 경우 notifyVMStop()을 호출합니다. 한편 새로 도입된 WasmAtomicsWaitBlocked callback 타입은 check-in마다 stop state를 초기화하지 않고, 여러 STW 사이클에 걸쳐 상태를 유지합니다. atomics-wait 지점은 대기가 완료되기 전까지 여러 STW 사이클에 걸쳐 진입될 수 있기 때문입니다.

WasmAtomicsWaitBlocked callback은 의도적으로 clearStop()을 건너뜁니다. 이로 인해 stop data는 여러 STW 사이클에 걸쳐 유지됩니다. 종료 경로에서 보상 역할의 clearStop()이 호출되지 않으면, debugger는 실행 중인 스레드에 대해 stale한 PC/CFR/stack 데이터를 참조하게 됩니다.

🔒

New STW participation paths at multiple blocking sites introduce edge cases in state lifetime and epoch ordering — audit directions included.

더 확인하려면 구독해 주세요