Skip to content

Commit 1391400

Browse files
committed
node-api: run finalizers directly from GC
1 parent 640a791 commit 1391400

5 files changed

+134
-42
lines changed

src/js_native_api.h

+10
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,16 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env,
517517

518518
#endif // NAPI_VERSION >= 5
519519

520+
#ifdef NAPI_EXPERIMENTAL
521+
522+
NAPI_EXTERN napi_status NAPI_CDECL
523+
node_api_post_finalizer(napi_env env,
524+
napi_finalize finalize_cb,
525+
void* finalize_data,
526+
void* finalize_hint);
527+
528+
#endif // NAPI_EXPERIMENTAL
529+
520530
#if NAPI_VERSION >= 6
521531

522532
// BigInt

src/js_native_api_v8.cc

+93-37
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,28 @@
5757
(out) = v8::type::New((buffer), (byte_offset), (length)); \
5858
} while (0)
5959

60-
namespace v8impl {
60+
void napi_env__::InvokeFinalizerFromGC(v8impl::RefTracker* finalizer) {
61+
if (module_api_version != NAPI_VERSION_EXPERIMENTAL) {
62+
EnqueueFinalizer(finalizer);
63+
} else {
64+
// The experimental code calls finalizers immediately to release native
65+
// objects as soon as possible, but it suspends use of JS from finalizer.
66+
// If JS calls are needed, then the finalizer code must call
67+
// node_api_post_finalizer.
68+
if (last_error.error_code == napi_ok && last_exception.IsEmpty()) {
69+
bool saved_suspend_call_into_js = suspend_call_into_js;
70+
finalizer->Finalize();
71+
suspend_call_into_js = saved_suspend_call_into_js;
72+
} else {
73+
// The finalizers can be run in the middle of JS or C++ code.
74+
// That code may be in an error state. In that case use the asynchronous
75+
// finalizer.
76+
EnqueueFinalizer(finalizer);
77+
}
78+
}
79+
}
6180

81+
namespace v8impl {
6282
namespace {
6383

6484
template <typename CCharType, typename StringMaker>
@@ -604,28 +624,72 @@ void Finalizer::ResetFinalizer() {
604624
finalize_hint_ = nullptr;
605625
}
606626

607-
// Wrapper around v8impl::Persistent that implements reference counting.
608-
RefBase::RefBase(napi_env env,
609-
uint32_t initial_refcount,
610-
Ownership ownership,
611-
napi_finalize finalize_callback,
612-
void* finalize_data,
613-
void* finalize_hint)
627+
TrackedFinalizer::TrackedFinalizer(napi_env env,
628+
napi_finalize finalize_callback,
629+
void* finalize_data,
630+
void* finalize_hint)
614631
: Finalizer(env, finalize_callback, finalize_data, finalize_hint),
615-
refcount_(initial_refcount),
616-
ownership_(ownership) {
632+
RefTracker() {
617633
Link(finalize_callback == nullptr ? &env->reflist : &env->finalizing_reflist);
618634
}
619635

620-
// When a RefBase is being deleted, it may have been queued to call its
636+
TrackedFinalizer* TrackedFinalizer::New(napi_env env,
637+
napi_finalize finalize_callback,
638+
void* finalize_data,
639+
void* finalize_hint) {
640+
return new TrackedFinalizer(
641+
env, finalize_callback, finalize_data, finalize_hint);
642+
}
643+
644+
// When a TrackedFinalizer is being deleted, it may have been queued to call its
621645
// finalizer.
622-
RefBase::~RefBase() {
646+
TrackedFinalizer::~TrackedFinalizer() {
623647
// Remove from the env's tracked list.
624648
Unlink();
625649
// Try to remove the finalizer from the scheduled second pass callback.
626650
env_->DequeueFinalizer(this);
627651
}
628652

653+
void TrackedFinalizer::Finalize() {
654+
FinalizeCore(/*deleteMe:*/ true);
655+
}
656+
657+
void TrackedFinalizer::FinalizeCore(bool deleteMe) {
658+
// Swap out the field finalize_callback so that it can not be accidentally
659+
// called more than once.
660+
napi_finalize finalize_callback = finalize_callback_;
661+
void* finalize_data = finalize_data_;
662+
void* finalize_hint = finalize_hint_;
663+
ResetFinalizer();
664+
665+
// Either the RefBase is going to be deleted in the finalize_callback or not,
666+
// it should be removed from the tracked list.
667+
Unlink();
668+
// 1. If the finalize_callback is present, it should either delete the
669+
// derived RefBase, or set ownership with Ownership::kRuntime.
670+
// 2. If the finalizer is not present, the derived RefBase can be deleted
671+
// after the call.
672+
if (finalize_callback != nullptr) {
673+
env_->CallFinalizer(finalize_callback, finalize_data, finalize_hint);
674+
// No access to `this` after finalize_callback is called.
675+
}
676+
677+
if (deleteMe) {
678+
delete this;
679+
}
680+
}
681+
682+
// Wrapper around v8impl::Persistent that implements reference counting.
683+
RefBase::RefBase(napi_env env,
684+
uint32_t initial_refcount,
685+
Ownership ownership,
686+
napi_finalize finalize_callback,
687+
void* finalize_data,
688+
void* finalize_hint)
689+
: TrackedFinalizer(env, finalize_callback, finalize_data, finalize_hint),
690+
refcount_(initial_refcount),
691+
ownership_(ownership) {}
692+
629693
RefBase* RefBase::New(napi_env env,
630694
uint32_t initial_refcount,
631695
Ownership ownership,
@@ -660,31 +724,9 @@ uint32_t RefBase::RefCount() {
660724
}
661725

662726
void RefBase::Finalize() {
663-
Ownership ownership = ownership_;
664-
// Swap out the field finalize_callback so that it can not be accidentally
665-
// called more than once.
666-
napi_finalize finalize_callback = finalize_callback_;
667-
void* finalize_data = finalize_data_;
668-
void* finalize_hint = finalize_hint_;
669-
ResetFinalizer();
670-
671-
// Either the RefBase is going to be deleted in the finalize_callback or not,
672-
// it should be removed from the tracked list.
673-
Unlink();
674-
// 1. If the finalize_callback is present, it should either delete the
675-
// RefBase, or set ownership with Ownership::kRuntime.
676-
// 2. If the finalizer is not present, the RefBase can be deleted after the
677-
// call.
678-
if (finalize_callback != nullptr) {
679-
env_->CallFinalizer(finalize_callback, finalize_data, finalize_hint);
680-
// No access to `this` after finalize_callback is called.
681-
}
682-
683727
// If the RefBase is not Ownership::kRuntime, userland code should delete it.
684-
// Now delete it if it is Ownership::kRuntime.
685-
if (ownership == Ownership::kRuntime) {
686-
delete this;
687-
}
728+
// Delete it if it is Ownership::kRuntime.
729+
FinalizeCore(/*deleteMe:*/ ownership_ == Ownership::kRuntime);
688730
}
689731

690732
template <typename... Args>
@@ -779,7 +821,7 @@ void Reference::WeakCallback(const v8::WeakCallbackInfo<Reference>& data) {
779821
Reference* reference = data.GetParameter();
780822
// The reference must be reset during the weak callback as the API protocol.
781823
reference->persistent_.Reset();
782-
reference->env_->EnqueueFinalizer(reference);
824+
reference->env_->InvokeFinalizerFromGC(reference);
783825
}
784826

785827
} // end of namespace v8impl
@@ -3310,6 +3352,20 @@ napi_status NAPI_CDECL napi_add_finalizer(napi_env env,
33103352
return napi_clear_last_error(env);
33113353
}
33123354

3355+
#ifdef NAPI_EXPERIMENTAL
3356+
3357+
napi_status NAPI_CDECL node_api_post_finalizer(napi_env env,
3358+
napi_finalize finalize_cb,
3359+
void* finalize_data,
3360+
void* finalize_hint) {
3361+
CHECK_ENV(env);
3362+
env->EnqueueFinalizer(v8impl::TrackedFinalizer::New(
3363+
env, finalize_cb, finalize_data, finalize_hint));
3364+
return napi_clear_last_error(env);
3365+
}
3366+
3367+
#endif
3368+
33133369
napi_status NAPI_CDECL napi_adjust_external_memory(napi_env env,
33143370
int64_t change_in_bytes,
33153371
int64_t* adjusted_value) {

src/js_native_api_v8.h

+28-4
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ struct napi_env__ {
6868
if (--refs == 0) DeleteMe();
6969
}
7070

71-
virtual bool can_call_into_js() const { return true; }
71+
virtual bool can_call_into_js() const { return !suspend_call_into_js; }
7272

7373
static inline void HandleThrow(napi_env env, v8::Local<v8::Value> value) {
7474
if (env->terminatedOrTerminating()) {
@@ -102,9 +102,13 @@ struct napi_env__ {
102102
// Call finalizer immediately.
103103
virtual void CallFinalizer(napi_finalize cb, void* data, void* hint) {
104104
v8::HandleScope handle_scope(isolate);
105+
v8::Context::Scope context_scope(context());
105106
CallIntoModule([&](napi_env env) { cb(env, data, hint); });
106107
}
107108

109+
// Invoke finalizer from V8 garbage collector.
110+
void InvokeFinalizerFromGC(v8impl::RefTracker* finalizer);
111+
108112
// Enqueue the finalizer to the napi_env's own queue of the second pass
109113
// weak callback.
110114
// Implementation should drain the queue at the time it is safe to call
@@ -148,6 +152,7 @@ struct napi_env__ {
148152
int refs = 1;
149153
void* instance_data = nullptr;
150154
int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
155+
bool suspend_call_into_js = false;
151156

152157
protected:
153158
// Should not be deleted directly. Delete with `napi_env__::DeleteMe()`
@@ -363,8 +368,28 @@ enum class Ownership {
363368
kUserland,
364369
};
365370

366-
// Wrapper around Finalizer that implements reference counting.
367-
class RefBase : public Finalizer, public RefTracker {
371+
// Wrapper around Finalizer that can be tracked.
372+
class TrackedFinalizer : public Finalizer, public RefTracker {
373+
protected:
374+
TrackedFinalizer(napi_env env,
375+
napi_finalize finalize_callback,
376+
void* finalize_data,
377+
void* finalize_hint);
378+
379+
public:
380+
static TrackedFinalizer* New(napi_env env,
381+
napi_finalize finalize_callback,
382+
void* finalize_data,
383+
void* finalize_hint);
384+
~TrackedFinalizer() override;
385+
386+
protected:
387+
void Finalize() override;
388+
void FinalizeCore(bool deleteMe);
389+
};
390+
391+
// Wrapper around TrackedFinalizer that implements reference counting.
392+
class RefBase : public TrackedFinalizer {
368393
protected:
369394
RefBase(napi_env env,
370395
uint32_t initial_refcount,
@@ -380,7 +405,6 @@ class RefBase : public Finalizer, public RefTracker {
380405
napi_finalize finalize_callback,
381406
void* finalize_data,
382407
void* finalize_hint);
383-
virtual ~RefBase();
384408

385409
void* Data();
386410
uint32_t Ref();

src/node_api.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void node_napi_env__::DeleteMe() {
3333
}
3434

3535
bool node_napi_env__::can_call_into_js() const {
36-
return node_env()->can_call_into_js();
36+
return Super::can_call_into_js() && node_env()->can_call_into_js();
3737
}
3838

3939
void node_napi_env__::CallFinalizer(napi_finalize cb, void* data, void* hint) {

src/node_api_internals.h

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "util-inl.h"
1010

1111
struct node_napi_env__ : public napi_env__ {
12+
using Super = napi_env__;
13+
1214
node_napi_env__(v8::Local<v8::Context> context,
1315
const std::string& module_filename,
1416
int32_t module_api_version);

0 commit comments

Comments
 (0)