@@ -313,16 +313,16 @@ class RefBase : protected Finalizer, RefTracker {
313313};
314314
315315class Reference : public RefBase {
316+ using SecondPassCallParameterRef = Reference*;
317+
316318 protected:
317319 template <typename ... Args>
318- Reference (napi_env env,
319- v8::Local<v8::Value> value,
320- Args&&... args)
320+ Reference (napi_env env, v8::Local<v8::Value> value, Args&&... args)
321321 : RefBase(env, std::forward<Args>(args)...),
322- _persistent (env->isolate, value) {
322+ _persistent (env->isolate, value),
323+ _secondPassParameter(new SecondPassCallParameterRef(this )) {
323324 if (RefCount () == 0 ) {
324- _persistent.SetWeak (
325- this , FinalizeCallback, v8::WeakCallbackType::kParameter );
325+ SetWeak ();
326326 }
327327 }
328328
@@ -343,10 +343,19 @@ class Reference : public RefBase {
343343 finalize_hint);
344344 }
345345
346+ virtual ~Reference () {
347+ // If the second pass callback is scheduled, it will delete the
348+ // parameter passed to it, otherwise it will never be scheduled
349+ // and we need to delete it here.
350+ if (_secondPassParameter != nullptr ) {
351+ delete _secondPassParameter;
352+ }
353+ }
354+
346355 inline uint32_t Ref () {
347356 uint32_t refcount = RefBase::Ref ();
348357 if (refcount == 1 ) {
349- _persistent. ClearWeak ();
358+ ClearWeak ();
350359 }
351360 return refcount;
352361 }
@@ -355,8 +364,7 @@ class Reference : public RefBase {
355364 uint32_t old_refcount = RefCount ();
356365 uint32_t refcount = RefBase::Unref ();
357366 if (old_refcount == 1 && refcount == 0 ) {
358- _persistent.SetWeak (
359- this , FinalizeCallback, v8::WeakCallbackType::kParameter );
367+ SetWeak ();
360368 }
361369 return refcount;
362370 }
@@ -373,38 +381,94 @@ class Reference : public RefBase {
373381 inline void Finalize (bool is_env_teardown = false ) override {
374382 // During env teardown, `~napi_env()` alone is responsible for finalizing.
375383 // Thus, we don't want any stray gc passes to trigger a second call to
376- // `Finalize()`, so let's reset the persistent here if nothing is
377- // keeping it alive.
378- if (is_env_teardown && _persistent.IsWeak ()) {
379- _persistent.ClearWeak ();
384+ // `RefBase::Finalize()`. ClearWeak will ensure that even if the
385+ // gc is in progress no Finalization will be run for this Reference
386+ // by the gc.
387+ if (is_env_teardown) {
388+ ClearWeak ();
380389 }
381390
382391 // Chain up to perform the rest of the finalization.
383392 RefBase::Finalize (is_env_teardown);
384393 }
385394
386395 private:
396+ // ClearWeak is marking the Reference so that the gc should not
397+ // collect it, but it is possible that a second pass callback
398+ // may have been scheduled already if we are in shutdown. We clear
399+ // the secondPassParameter so that even if it has been
400+ // secheduled no Finalization will be run.
401+ inline void ClearWeak () {
402+ if (!_persistent.IsEmpty ()) {
403+ _persistent.ClearWeak ();
404+ }
405+ if (_secondPassParameter != nullptr ) {
406+ *_secondPassParameter = nullptr ;
407+ }
408+ }
409+
410+ // Mark the reference as weak and eligible for collection
411+ // by the gc.
412+ inline void SetWeak () {
413+ if (_secondPassParameter == nullptr ) {
414+ // This means that the Reference has already been processed
415+ // by the second pass calback, so its already been Finalized, do
416+ // nothing
417+ return ;
418+ }
419+ _persistent.SetWeak (
420+ _secondPassParameter, FinalizeCallback,
421+ v8::WeakCallbackType::kParameter );
422+ *_secondPassParameter = this ;
423+ }
424+
387425 // The N-API finalizer callback may make calls into the engine. V8's heap is
388426 // not in a consistent state during the weak callback, and therefore it does
389427 // not support calls back into it. However, it provides a mechanism for adding
390428 // a finalizer which may make calls back into the engine by allowing us to
391429 // attach such a second-pass finalizer from the first pass finalizer. Thus,
392430 // we do that here to ensure that the N-API finalizer callback is free to call
393431 // into the engine.
394- static void FinalizeCallback (const v8::WeakCallbackInfo<Reference>& data) {
395- Reference* reference = data.GetParameter ();
432+ static void FinalizeCallback (
433+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
434+ SecondPassCallParameterRef* parameter = data.GetParameter ();
435+ Reference* reference = *parameter;
436+ if (reference == nullptr ) {
437+ return ;
438+ }
396439
397440 // The reference must be reset during the first pass.
398441 reference->_persistent .Reset ();
442+ // Mark the parameter not delete-able until the second pass callback is
443+ // invoked.
444+ reference->_secondPassParameter = nullptr ;
399445
400446 data.SetSecondPassCallback (SecondPassCallback);
401447 }
402448
403- static void SecondPassCallback (const v8::WeakCallbackInfo<Reference>& data) {
404- data.GetParameter ()->Finalize ();
449+ // Second pass callbacks are scheduled with platform tasks. At env teardown,
450+ // the tasks may have already be scheduled and we are unable to cancel the
451+ // second pass callback task. We have to make sure that parameter is kept
452+ // alive until the second pass callback is been invoked. In order to do
453+ // this and still allow our code to Finalize/delete the Reference during
454+ // shutdown we have to use a seperately allocated parameter instead
455+ // of a parameter within the Reference object itself. This is what
456+ // is stored in _secondPassParameter and it is alocated in the
457+ // constructor for the Reference.
458+ static void SecondPassCallback (
459+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
460+ SecondPassCallParameterRef* parameter = data.GetParameter ();
461+ Reference* reference = *parameter;
462+ delete parameter;
463+ if (reference == nullptr ) {
464+ // the reference itself has already been deleted so nothing to do
465+ return ;
466+ }
467+ reference->Finalize ();
405468 }
406469
407470 v8impl::Persistent<v8::Value> _persistent;
471+ SecondPassCallParameterRef* _secondPassParameter;
408472};
409473
410474class ArrayBufferReference final : public Reference {
0 commit comments