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