@@ -314,16 +314,16 @@ class RefBase : protected Finalizer, RefTracker {
314314};
315315
316316class Reference : public RefBase {
317+ using SecondPassCallParameterRef = Reference*;
318+
317319 protected:
318320 template <typename ... Args>
319- Reference (napi_env env,
320- v8::Local<v8::Value> value,
321- Args&&... args)
321+ Reference (napi_env env, v8::Local<v8::Value> value, Args&&... args)
322322 : RefBase(env, std::forward<Args>(args)...),
323- _persistent (env->isolate, value) {
323+ _persistent (env->isolate, value),
324+ _secondPassParameter(new SecondPassCallParameterRef(this )) {
324325 if (RefCount () == 0 ) {
325- _persistent.SetWeak (
326- this , FinalizeCallback, v8::WeakCallbackType::kParameter );
326+ SetWeak ();
327327 }
328328 }
329329
@@ -344,10 +344,19 @@ class Reference : public RefBase {
344344 finalize_hint);
345345 }
346346
347+ virtual ~Reference () {
348+ // If the second pass callback is scheduled, it will delete the
349+ // parameter passed to it, otherwise it will never be scheduled
350+ // and we need to delete it here.
351+ if (_secondPassParameter != nullptr ) {
352+ delete _secondPassParameter;
353+ }
354+ }
355+
347356 inline uint32_t Ref () {
348357 uint32_t refcount = RefBase::Ref ();
349358 if (refcount == 1 ) {
350- _persistent. ClearWeak ();
359+ ClearWeak ();
351360 }
352361 return refcount;
353362 }
@@ -356,8 +365,7 @@ class Reference : public RefBase {
356365 uint32_t old_refcount = RefCount ();
357366 uint32_t refcount = RefBase::Unref ();
358367 if (old_refcount == 1 && refcount == 0 ) {
359- _persistent.SetWeak (
360- this , FinalizeCallback, v8::WeakCallbackType::kParameter );
368+ SetWeak ();
361369 }
362370 return refcount;
363371 }
@@ -377,39 +385,95 @@ class Reference : public RefBase {
377385
378386 // During env teardown, `~napi_env()` alone is responsible for finalizing.
379387 // Thus, we don't want any stray gc passes to trigger a second call to
380- // `Finalize()`, so let's reset the persistent here if nothing is
381- // keeping it alive.
382- if (is_env_teardown && _persistent.IsWeak ()) {
383- _persistent.ClearWeak ();
388+ // `RefBase::Finalize()`. ClearWeak will ensure that even if the
389+ // gc is in progress no Finalization will be run for this Reference
390+ // by the gc.
391+ if (is_env_teardown) {
392+ ClearWeak ();
384393 }
385394
386395 // Chain up to perform the rest of the finalization.
387396 RefBase::Finalize (is_env_teardown);
388397 }
389398
390399 private:
400+ // ClearWeak is marking the Reference so that the gc should not
401+ // collect it, but it is possible that a second pass callback
402+ // may have been scheduled already if we are in shutdown. We clear
403+ // the secondPassParameter so that even if it has been
404+ // secheduled no Finalization will be run.
405+ inline void ClearWeak () {
406+ if (!_persistent.IsEmpty ()) {
407+ _persistent.ClearWeak ();
408+ }
409+ if (_secondPassParameter != nullptr ) {
410+ *_secondPassParameter = nullptr ;
411+ }
412+ }
413+
414+ // Mark the reference as weak and eligible for collection
415+ // by the gc.
416+ inline void SetWeak () {
417+ if (_secondPassParameter == nullptr ) {
418+ // This means that the Reference has already been processed
419+ // by the second pass calback, so its already been Finalized, do
420+ // nothing
421+ return ;
422+ }
423+ _persistent.SetWeak (
424+ _secondPassParameter, FinalizeCallback,
425+ v8::WeakCallbackType::kParameter );
426+ *_secondPassParameter = this ;
427+ }
428+
391429 // The N-API finalizer callback may make calls into the engine. V8's heap is
392430 // not in a consistent state during the weak callback, and therefore it does
393431 // not support calls back into it. However, it provides a mechanism for adding
394432 // a finalizer which may make calls back into the engine by allowing us to
395433 // attach such a second-pass finalizer from the first pass finalizer. Thus,
396434 // we do that here to ensure that the N-API finalizer callback is free to call
397435 // into the engine.
398- static void FinalizeCallback (const v8::WeakCallbackInfo<Reference>& data) {
399- Reference* reference = data.GetParameter ();
436+ static void FinalizeCallback (
437+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
438+ SecondPassCallParameterRef* parameter = data.GetParameter ();
439+ Reference* reference = *parameter;
440+ if (reference == nullptr ) {
441+ return ;
442+ }
400443
401444 // The reference must be reset during the first pass.
402445 reference->_persistent .Reset ();
446+ // Mark the parameter not delete-able until the second pass callback is
447+ // invoked.
448+ reference->_secondPassParameter = nullptr ;
403449
404450 data.SetSecondPassCallback (SecondPassCallback);
405451 }
406452
407- static void SecondPassCallback (const v8::WeakCallbackInfo<Reference>& data) {
408- data.GetParameter ()->Finalize ();
453+ // Second pass callbacks are scheduled with platform tasks. At env teardown,
454+ // the tasks may have already be scheduled and we are unable to cancel the
455+ // second pass callback task. We have to make sure that parameter is kept
456+ // alive until the second pass callback is been invoked. In order to do
457+ // this and still allow our code to Finalize/delete the Reference during
458+ // shutdown we have to use a seperately allocated parameter instead
459+ // of a parameter within the Reference object itself. This is what
460+ // is stored in _secondPassParameter and it is alocated in the
461+ // constructor for the Reference.
462+ static void SecondPassCallback (
463+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
464+ SecondPassCallParameterRef* parameter = data.GetParameter ();
465+ Reference* reference = *parameter;
466+ delete parameter;
467+ if (reference == nullptr ) {
468+ // the reference itself has already been deleted so nothing to do
469+ return ;
470+ }
471+ reference->Finalize ();
409472 }
410473
411474 bool env_teardown_finalize_started_ = false ;
412475 v8impl::Persistent<v8::Value> _persistent;
476+ SecondPassCallParameterRef* _secondPassParameter;
413477};
414478
415479enum UnwrapAction {
0 commit comments