From 068a62c7fd690a8ab4c929a0a4473ae3d6e9abe6 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Thu, 20 Aug 2026 18:57:47 -0700 Subject: [PATCH] Fix V8 napi leaking every promise ever created v8impl::Persistent aliased v8::Persistent, whose traits set kResetInDestructor = false, so destroying one never releases the underlying global handle. ConcludeDeferred() disposes the deferred with `delete deferred_ref`, which therefore leaked the handle and pinned the resolved promise -- and everything reachable from its fulfilled value -- for the lifetime of the isolate. Alias v8::Global instead, as upstream Node.js does. Global resets in its destructor, so the existing `delete` now releases the handle. This also covers napi_env__::context_persistent, which relies on the same destructor. Found via a heap snapshot of a long-running Babylon Native session: 769 fulfilled promises, each retained solely by a global handle, holding 404 MB of decoded-image ArrayBuffers. Peak RSS over a full validation sweep drops from >13 GB (OOM crash) to ~2.4 GB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 88569c10-a7ff-4373-9a58-afa9c68b8c09 --- Core/Node-API/Source/js_native_api_v8_internals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Node-API/Source/js_native_api_v8_internals.h b/Core/Node-API/Source/js_native_api_v8_internals.h index 039e249b..3b7e094c 100644 --- a/Core/Node-API/Source/js_native_api_v8_internals.h +++ b/Core/Node-API/Source/js_native_api_v8_internals.h @@ -50,7 +50,7 @@ inline v8::Local OneByteString(v8::Isolate* isolate, namespace v8impl { template -using Persistent = v8::Persistent; +using Persistent = v8::Global; class PersistentToLocal { public: