Skip to content

Commit d771221

Browse files
addaleaxtargos
authored andcommitted
n-api: keep napi_env alive while it has finalizers
Manage the napi_env refcount from Finalizer instances, as the finalizer may refer to the napi_env until it is deleted. Fixes: #31134 Fixes: node-ffi-napi/node-ffi-napi#48 PR-URL: #31140 Reviewed-By: Jiawen Geng <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 18acacc commit d771221

File tree

3 files changed

+39
-6
lines changed

3 files changed

+39
-6
lines changed

src/js_native_api_v8.h

+23-5
Original file line numberDiff line numberDiff line change
@@ -186,26 +186,43 @@ inline v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v) {
186186

187187
// Adapter for napi_finalize callbacks.
188188
class Finalizer {
189+
public:
190+
// Some Finalizers are run during shutdown when the napi_env is destroyed,
191+
// and some need to keep an explicit reference to the napi_env because they
192+
// are run independently.
193+
enum EnvReferenceMode {
194+
kNoEnvReference,
195+
kKeepEnvReference
196+
};
197+
189198
protected:
190199
Finalizer(napi_env env,
191200
napi_finalize finalize_callback,
192201
void* finalize_data,
193-
void* finalize_hint)
202+
void* finalize_hint,
203+
EnvReferenceMode refmode = kNoEnvReference)
194204
: _env(env),
195205
_finalize_callback(finalize_callback),
196206
_finalize_data(finalize_data),
197-
_finalize_hint(finalize_hint) {
207+
_finalize_hint(finalize_hint),
208+
_has_env_reference(refmode == kKeepEnvReference) {
209+
if (_has_env_reference)
210+
_env->Ref();
198211
}
199212

200-
~Finalizer() = default;
213+
~Finalizer() {
214+
if (_has_env_reference)
215+
_env->Unref();
216+
}
201217

202218
public:
203219
static Finalizer* New(napi_env env,
204220
napi_finalize finalize_callback = nullptr,
205221
void* finalize_data = nullptr,
206-
void* finalize_hint = nullptr) {
222+
void* finalize_hint = nullptr,
223+
EnvReferenceMode refmode = kNoEnvReference) {
207224
return new Finalizer(
208-
env, finalize_callback, finalize_data, finalize_hint);
225+
env, finalize_callback, finalize_data, finalize_hint, refmode);
209226
}
210227

211228
static void Delete(Finalizer* finalizer) {
@@ -218,6 +235,7 @@ class Finalizer {
218235
void* _finalize_data;
219236
void* _finalize_hint;
220237
bool _finalize_ran = false;
238+
bool _has_env_reference = false;
221239
};
222240

223241
class TryCatch : public v8::TryCatch {

src/node_api.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,8 @@ napi_status napi_create_external_buffer(napi_env env,
732732

733733
// The finalizer object will delete itself after invoking the callback.
734734
v8impl::Finalizer* finalizer = v8impl::Finalizer::New(
735-
env, finalize_cb, nullptr, finalize_hint);
735+
env, finalize_cb, nullptr, finalize_hint,
736+
v8impl::Finalizer::kKeepEnvReference);
736737

737738
auto maybe = node::Buffer::New(isolate,
738739
static_cast<char*>(data),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const binding = require(`./build/${common.buildType}/test_buffer`);
5+
const assert = require('assert');
6+
7+
// Regression test for https://github.com/nodejs/node/issues/31134
8+
// Buffers with finalizers are allowed to remain alive until
9+
// Environment cleanup without crashing the process.
10+
// The test stores the buffer on `process` to make sure it lives as long as
11+
// the Context does.
12+
13+
process.externalBuffer = binding.newExternalBuffer();
14+
assert.strictEqual(process.externalBuffer.toString(), binding.theText);

0 commit comments

Comments
 (0)