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