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