From ff1f6242c7f4a6359a29349ede6fd11ceb5411b9 Mon Sep 17 00:00:00 2001 From: Adrian Niculescu <15037449+adrian-niculescu@users.noreply.github.com> Date: Wed, 7 Oct 2026 00:40:53 +0300 Subject: [PATCH] fix(worker): let a heap-limit terminate re-enter the inspector lock from console.log A worker's console.log holds inspectorMutex_ across the inspector's consoleLog, which captures a stack trace and so allocates. When that allocation hits the worker's heap cap, the near-heap-limit callback calls Terminate(), which takes the same non-recursive mutex on the same thread and deadlocks the worker. The mutex is recursive now, so that re-entry proceeds while other threads stay excluded as before. --- test-app/runtime/src/main/cpp/WorkerWrapper.cpp | 8 ++++---- test-app/runtime/src/main/cpp/WorkerWrapper.h | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp index 04abc68fd..f06b2c385 100644 --- a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp @@ -170,7 +170,7 @@ void WorkerWrapper::Terminate() { // A worker paused at a breakpoint sits in the inspector's nested pause // loop, not in Looper.loop() - kick it loose so TerminateExecution and // the looper quit below can take effect. - std::lock_guard lock(inspectorMutex_); + std::lock_guard lock(inspectorMutex_); if (inspector_ != nullptr) { inspector_->NotifyTerminating(); } @@ -810,7 +810,7 @@ void WorkerWrapper::CreateInspector(Isolate* isolate) { auto* client = new WorkerInspectorClient(workerId_, isolate, ALooper_forThread(), url); { - std::lock_guard lock(inspectorMutex_); + std::lock_guard lock(inspectorMutex_); inspector_ = client; } @@ -822,7 +822,7 @@ void WorkerWrapper::CreateInspector(Isolate* isolate) { void WorkerWrapper::DestroyInspector() { WorkerInspectorClient* client = nullptr; { - std::lock_guard lock(inspectorMutex_); + std::lock_guard lock(inspectorMutex_); client = inspector_; inspector_ = nullptr; } @@ -843,7 +843,7 @@ void WorkerWrapper::DestroyInspector() { void WorkerWrapper::ConsoleLog(v8_inspector::ConsoleAPIType method, const std::vector>& args) { - std::lock_guard lock(inspectorMutex_); + std::lock_guard lock(inspectorMutex_); if (inspector_ != nullptr) { inspector_->consoleLog(method, args); } diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.h b/test-app/runtime/src/main/cpp/WorkerWrapper.h index 5dd3b2fa8..0f3f351cf 100644 --- a/test-app/runtime/src/main/cpp/WorkerWrapper.h +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.h @@ -238,7 +238,10 @@ class WorkerWrapper : public std::enable_shared_from_this { void DestroyInspector(); WorkerInspectorClient* inspector_ = nullptr; - std::mutex inspectorMutex_; + // Recursive: ConsoleLog holds it across consoleLog, whose stack capture + // allocates, and a near-heap-limit callback raised by that allocation + // calls Terminate(), which takes it again on the same thread. + std::recursive_mutex inspectorMutex_; #endif static std::mutex registryMutex_;