diff --git a/NativeScript/runtime/android/jsi/Runtime.cpp b/NativeScript/runtime/android/jsi/Runtime.cpp index 7991157d2..aa2527539 100644 --- a/NativeScript/runtime/android/jsi/Runtime.cpp +++ b/NativeScript/runtime/android/jsi/Runtime.cpp @@ -557,6 +557,15 @@ std::string Runtime::ReadFileText(const std::string &filePath) { } void Runtime::DestroyRuntime() { + // Flush still-queued finalizer cleanup before is_destroying is set: with it + // set, the cleanup leaks the engine handles it would release, and a quit + // looper never drains it. Finalizers firing during teardown below run + // inline (Runtime::PostFinalizer's fallback). + if (m_finalizerQueue != nullptr) { + m_finalizerQueue->Destroy(); + delete m_finalizerQueue; + m_finalizerQueue = nullptr; + } is_destroying = true; engine::Runtime &rt = engineHost->GetRuntime(); if (m_looperTasks != nullptr) { @@ -578,14 +587,6 @@ void Runtime::DestroyRuntime() { #endif CallbackHandlers::RemoveEnvEntries(rt); this->m_objectManager->OnDisposeRuntime(); - // Release the finalizer handler and flush any still-queued cleanup while the - // runtime is still valid; finalizers firing during teardown below then run - // inline (Runtime::PostFinalizer's fallback). - if (m_finalizerQueue != nullptr) { - m_finalizerQueue->Destroy(); - delete m_finalizerQueue; - m_finalizerQueue = nullptr; - } // Every engine handle this runtime still owns must go while the VM is up. m_gcFunc = engine::Value::undefined(); // Last, because everything above may still call a js_util helper on its way diff --git a/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js b/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js index 7eaaa1cb8..1578259c8 100644 --- a/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js +++ b/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js @@ -219,6 +219,15 @@ describe("TNS Workers", () => { worker.postMessage({ eval: "close();" }); }); + if (global.android) { + it("Should not crash on close() after a native wrapper is collected in the same message", (done) => { + var worker = new Worker("./EvalWorker.js"); + + worker.onmessage = () => setTimeout(done, 500); + worker.postMessage({ eval: "new java.lang.Object(); gc(); postMessage('closing'); close();" }); + }); + } + it("Should not throw error if post message is called with native object", () => { var worker = new Worker("./EvalWorker.js");