@@ -218,7 +218,7 @@ inline napi_status Unwrap(napi_env env,
218
218
if (action == RemoveWrap) {
219
219
CHECK (obj->DeletePrivate (context, NAPI_PRIVATE_KEY (context, wrapper))
220
220
.FromJust ());
221
- Reference::Delete ( reference) ;
221
+ delete reference;
222
222
}
223
223
224
224
return GET_RETURN_STATUS (env);
@@ -464,11 +464,20 @@ RefBase::RefBase(napi_env env,
464
464
void * finalize_data,
465
465
void * finalize_hint)
466
466
: Finalizer(env, finalize_callback, finalize_data, finalize_hint),
467
- _refcount (initial_refcount),
468
- _delete_self (delete_self) {
467
+ refcount_ (initial_refcount),
468
+ delete_self_ (delete_self) {
469
469
Link (finalize_callback == nullptr ? &env->reflist : &env->finalizing_reflist );
470
470
}
471
471
472
+ // When a RefBase is being deleted, it may have been queued to call its
473
+ // finalizer.
474
+ RefBase::~RefBase () {
475
+ // Remove from the env's tracked list.
476
+ Unlink ();
477
+ // Try to remove the finalizer from the scheduled second pass callback.
478
+ env_->DequeueFinalizer (this );
479
+ }
480
+
472
481
RefBase* RefBase::New (napi_env env,
473
482
uint32_t initial_refcount,
474
483
bool delete_self,
@@ -483,105 +492,65 @@ RefBase* RefBase::New(napi_env env,
483
492
finalize_hint);
484
493
}
485
494
486
- RefBase::~RefBase () {
487
- Unlink ();
488
- }
489
-
490
495
void * RefBase::Data () {
491
- return _finalize_data;
492
- }
493
-
494
- // Delete is called in 2 ways. Either from the finalizer or
495
- // from one of Unwrap or napi_delete_reference.
496
- //
497
- // When it is called from Unwrap or napi_delete_reference we only
498
- // want to do the delete if the finalizer has already run or
499
- // cannot have been queued to run (ie the reference count is > 0),
500
- // otherwise we may crash when the finalizer does run.
501
- // If the finalizer may have been queued and has not already run
502
- // delay the delete until the finalizer runs by not doing the delete
503
- // and setting _delete_self to true so that the finalizer will
504
- // delete it when it runs.
505
- //
506
- // The second way this is called is from
507
- // the finalizer and _delete_self is set. In this case we
508
- // know we need to do the deletion so just do it.
509
- void RefBase::Delete (RefBase* reference) {
510
- if ((reference->RefCount () != 0 ) || (reference->_delete_self ) ||
511
- (reference->_finalize_ran )) {
512
- delete reference;
513
- } else {
514
- // defer until finalizer runs as
515
- // it may already be queued
516
- reference->_delete_self = true ;
517
- }
496
+ return finalize_data_;
518
497
}
519
498
520
499
uint32_t RefBase::Ref () {
521
- return ++_refcount ;
500
+ return ++refcount_ ;
522
501
}
523
502
524
503
uint32_t RefBase::Unref () {
525
- if (_refcount == 0 ) {
504
+ if (refcount_ == 0 ) {
526
505
return 0 ;
527
506
}
528
- return --_refcount ;
507
+ return --refcount_ ;
529
508
}
530
509
531
510
uint32_t RefBase::RefCount () {
532
- return _refcount;
533
- }
534
-
535
- void RefBase::Finalize (bool is_env_teardown) {
536
- // In addition to being called during environment teardown, this method is
537
- // also the entry point for the garbage collector. During environment
538
- // teardown we have to remove the garbage collector's reference to this
539
- // method so that, if, as part of the user's callback, JS gets executed,
540
- // resulting in a garbage collection pass, this method is not re-entered as
541
- // part of that pass, because that'll cause a double free (as seen in
542
- // https://github.com/nodejs/node/issues/37236).
543
- //
544
- // Since this class does not have access to the V8 persistent reference,
545
- // this method is overridden in the `Reference` class below. Therein the
546
- // weak callback is removed, ensuring that the garbage collector does not
547
- // re-enter this method, and the method chains up to continue the process of
548
- // environment-teardown-induced finalization.
549
-
550
- // During environment teardown we have to convert a strong reference to
551
- // a weak reference to force the deferring behavior if the user's finalizer
552
- // happens to delete this reference so that the code in this function that
553
- // follows the call to the user's finalizer may safely access variables from
554
- // this instance.
555
- if (is_env_teardown && RefCount () > 0 ) _refcount = 0 ;
556
-
557
- if (_finalize_callback != nullptr ) {
558
- // This ensures that we never call the finalizer twice.
559
- napi_finalize fini = _finalize_callback;
560
- _finalize_callback = nullptr ;
561
- _env->CallFinalizer (fini, _finalize_data, _finalize_hint);
562
- }
563
-
564
- // this is safe because if a request to delete the reference
565
- // is made in the finalize_callback it will defer deletion
566
- // to this block and set _delete_self to true
567
- if (_delete_self || is_env_teardown) {
568
- Delete (this );
569
- } else {
570
- _finalize_ran = true ;
511
+ return refcount_;
512
+ }
513
+
514
+ void RefBase::Finalize () {
515
+ bool delete_self = delete_self_;
516
+ // Swap out the field finalize_callback so that it can not be accidentally
517
+ // called more than once.
518
+ napi_finalize finalize_callback = finalize_callback_;
519
+ finalize_callback_ = nullptr ;
520
+
521
+ // Either the RefBase is going to be deleted in the finalize_callback or not,
522
+ // it should be removed from the tracked list.
523
+ Unlink ();
524
+ // 1. If the finalize_callback is present, it should either delete the
525
+ // RefBase, or set the flag `delete_self`.
526
+ // 2. If the finalizer is not present, the RefBase can be deleted after the
527
+ // call.
528
+ if (finalize_callback != nullptr ) {
529
+ env_->CallFinalizer (finalize_callback, finalize_data_, finalize_hint_);
530
+ // No access to `this` after finalize_callback is called.
531
+ }
532
+
533
+ // If the RefBase is not self-destructive, userland code should delete it.
534
+ // Now delete it if it is self-destructive.
535
+ if (delete_self) {
536
+ delete this ;
571
537
}
572
538
}
573
539
574
540
template <typename ... Args>
575
541
Reference::Reference (napi_env env, v8::Local<v8::Value> value, Args&&... args)
576
542
: RefBase(env, std::forward<Args>(args)...),
577
- _persistent(env->isolate, value),
578
- _secondPassParameter(new SecondPassCallParameterRef(this )),
579
- _secondPassScheduled(false ) {
543
+ persistent_(env->isolate, value) {
580
544
if (RefCount () == 0 ) {
581
545
SetWeak ();
582
546
}
583
547
}
584
548
549
+ Reference::~Reference () {
550
+ // Reset the handle. And no weak callback will be invoked.
551
+ persistent_.Reset ();
552
+ }
553
+
585
554
Reference* Reference::New (napi_env env,
586
555
v8::Local<v8::Value> value,
587
556
uint32_t initial_refcount,
@@ -598,24 +567,25 @@ Reference* Reference::New(napi_env env,
598
567
finalize_hint);
599
568
}
600
569
601
- Reference::~Reference () {
602
- // If the second pass callback is scheduled, it will delete the
603
- // parameter passed to it, otherwise it will never be scheduled
604
- // and we need to delete it here.
605
- if (!_secondPassScheduled) {
606
- delete _secondPassParameter;
607
- }
608
- }
609
-
610
570
uint32_t Reference::Ref () {
571
+ // When the persistent_ is cleared in the WeakCallback, and a second pass
572
+ // callback is pending, return 0 unconditionally.
573
+ if (persistent_.IsEmpty ()) {
574
+ return 0 ;
575
+ }
611
576
uint32_t refcount = RefBase::Ref ();
612
577
if (refcount == 1 ) {
613
- ClearWeak ();
578
+ persistent_. ClearWeak ();
614
579
}
615
580
return refcount;
616
581
}
617
582
618
583
uint32_t Reference::Unref () {
584
+ // When the persistent_ is cleared in the WeakCallback, and a second pass
585
+ // callback is pending, return 0 unconditionally.
586
+ if (persistent_.IsEmpty ()) {
587
+ return 0 ;
588
+ }
619
589
uint32_t old_refcount = RefCount ();
620
590
uint32_t refcount = RefBase::Unref ();
621
591
if (old_refcount == 1 && refcount == 0 ) {
@@ -625,99 +595,36 @@ uint32_t Reference::Unref() {
625
595
}
626
596
627
597
v8::Local<v8::Value> Reference::Get () {
628
- if (_persistent .IsEmpty ()) {
598
+ if (persistent_ .IsEmpty ()) {
629
599
return v8::Local<v8::Value>();
630
600
} else {
631
- return v8::Local<v8::Value>::New (_env ->isolate , _persistent );
601
+ return v8::Local<v8::Value>::New (env_ ->isolate , persistent_ );
632
602
}
633
603
}
634
604
635
- void Reference::Finalize (bool is_env_teardown) {
636
- // During env teardown, `~napi_env()` alone is responsible for finalizing.
637
- // Thus, we don't want any stray gc passes to trigger a second call to
638
- // `RefBase::Finalize()`. ClearWeak will ensure that even if the
639
- // gc is in progress no Finalization will be run for this Reference
640
- // by the gc.
641
- if (is_env_teardown) {
642
- ClearWeak ();
643
- }
605
+ void Reference::Finalize () {
606
+ // Unconditionally reset the persistent handle so that no weak callback will
607
+ // be invoked again.
608
+ persistent_.Reset ();
644
609
645
610
// Chain up to perform the rest of the finalization.
646
- RefBase::Finalize (is_env_teardown);
647
- }
648
-
649
- // ClearWeak is marking the Reference so that the gc should not
650
- // collect it, but it is possible that a second pass callback
651
- // may have been scheduled already if we are in shutdown. We clear
652
- // the secondPassParameter so that even if it has been
653
- // scheduled no Finalization will be run.
654
- void Reference::ClearWeak () {
655
- if (!_persistent.IsEmpty ()) {
656
- _persistent.ClearWeak ();
657
- }
658
- if (_secondPassParameter != nullptr ) {
659
- *_secondPassParameter = nullptr ;
660
- }
611
+ RefBase::Finalize ();
661
612
}
662
613
663
614
// Mark the reference as weak and eligible for collection
664
615
// by the gc.
665
616
void Reference::SetWeak () {
666
- if (_secondPassParameter == nullptr ) {
667
- // This means that the Reference has already been processed
668
- // by the second pass callback, so its already been Finalized, do
669
- // nothing
670
- return ;
671
- }
672
- _persistent.SetWeak (
673
- _secondPassParameter, FinalizeCallback, v8::WeakCallbackType::kParameter );
674
- *_secondPassParameter = this ;
617
+ persistent_.SetWeak (this , WeakCallback, v8::WeakCallbackType::kParameter );
675
618
}
676
619
677
620
// The N-API finalizer callback may make calls into the engine. V8's heap is
678
621
// not in a consistent state during the weak callback, and therefore it does
679
- // not support calls back into it. However, it provides a mechanism for adding
680
- // a finalizer which may make calls back into the engine by allowing us to
681
- // attach such a second-pass finalizer from the first pass finalizer. Thus,
682
- // we do that here to ensure that the N-API finalizer callback is free to call
683
- // into the engine.
684
- void Reference::FinalizeCallback (
685
- const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
686
- SecondPassCallParameterRef* parameter = data.GetParameter ();
687
- Reference* reference = *parameter;
688
- if (reference == nullptr ) {
689
- return ;
690
- }
691
-
692
- // The reference must be reset during the first pass.
693
- reference->_persistent .Reset ();
694
- // Mark the parameter not delete-able until the second pass callback is
695
- // invoked.
696
- reference->_secondPassScheduled = true ;
697
-
698
- data.SetSecondPassCallback (SecondPassCallback);
699
- }
700
-
701
- // Second pass callbacks are scheduled with platform tasks. At env teardown,
702
- // the tasks may have already be scheduled and we are unable to cancel the
703
- // second pass callback task. We have to make sure that parameter is kept
704
- // alive until the second pass callback is been invoked. In order to do
705
- // this and still allow our code to Finalize/delete the Reference during
706
- // shutdown we have to use a separately allocated parameter instead
707
- // of a parameter within the Reference object itself. This is what
708
- // is stored in _secondPassParameter and it is allocated in the
709
- // constructor for the Reference.
710
- void Reference::SecondPassCallback (
711
- const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
712
- SecondPassCallParameterRef* parameter = data.GetParameter ();
713
- Reference* reference = *parameter;
714
- delete parameter;
715
- if (reference == nullptr ) {
716
- // the reference itself has already been deleted so nothing to do
717
- return ;
718
- }
719
- reference->_secondPassParameter = nullptr ;
720
- reference->Finalize ();
622
+ // not support calls back into it. Enqueue the invocation of the finalizer.
623
+ void Reference::WeakCallback (const v8::WeakCallbackInfo<Reference>& data) {
624
+ Reference* reference = data.GetParameter ();
625
+ // The reference must be reset during the weak callback as the API protocol.
626
+ reference->persistent_ .Reset ();
627
+ reference->env_ ->EnqueueFinalizer (reference);
721
628
}
722
629
723
630
} // end of namespace v8impl
@@ -2515,7 +2422,7 @@ napi_status NAPI_CDECL napi_delete_reference(napi_env env, napi_ref ref) {
2515
2422
CHECK_ENV (env);
2516
2423
CHECK_ARG (env, ref);
2517
2424
2518
- v8impl::Reference::Delete ( reinterpret_cast <v8impl::Reference*>(ref) );
2425
+ delete reinterpret_cast <v8impl::Reference*>(ref);
2519
2426
2520
2427
return napi_clear_last_error (env);
2521
2428
}
@@ -3205,7 +3112,7 @@ napi_status NAPI_CDECL napi_set_instance_data(napi_env env,
3205
3112
if (old_data != nullptr ) {
3206
3113
// Our contract so far has been to not finalize any old data there may be.
3207
3114
// So we simply delete it.
3208
- v8impl::RefBase::Delete ( old_data) ;
3115
+ delete old_data;
3209
3116
}
3210
3117
3211
3118
env->instance_data =
0 commit comments