@@ -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 }
@@ -374,38 +382,94 @@ class Reference : public RefBase {
374382 inline void Finalize (bool is_env_teardown = false ) override {
375383 // During env teardown, `~napi_env()` alone is responsible for finalizing.
376384 // Thus, we don't want any stray gc passes to trigger a second call to
377- // `Finalize()`, so let's reset the persistent here if nothing is
378- // keeping it alive.
379- if (is_env_teardown && _persistent.IsWeak ()) {
380- _persistent.ClearWeak ();
385+ // `RefBase::Finalize()`. ClearWeak will ensure that even if the
386+ // gc is in progress no Finalization will be run for this Reference
387+ // by the gc.
388+ if (is_env_teardown) {
389+ ClearWeak ();
381390 }
382391
383392 // Chain up to perform the rest of the finalization.
384393 RefBase::Finalize (is_env_teardown);
385394 }
386395
387396 private:
397+ // ClearWeak is marking the Reference so that the gc should not
398+ // collect it, but it is possible that a second pass callback
399+ // may have been scheduled already if we are in shutdown. We clear
400+ // the secondPassParameter so that even if it has been
401+ // secheduled no Finalization will be run.
402+ inline void ClearWeak () {
403+ if (!_persistent.IsEmpty ()) {
404+ _persistent.ClearWeak ();
405+ }
406+ if (_secondPassParameter != nullptr ) {
407+ *_secondPassParameter = nullptr ;
408+ }
409+ }
410+
411+ // Mark the reference as weak and eligible for collection
412+ // by the gc.
413+ inline void SetWeak () {
414+ if (_secondPassParameter == nullptr ) {
415+ // This means that the Reference has already been processed
416+ // by the second pass calback, so its already been Finalized, do
417+ // nothing
418+ return ;
419+ }
420+ _persistent.SetWeak (
421+ _secondPassParameter, FinalizeCallback,
422+ v8::WeakCallbackType::kParameter );
423+ *_secondPassParameter = this ;
424+ }
425+
388426 // The N-API finalizer callback may make calls into the engine. V8's heap is
389427 // not in a consistent state during the weak callback, and therefore it does
390428 // not support calls back into it. However, it provides a mechanism for adding
391429 // a finalizer which may make calls back into the engine by allowing us to
392430 // attach such a second-pass finalizer from the first pass finalizer. Thus,
393431 // we do that here to ensure that the N-API finalizer callback is free to call
394432 // into the engine.
395- static void FinalizeCallback (const v8::WeakCallbackInfo<Reference>& data) {
396- Reference* reference = data.GetParameter ();
433+ static void FinalizeCallback (
434+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
435+ SecondPassCallParameterRef* parameter = data.GetParameter ();
436+ Reference* reference = *parameter;
437+ if (reference == nullptr ) {
438+ return ;
439+ }
397440
398441 // The reference must be reset during the first pass.
399442 reference->_persistent .Reset ();
443+ // Mark the parameter not delete-able until the second pass callback is
444+ // invoked.
445+ reference->_secondPassParameter = nullptr ;
400446
401447 data.SetSecondPassCallback (SecondPassCallback);
402448 }
403449
404- static void SecondPassCallback (const v8::WeakCallbackInfo<Reference>& data) {
405- data.GetParameter ()->Finalize ();
450+ // Second pass callbacks are scheduled with platform tasks. At env teardown,
451+ // the tasks may have already be scheduled and we are unable to cancel the
452+ // second pass callback task. We have to make sure that parameter is kept
453+ // alive until the second pass callback is been invoked. In order to do
454+ // this and still allow our code to Finalize/delete the Reference during
455+ // shutdown we have to use a seperately allocated parameter instead
456+ // of a parameter within the Reference object itself. This is what
457+ // is stored in _secondPassParameter and it is alocated in the
458+ // constructor for the Reference.
459+ static void SecondPassCallback (
460+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
461+ SecondPassCallParameterRef* parameter = data.GetParameter ();
462+ Reference* reference = *parameter;
463+ delete parameter;
464+ if (reference == nullptr ) {
465+ // the reference itself has already been deleted so nothing to do
466+ return ;
467+ }
468+ reference->Finalize ();
406469 }
407470
408471 v8impl::Persistent<v8::Value> _persistent;
472+ SecondPassCallParameterRef* _secondPassParameter;
409473};
410474
411475enum UnwrapAction {
0 commit comments