From 2421b74526533036ebe46f4b1239f43bec074c1a Mon Sep 17 00:00:00 2001 From: Adrian Niculescu <15037449+adrian-niculescu@users.noreply.github.com> Date: Wed, 7 Oct 2026 00:32:56 +0300 Subject: [PATCH 1/2] fix(worker): tear down a worker runtime whose Java bootstrap failed When initWorkerRuntime built the native runtime and then failed in Java, for example because internal/ts_helpers.js threw, it threw before returning the runtime id, so the worker never took ownership and the native runtime and its isolate were never disposed. Worker shutdown now takes that runtime from the thread's current one and tears it down like any other. --- test-app/runtime/src/main/cpp/WorkerWrapper.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp index 04abc68fd..0084370e4 100644 --- a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp @@ -617,6 +617,14 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr self) { isTerminating_ = true; + // A bootstrap that failed in Java after the native runtime was built (an + // exception from internal/ts_helpers.js, for one) never handed it back: + // initWorkerRuntime threw before returning its id. It is still this + // thread's current runtime, so it is torn down below like any other. + if (runtime_ == nullptr) { + runtime_ = Runtime::GetCurrentRuntime(); + } + // Terminate any workers this worker created (nested workers). Their // Worker object persistents live in this isolate, so they must be // released before the isolate is disposed below. Each child cascades to From 84d8a992ed2c7e3d9ede85baa8c3417848906a13 Mon Sep 17 00:00:00 2001 From: Adrian Niculescu <15037449+adrian-niculescu@users.noreply.github.com> Date: Wed, 7 Oct 2026 00:43:57 +0300 Subject: [PATCH 2/2] fix(worker): detach a recovered worker runtime from Java by its own id The bootstrap can also fail after the Java runtime finished initializing, when the logger throws on its last line, and the Java side then still holds the runtime and its GC subscription. Detaching with the id the call never returned left both behind, so a later GC notification could reach the deleted native runtime. --- test-app/runtime/src/main/cpp/WorkerWrapper.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp index 0084370e4..500b06426 100644 --- a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp @@ -618,11 +618,15 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr self) { isTerminating_ = true; // A bootstrap that failed in Java after the native runtime was built (an - // exception from internal/ts_helpers.js, for one) never handed it back: - // initWorkerRuntime threw before returning its id. It is still this - // thread's current runtime, so it is torn down below like any other. + // exception from internal/ts_helpers.js, or from the logger once the + // runtime is up) never handed it back: initWorkerRuntime threw before + // returning its id. It is still this thread's current runtime, so it is + // torn down below like any other, and detached from Java by its own id. if (runtime_ == nullptr) { runtime_ = Runtime::GetCurrentRuntime(); + if (runtime_ != nullptr) { + runtimeId = runtime_->GetId(); + } } // Terminate any workers this worker created (nested workers). Their