VMTraps Deadlock Fix in Suspended-Thread Lambda
Source/JavaScriptCore/runtime/VMTraps.cpp
VMTraps is JSC's mechanism for asynchronously interrupting a running JavaScript VM, used by the debugger, GC stop-the-world, and watchdog timers. SignalSender::work() suspends the target VM thread via mach thread_suspend(), then executes a lambda while the thread is frozen to install trap breakpoints or verify ownership. Any operation inside that lambda must be effectively async-signal-safe — it must not attempt to acquire any lock that the suspended thread might be holding at an arbitrary point in its execution. Thread reference counting (RefPtr<Thread>) goes through a ThreadSafeWeakPtrControlBlock protected by a WordLock, making even an innocent-looking ownerThread() call a potential deadlock source.
T5 (VM thread) T2 (SignalSender)
│ │
├─ strongDeref() │
│ └─ acquire WordLock ──► LOCK HELD
│ [suspended here] ◄── thread_suspend(T5)
│ │
│ ├─ ownerThread()
│ │ └─ strongRef()
│ │ └─ acquire WordLock
│ │ └─ DEADLOCK
The fix replaces ownerThread() with ownerThreadUID(), which reads the thread UID directly from the stored pointer with no RefPtr copy and no WordLock acquisition. The expectedUID is captured before suspension and compared inside the lambda.
Significance
This was causing full cascade hangs in JSC test infrastructure on arm64 debug builds — other VMs queued behind the deadlocked SignalSender on the serial WorkQueue would never complete, and their ~VM() destructors would block indefinitely on waitForCompletion(). The underlying pattern — acquiring a lock while a thread is suspended holding that same lock — is a general hazard in any code that runs inside mach thread_suspend() callbacks.