From 48c95d0bbc00fa3770fcc36bbd71f48368f7d270 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Wed, 31 Mar 2021 14:42:11 +0800 Subject: [PATCH 1/3] node-api: make reference weak parameter an indirect link to references As the cancellation of second pass callbacks are not reachable from the current v8 API, and the second pass callbacks are scheduled with NodePlatform's task runner, we have to ensure that the weak parameter holds indirect access to the v8impl::Reference object so that the object can be destroyed on addon env teardown before the whole node env is able to shutdown. --- src/js_native_api_v8.cc | 85 ++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 664aa2d41bbc98..174437754d05e0 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -314,16 +314,16 @@ class RefBase : protected Finalizer, RefTracker { }; class Reference : public RefBase { + using PersistentWeakParameter = Reference*; + protected: template - Reference(napi_env env, - v8::Local value, - Args&&... args) + Reference(napi_env env, v8::Local value, Args&&... args) : RefBase(env, std::forward(args)...), - _persistent(env->isolate, value) { + _persistent(env->isolate, value), + _parameter(new PersistentWeakParameter(this)) { if (RefCount() == 0) { - _persistent.SetWeak( - this, FinalizeCallback, v8::WeakCallbackType::kParameter); + SetWeak(); } } @@ -344,10 +344,18 @@ class Reference : public RefBase { finalize_hint); } + virtual ~Reference() { + // If the second pass callback is scheduled, they are responsible for + // deleting the weak parameter. + if (_parameter != nullptr) { + delete _parameter; + } + } + inline uint32_t Ref() { uint32_t refcount = RefBase::Ref(); if (refcount == 1) { - _persistent.ClearWeak(); + ClearWeak(); } return refcount; } @@ -356,8 +364,7 @@ class Reference : public RefBase { uint32_t old_refcount = RefCount(); uint32_t refcount = RefBase::Unref(); if (old_refcount == 1 && refcount == 0) { - _persistent.SetWeak( - this, FinalizeCallback, v8::WeakCallbackType::kParameter); + SetWeak(); } return refcount; } @@ -374,10 +381,9 @@ class Reference : public RefBase { inline void Finalize(bool is_env_teardown = false) override { // During env teardown, `~napi_env()` alone is responsible for finalizing. // Thus, we don't want any stray gc passes to trigger a second call to - // `Finalize()`, so let's reset the persistent here if nothing is - // keeping it alive. - if (is_env_teardown && _persistent.IsWeak()) { - _persistent.ClearWeak(); + // `RefBase::Finalize()`, so let's clear the weakness here. + if (is_env_teardown) { + ClearWeak(); } // Chain up to perform the rest of the finalization. @@ -385,6 +391,31 @@ class Reference : public RefBase { } private: + // The persistent may have been reset in the first pass weak callback. + // However a second pass callback may have been scheduled already, we + // must ensure that the weak parameter no longer link to this reference + // so that the second pass callback is not able to calling into the + // `Reference::Finalize()`. + inline void ClearWeak() { + if (!_persistent.IsEmpty()) { + _persistent.ClearWeak(); + } + if (_parameter != nullptr) { + *_parameter = nullptr; + } + } + + // Setup the weakness callback and parameter. + inline void SetWeak() { + // If second pass callback has been scheduled, suppress the set weak. + if (_parameter == nullptr) { + return; + } + _persistent.SetWeak( + _parameter, FinalizeCallback, v8::WeakCallbackType::kParameter); + *_parameter = this; + } + // The N-API finalizer callback may make calls into the engine. V8's heap is // not in a consistent state during the weak callback, and therefore it does // not support calls back into it. However, it provides a mechanism for adding @@ -392,20 +423,40 @@ class Reference : public RefBase { // attach such a second-pass finalizer from the first pass finalizer. Thus, // we do that here to ensure that the N-API finalizer callback is free to call // into the engine. - static void FinalizeCallback(const v8::WeakCallbackInfo& data) { - Reference* reference = data.GetParameter(); + static void FinalizeCallback( + const v8::WeakCallbackInfo& data) { + PersistentWeakParameter* parameter = data.GetParameter(); + Reference* reference = *parameter; + if (reference == nullptr) { + return; + } // The reference must be reset during the first pass. reference->_persistent.Reset(); + // Mark the parameter not delete-able until the second pass callback is + // invoked. + reference->_parameter = nullptr; data.SetSecondPassCallback(SecondPassCallback); } - static void SecondPassCallback(const v8::WeakCallbackInfo& data) { - data.GetParameter()->Finalize(); + // Second pass callbacks are scheduled with platform tasks. At env teardown, + // the tasks may have already be scheduled and we are unable to cancel the + // second pass callback task. So we have to make sure that parameter is kept + // alive until the second pass callback is been invoked. + static void SecondPassCallback( + const v8::WeakCallbackInfo& data) { + PersistentWeakParameter* parameter = data.GetParameter(); + Reference* reference = *parameter; + delete parameter; + if (reference == nullptr) { + return; + } + reference->Finalize(); } v8impl::Persistent _persistent; + PersistentWeakParameter* _parameter; }; enum UnwrapAction { From a85e1f7f0c5c0977007468cd31a6033a1ecb4429 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 7 Apr 2021 17:17:26 -0400 Subject: [PATCH 2/3] squash: update wording, tweak comments --- src/js_native_api_v8.cc | 66 ++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 174437754d05e0..13f72a9950b35f 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -314,14 +314,14 @@ class RefBase : protected Finalizer, RefTracker { }; class Reference : public RefBase { - using PersistentWeakParameter = Reference*; + using SecondPassCallParameterRef = Reference*; protected: template Reference(napi_env env, v8::Local value, Args&&... args) : RefBase(env, std::forward(args)...), _persistent(env->isolate, value), - _parameter(new PersistentWeakParameter(this)) { + _secondPassParameter(new SecondPassCallParameterRef(this)) { if (RefCount() == 0) { SetWeak(); } @@ -345,10 +345,11 @@ class Reference : public RefBase { } virtual ~Reference() { - // If the second pass callback is scheduled, they are responsible for - // deleting the weak parameter. - if (_parameter != nullptr) { - delete _parameter; + // If the second pass callback is scheduled, it will delete the + // parameter passed to it, otherwise it will never be scheduled + // and we need to delete it here. + if (_secondPassParameter != nullptr) { + delete _secondPassParameter; } } @@ -381,7 +382,9 @@ class Reference : public RefBase { inline void Finalize(bool is_env_teardown = false) override { // During env teardown, `~napi_env()` alone is responsible for finalizing. // Thus, we don't want any stray gc passes to trigger a second call to - // `RefBase::Finalize()`, so let's clear the weakness here. + // `RefBase::Finalize()`. ClearWeak will ensure that even if the + // gc is in progress no Finalization will be run for this Reference + // by the gc. if (is_env_teardown) { ClearWeak(); } @@ -391,29 +394,32 @@ class Reference : public RefBase { } private: - // The persistent may have been reset in the first pass weak callback. - // However a second pass callback may have been scheduled already, we - // must ensure that the weak parameter no longer link to this reference - // so that the second pass callback is not able to calling into the - // `Reference::Finalize()`. + // ClearWeak is marking the Reference so that the gc should not + // collect it, but it is possible that a second pass callback + // may have been scheduled already if we are in shutdown. We clear + // the secondPassParameter so that even if it has been + // secheduled no Finalization will be run. inline void ClearWeak() { if (!_persistent.IsEmpty()) { _persistent.ClearWeak(); } - if (_parameter != nullptr) { - *_parameter = nullptr; + if (_secondPassParameter != nullptr) { + *_secondPassParameter = nullptr; } } - // Setup the weakness callback and parameter. + // Mark the reference as weak and eligible for collection + // by the gc. inline void SetWeak() { - // If second pass callback has been scheduled, suppress the set weak. - if (_parameter == nullptr) { + if (_secondPassParameter == nullptr) { + // This means that the Reference has already been processed + // by the second pass calback, so its already been Finalized, do + // nothing return; } _persistent.SetWeak( - _parameter, FinalizeCallback, v8::WeakCallbackType::kParameter); - *_parameter = this; + _secondPassParameter, FinalizeCallback, v8::WeakCallbackType::kParameter); + *_secondPassParameter = this; } // The N-API finalizer callback may make calls into the engine. V8's heap is @@ -424,8 +430,8 @@ class Reference : public RefBase { // we do that here to ensure that the N-API finalizer callback is free to call // into the engine. static void FinalizeCallback( - const v8::WeakCallbackInfo& data) { - PersistentWeakParameter* parameter = data.GetParameter(); + const v8::WeakCallbackInfo& data) { + SecondPassCallParameterRef* parameter = data.GetParameter(); Reference* reference = *parameter; if (reference == nullptr) { return; @@ -435,28 +441,34 @@ class Reference : public RefBase { reference->_persistent.Reset(); // Mark the parameter not delete-able until the second pass callback is // invoked. - reference->_parameter = nullptr; + reference->_secondPassParameter = nullptr; data.SetSecondPassCallback(SecondPassCallback); } // Second pass callbacks are scheduled with platform tasks. At env teardown, // the tasks may have already be scheduled and we are unable to cancel the - // second pass callback task. So we have to make sure that parameter is kept - // alive until the second pass callback is been invoked. + // second pass callback task. We have to make sure that parameter is kept + // alive until the second pass callback is been invoked. In order to do + // this and still allow our code to Finalize/delete the Reference during + // shutdown we have to use a seperately allocated parameter instead + // of a parameter within the Reference object itself. This is what + // is stored in _secondPassParameter and it is alocated in the + // constructor for the Reference. + SecondPassCallParameterRef* _secondPassParameter; static void SecondPassCallback( - const v8::WeakCallbackInfo& data) { - PersistentWeakParameter* parameter = data.GetParameter(); + const v8::WeakCallbackInfo& data) { + SecondPassCallParameterRef* parameter = data.GetParameter(); Reference* reference = *parameter; delete parameter; if (reference == nullptr) { + // the reference itself has already been deleted so nothing to do return; } reference->Finalize(); } v8impl::Persistent _persistent; - PersistentWeakParameter* _parameter; }; enum UnwrapAction { From 98eecdffd0555a9f9cd88baf18e821ddd466357e Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 7 Apr 2021 17:50:40 -0400 Subject: [PATCH 3/3] squash: fixup Signed-off-by: Michael Dawson --- src/js_native_api_v8.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 13f72a9950b35f..66753e2295d194 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -418,7 +418,8 @@ class Reference : public RefBase { return; } _persistent.SetWeak( - _secondPassParameter, FinalizeCallback, v8::WeakCallbackType::kParameter); + _secondPassParameter, FinalizeCallback, + v8::WeakCallbackType::kParameter); *_secondPassParameter = this; } @@ -455,7 +456,6 @@ class Reference : public RefBase { // of a parameter within the Reference object itself. This is what // is stored in _secondPassParameter and it is alocated in the // constructor for the Reference. - SecondPassCallParameterRef* _secondPassParameter; static void SecondPassCallback( const v8::WeakCallbackInfo& data) { SecondPassCallParameterRef* parameter = data.GetParameter(); @@ -469,6 +469,7 @@ class Reference : public RefBase { } v8impl::Persistent _persistent; + SecondPassCallParameterRef* _secondPassParameter; }; enum UnwrapAction {