@@ -269,6 +269,20 @@ class RefBase : protected Finalizer, RefTracker {
269
269
270
270
protected:
271
271
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
+
272
286
// During environment teardown we have to convert a strong reference to
273
287
// a weak reference to force the deferring behavior if the user's finalizer
274
288
// happens to delete this reference so that the code in this function that
@@ -277,9 +291,10 @@ class RefBase : protected Finalizer, RefTracker {
277
291
if (is_env_teardown && RefCount () > 0 ) _refcount = 0 ;
278
292
279
293
if (_finalize_callback != nullptr ) {
280
- _env->CallFinalizer (_finalize_callback, _finalize_data, _finalize_hint);
281
294
// This ensures that we never call the finalizer twice.
295
+ napi_finalize fini = _finalize_callback;
282
296
_finalize_callback = nullptr ;
297
+ _env->CallFinalizer (fini, _finalize_data, _finalize_hint);
283
298
}
284
299
285
300
// this is safe because if a request to delete the reference
@@ -354,6 +369,17 @@ class Reference : public RefBase {
354
369
}
355
370
}
356
371
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
+
357
383
private:
358
384
// The N-API finalizer callback may make calls into the engine. V8's heap is
359
385
// not in a consistent state during the weak callback, and therefore it does
0 commit comments