@@ -313,16 +313,16 @@ class RefBase : protected Finalizer, RefTracker {
313
313
};
314
314
315
315
class Reference : public RefBase {
316
+ using SecondPassCallParameterRef = Reference*;
317
+
316
318
protected:
317
319
template <typename ... Args>
318
- Reference (napi_env env,
319
- v8::Local<v8::Value> value,
320
- Args&&... args)
320
+ Reference (napi_env env, v8::Local<v8::Value> value, Args&&... args)
321
321
: RefBase(env, std::forward<Args>(args)...),
322
- _persistent (env->isolate, value) {
322
+ _persistent (env->isolate, value),
323
+ _secondPassParameter(new SecondPassCallParameterRef(this )) {
323
324
if (RefCount () == 0 ) {
324
- _persistent.SetWeak (
325
- this , FinalizeCallback, v8::WeakCallbackType::kParameter );
325
+ SetWeak ();
326
326
}
327
327
}
328
328
@@ -343,10 +343,19 @@ class Reference : public RefBase {
343
343
finalize_hint);
344
344
}
345
345
346
+ virtual ~Reference () {
347
+ // If the second pass callback is scheduled, it will delete the
348
+ // parameter passed to it, otherwise it will never be scheduled
349
+ // and we need to delete it here.
350
+ if (_secondPassParameter != nullptr ) {
351
+ delete _secondPassParameter;
352
+ }
353
+ }
354
+
346
355
inline uint32_t Ref () {
347
356
uint32_t refcount = RefBase::Ref ();
348
357
if (refcount == 1 ) {
349
- _persistent. ClearWeak ();
358
+ ClearWeak ();
350
359
}
351
360
return refcount;
352
361
}
@@ -355,8 +364,7 @@ class Reference : public RefBase {
355
364
uint32_t old_refcount = RefCount ();
356
365
uint32_t refcount = RefBase::Unref ();
357
366
if (old_refcount == 1 && refcount == 0 ) {
358
- _persistent.SetWeak (
359
- this , FinalizeCallback, v8::WeakCallbackType::kParameter );
367
+ SetWeak ();
360
368
}
361
369
return refcount;
362
370
}
@@ -373,38 +381,94 @@ class Reference : public RefBase {
373
381
inline void Finalize (bool is_env_teardown = false ) override {
374
382
// During env teardown, `~napi_env()` alone is responsible for finalizing.
375
383
// Thus, we don't want any stray gc passes to trigger a second call to
376
- // `Finalize()`, so let's reset the persistent here if nothing is
377
- // keeping it alive.
378
- if (is_env_teardown && _persistent.IsWeak ()) {
379
- _persistent.ClearWeak ();
384
+ // `RefBase::Finalize()`. ClearWeak will ensure that even if the
385
+ // gc is in progress no Finalization will be run for this Reference
386
+ // by the gc.
387
+ if (is_env_teardown) {
388
+ ClearWeak ();
380
389
}
381
390
382
391
// Chain up to perform the rest of the finalization.
383
392
RefBase::Finalize (is_env_teardown);
384
393
}
385
394
386
395
private:
396
+ // ClearWeak is marking the Reference so that the gc should not
397
+ // collect it, but it is possible that a second pass callback
398
+ // may have been scheduled already if we are in shutdown. We clear
399
+ // the secondPassParameter so that even if it has been
400
+ // secheduled no Finalization will be run.
401
+ inline void ClearWeak () {
402
+ if (!_persistent.IsEmpty ()) {
403
+ _persistent.ClearWeak ();
404
+ }
405
+ if (_secondPassParameter != nullptr ) {
406
+ *_secondPassParameter = nullptr ;
407
+ }
408
+ }
409
+
410
+ // Mark the reference as weak and eligible for collection
411
+ // by the gc.
412
+ inline void SetWeak () {
413
+ if (_secondPassParameter == nullptr ) {
414
+ // This means that the Reference has already been processed
415
+ // by the second pass calback, so its already been Finalized, do
416
+ // nothing
417
+ return ;
418
+ }
419
+ _persistent.SetWeak (
420
+ _secondPassParameter, FinalizeCallback,
421
+ v8::WeakCallbackType::kParameter );
422
+ *_secondPassParameter = this ;
423
+ }
424
+
387
425
// The N-API finalizer callback may make calls into the engine. V8's heap is
388
426
// not in a consistent state during the weak callback, and therefore it does
389
427
// not support calls back into it. However, it provides a mechanism for adding
390
428
// a finalizer which may make calls back into the engine by allowing us to
391
429
// attach such a second-pass finalizer from the first pass finalizer. Thus,
392
430
// we do that here to ensure that the N-API finalizer callback is free to call
393
431
// into the engine.
394
- static void FinalizeCallback (const v8::WeakCallbackInfo<Reference>& data) {
395
- Reference* reference = data.GetParameter ();
432
+ static void FinalizeCallback (
433
+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
434
+ SecondPassCallParameterRef* parameter = data.GetParameter ();
435
+ Reference* reference = *parameter;
436
+ if (reference == nullptr ) {
437
+ return ;
438
+ }
396
439
397
440
// The reference must be reset during the first pass.
398
441
reference->_persistent .Reset ();
442
+ // Mark the parameter not delete-able until the second pass callback is
443
+ // invoked.
444
+ reference->_secondPassParameter = nullptr ;
399
445
400
446
data.SetSecondPassCallback (SecondPassCallback);
401
447
}
402
448
403
- static void SecondPassCallback (const v8::WeakCallbackInfo<Reference>& data) {
404
- data.GetParameter ()->Finalize ();
449
+ // Second pass callbacks are scheduled with platform tasks. At env teardown,
450
+ // the tasks may have already be scheduled and we are unable to cancel the
451
+ // second pass callback task. We have to make sure that parameter is kept
452
+ // alive until the second pass callback is been invoked. In order to do
453
+ // this and still allow our code to Finalize/delete the Reference during
454
+ // shutdown we have to use a seperately allocated parameter instead
455
+ // of a parameter within the Reference object itself. This is what
456
+ // is stored in _secondPassParameter and it is alocated in the
457
+ // constructor for the Reference.
458
+ static void SecondPassCallback (
459
+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
460
+ SecondPassCallParameterRef* parameter = data.GetParameter ();
461
+ Reference* reference = *parameter;
462
+ delete parameter;
463
+ if (reference == nullptr ) {
464
+ // the reference itself has already been deleted so nothing to do
465
+ return ;
466
+ }
467
+ reference->Finalize ();
405
468
}
406
469
407
470
v8impl::Persistent<v8::Value> _persistent;
471
+ SecondPassCallParameterRef* _secondPassParameter;
408
472
};
409
473
410
474
class ArrayBufferReference final : public Reference {
0 commit comments