@@ -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
}
@@ -374,38 +382,94 @@ class Reference : public RefBase {
374
382
inline void Finalize (bool is_env_teardown = false ) override {
375
383
// During env teardown, `~napi_env()` alone is responsible for finalizing.
376
384
// Thus, we don't want any stray gc passes to trigger a second call to
377
- // `Finalize()`, so let's reset the persistent here if nothing is
378
- // keeping it alive.
379
- if (is_env_teardown && _persistent.IsWeak ()) {
380
- _persistent.ClearWeak ();
385
+ // `RefBase::Finalize()`. ClearWeak will ensure that even if the
386
+ // gc is in progress no Finalization will be run for this Reference
387
+ // by the gc.
388
+ if (is_env_teardown) {
389
+ ClearWeak ();
381
390
}
382
391
383
392
// Chain up to perform the rest of the finalization.
384
393
RefBase::Finalize (is_env_teardown);
385
394
}
386
395
387
396
private:
397
+ // ClearWeak is marking the Reference so that the gc should not
398
+ // collect it, but it is possible that a second pass callback
399
+ // may have been scheduled already if we are in shutdown. We clear
400
+ // the secondPassParameter so that even if it has been
401
+ // secheduled no Finalization will be run.
402
+ inline void ClearWeak () {
403
+ if (!_persistent.IsEmpty ()) {
404
+ _persistent.ClearWeak ();
405
+ }
406
+ if (_secondPassParameter != nullptr ) {
407
+ *_secondPassParameter = nullptr ;
408
+ }
409
+ }
410
+
411
+ // Mark the reference as weak and eligible for collection
412
+ // by the gc.
413
+ inline void SetWeak () {
414
+ if (_secondPassParameter == nullptr ) {
415
+ // This means that the Reference has already been processed
416
+ // by the second pass calback, so its already been Finalized, do
417
+ // nothing
418
+ return ;
419
+ }
420
+ _persistent.SetWeak (
421
+ _secondPassParameter, FinalizeCallback,
422
+ v8::WeakCallbackType::kParameter );
423
+ *_secondPassParameter = this ;
424
+ }
425
+
388
426
// The N-API finalizer callback may make calls into the engine. V8's heap is
389
427
// not in a consistent state during the weak callback, and therefore it does
390
428
// not support calls back into it. However, it provides a mechanism for adding
391
429
// a finalizer which may make calls back into the engine by allowing us to
392
430
// attach such a second-pass finalizer from the first pass finalizer. Thus,
393
431
// we do that here to ensure that the N-API finalizer callback is free to call
394
432
// into the engine.
395
- static void FinalizeCallback (const v8::WeakCallbackInfo<Reference>& data) {
396
- Reference* reference = data.GetParameter ();
433
+ static void FinalizeCallback (
434
+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
435
+ SecondPassCallParameterRef* parameter = data.GetParameter ();
436
+ Reference* reference = *parameter;
437
+ if (reference == nullptr ) {
438
+ return ;
439
+ }
397
440
398
441
// The reference must be reset during the first pass.
399
442
reference->_persistent .Reset ();
443
+ // Mark the parameter not delete-able until the second pass callback is
444
+ // invoked.
445
+ reference->_secondPassParameter = nullptr ;
400
446
401
447
data.SetSecondPassCallback (SecondPassCallback);
402
448
}
403
449
404
- static void SecondPassCallback (const v8::WeakCallbackInfo<Reference>& data) {
405
- data.GetParameter ()->Finalize ();
450
+ // Second pass callbacks are scheduled with platform tasks. At env teardown,
451
+ // the tasks may have already be scheduled and we are unable to cancel the
452
+ // second pass callback task. We have to make sure that parameter is kept
453
+ // alive until the second pass callback is been invoked. In order to do
454
+ // this and still allow our code to Finalize/delete the Reference during
455
+ // shutdown we have to use a seperately allocated parameter instead
456
+ // of a parameter within the Reference object itself. This is what
457
+ // is stored in _secondPassParameter and it is alocated in the
458
+ // constructor for the Reference.
459
+ static void SecondPassCallback (
460
+ const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
461
+ SecondPassCallParameterRef* parameter = data.GetParameter ();
462
+ Reference* reference = *parameter;
463
+ delete parameter;
464
+ if (reference == nullptr ) {
465
+ // the reference itself has already been deleted so nothing to do
466
+ return ;
467
+ }
468
+ reference->Finalize ();
406
469
}
407
470
408
471
v8impl::Persistent<v8::Value> _persistent;
472
+ SecondPassCallParameterRef* _secondPassParameter;
409
473
};
410
474
411
475
enum UnwrapAction {
0 commit comments