@@ -270,6 +270,20 @@ class RefBase : protected Finalizer, RefTracker {
270270
271271 protected: 
272272  inline  void  Finalize (bool  is_env_teardown = false ) override  {
273+     //  In addition to being called during environment teardown, this method is
274+     //  also the entry point for the garbage collector. During environment
275+     //  teardown we have to remove the garbage collector's reference to this
276+     //  method so that, if, as part of the user's callback, JS gets executed,
277+     //  resulting in a garbage collection pass, this method is not re-entered as
278+     //  part of that pass, because that'll cause a double free (as seen in
279+     //  https://github.com/nodejs/node/issues/37236).
280+     // 
281+     //  Since this class does not have access to the V8 persistent reference,
282+     //  this method is overridden in the `Reference` class below. Therein the
283+     //  weak callback is removed, ensuring that the garbage collector does not
284+     //  re-enter this method, and the method chains up to continue the process of
285+     //  environment-teardown-induced finalization.
286+ 
273287    //  During environment teardown we have to convert a strong reference to
274288    //  a weak reference to force the deferring behavior if the user's finalizer
275289    //  happens to delete this reference so that the code in this function that
@@ -278,9 +292,10 @@ class RefBase : protected Finalizer, RefTracker {
278292    if  (is_env_teardown && RefCount () > 0 ) _refcount = 0 ;
279293
280294    if  (_finalize_callback != nullptr ) {
281-       _env->CallFinalizer (_finalize_callback, _finalize_data, _finalize_hint);
282295      //  This ensures that we never call the finalizer twice.
296+       napi_finalize fini = _finalize_callback;
283297      _finalize_callback = nullptr ;
298+       _env->CallFinalizer (fini, _finalize_data, _finalize_hint);
284299    }
285300
286301    //  this is safe because if a request to delete the reference
@@ -355,6 +370,17 @@ class Reference : public RefBase {
355370    }
356371  }
357372
373+  protected: 
374+   inline  void  Finalize (bool  is_env_teardown = false ) override  {
375+     //  During env teardown, `~napi_env()` alone is responsible for finalizing.
376+     //  Thus, we don't want any stray gc passes to trigger a second call to
377+     //  `Finalize()`, so let's reset the persistent here.
378+     if  (is_env_teardown) _persistent.ClearWeak ();
379+ 
380+     //  Chain up to perform the rest of the finalization.
381+     RefBase::Finalize (is_env_teardown);
382+   }
383+ 
358384 private: 
359385  //  The N-API finalizer callback may make calls into the engine. V8's heap is
360386  //  not in a consistent state during the weak callback, and therefore it does
0 commit comments