From 5be914839b79630e569f6b62393822d5f65bb6dd Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Sun, 8 Nov 2026 17:31:46 +0500 Subject: [PATCH] fix(apple/v8): stop the runtime aborting on teardown Every V8 'nsr run' aborted at exit with 'Cannot exit non-entered context', so the Performance (v8) job failed on every PR into refactor. ~Runtime kept NapiScope open around js_free_napi_env. NapiScope enters env->context(), a Local that aliases the env's own persistent slot, and freeing the env destroys that slot; the scope's Context::Exit then read a dead handle. The env is now freed in its own scope that enters a HandleScope-owned copy of the context, after globalScope_ is closed. That exposed a second crash: the ES module cache keeps thread-local Globals per isolate and nothing released them, so they were reset at exit against the disposed isolate. js_free_runtime now calls the existing CleanupESModuleSystem before disposing. Co-Authored-By: Claude Opus 5.5 --- NativeScript/napi/v8/jsr.cpp | 4 ++++ NativeScript/runtime/apple/Runtime.cpp | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/NativeScript/napi/v8/jsr.cpp b/NativeScript/napi/v8/jsr.cpp index 98af6b0b..4cfc3aae 100644 --- a/NativeScript/napi/v8/jsr.cpp +++ b/NativeScript/napi/v8/jsr.cpp @@ -11,6 +11,7 @@ #include "libplatform/libplatform.h" #include "v8-fast-api-calls.h" +#include "v8-module-loader.h" #ifdef __ANDROID__ #include "NativeScriptAssert.h" @@ -262,6 +263,9 @@ napi_status js_free_runtime(jsr_ns_runtime runtime) { // the locker has been destroyed. { v8::Locker locker(jsr->isolate); + // The ES module cache holds thread-local Globals per isolate. Release them + // now; left to thread exit, they would be reset against a disposed isolate. + v8impl::CleanupESModuleSystem(jsr->isolate); while (v8::Isolate::GetCurrent() == jsr->isolate) { jsr->isolate->Exit(); } diff --git a/NativeScript/runtime/apple/Runtime.cpp b/NativeScript/runtime/apple/Runtime.cpp index 66dc7869..ae779a64 100644 --- a/NativeScript/runtime/apple/Runtime.cpp +++ b/NativeScript/runtime/apple/Runtime.cpp @@ -148,8 +148,26 @@ Runtime::~Runtime() { napi_close_handle_scope(env_, globalScope_); globalScope_ = nullptr; } +#ifndef TARGET_ENGINE_V8 + js_free_napi_env(env_); +#endif + } +#ifdef TARGET_ENGINE_V8 + { + // NapiScope enters env_->context(), a Local that aliases the env's own + // persistent slot. Freeing the env destroys that slot, so exiting the + // scope afterwards reads a dead handle and V8 aborts with "Cannot exit + // non-entered context". Enter a HandleScope-owned copy instead, so the + // env's finalizers still run inside the context. + v8::Locker locker(env_->isolate); + v8::Isolate::Scope isolateScope(env_->isolate); + v8::HandleScope handleScope(env_->isolate); + v8::Local context = + v8::Local::New(env_->isolate, env_->context()); + v8::Context::Scope contextScope(context); js_free_napi_env(env_); } +#endif js_free_runtime(runtime_); } else {