Skip to content

Commit 7b2de34

Browse files
committed
node-api: make reference weak parameter an indirect link to references
As the cancellation of second pass callbacks are not reachable from the current v8 API, and the second pass callbacks are scheduled with NodePlatform's task runner, we have to ensure that the weak parameter holds indirect access to the v8impl::Reference object so that the object can be destroyed on addon env teardown before the whole node env is able to shutdown.
1 parent 01d8b39 commit 7b2de34

File tree

1 file changed

+81
-17
lines changed

1 file changed

+81
-17
lines changed

src/js_native_api_v8.cc

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -314,16 +314,29 @@ class RefBase : protected Finalizer, RefTracker {
314314
};
315315

316316
class Reference : public RefBase {
317+
class PersistentWeakParameter {
318+
public:
319+
explicit PersistentWeakParameter(Reference* reference)
320+
: _reference(reference) {}
321+
322+
inline Reference* reference() { return _reference; }
323+
324+
inline void Reset(Reference* reference = nullptr) {
325+
_reference = reference;
326+
}
327+
328+
private:
329+
Reference* _reference;
330+
};
331+
317332
protected:
318333
template <typename... Args>
319-
Reference(napi_env env,
320-
v8::Local<v8::Value> value,
321-
Args&&... args)
334+
Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args)
322335
: RefBase(env, std::forward<Args>(args)...),
323-
_persistent(env->isolate, value) {
336+
_persistent(env->isolate, value),
337+
_parameter(new PersistentWeakParameter(this)) {
324338
if (RefCount() == 0) {
325-
_persistent.SetWeak(
326-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
339+
SetWeak();
327340
}
328341
}
329342

@@ -344,10 +357,18 @@ class Reference : public RefBase {
344357
finalize_hint);
345358
}
346359

360+
virtual ~Reference() {
361+
// If the second pass callback is scheduled, they are responsible for
362+
// deleting the weak parameter.
363+
if (_parameter != nullptr) {
364+
delete _parameter;
365+
}
366+
}
367+
347368
inline uint32_t Ref() {
348369
uint32_t refcount = RefBase::Ref();
349370
if (refcount == 1) {
350-
_persistent.ClearWeak();
371+
ClearWeak();
351372
}
352373
return refcount;
353374
}
@@ -356,8 +377,7 @@ class Reference : public RefBase {
356377
uint32_t old_refcount = RefCount();
357378
uint32_t refcount = RefBase::Unref();
358379
if (old_refcount == 1 && refcount == 0) {
359-
_persistent.SetWeak(
360-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
380+
SetWeak();
361381
}
362382
return refcount;
363383
}
@@ -374,38 +394,82 @@ class Reference : public RefBase {
374394
inline void Finalize(bool is_env_teardown = false) override {
375395
// During env teardown, `~napi_env()` alone is responsible for finalizing.
376396
// 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();
397+
// `RefBase::Finalize()`, so let's clear the weakness here.
398+
if (is_env_teardown) {
399+
ClearWeak();
381400
}
382401

383402
// Chain up to perform the rest of the finalization.
384403
RefBase::Finalize(is_env_teardown);
385404
}
386405

387406
private:
407+
// The persistent may have been reset in the first pass weak callback.
408+
// However a second pass callback may have been scheduled already, we
409+
// must ensure that the weak parameter no longer link to this reference
410+
// so that the second pass callback is not able to calling into the
411+
// `Reference::Finalize()`.
412+
inline void ClearWeak() {
413+
if (!_persistent.IsEmpty()) {
414+
_persistent.ClearWeak();
415+
}
416+
if (_parameter != nullptr) {
417+
_parameter->Reset();
418+
}
419+
}
420+
421+
// Setup the weakness callback and parameter.
422+
inline void SetWeak() {
423+
// If second pass callback has been scheduled, suppress the set weak.
424+
if (_parameter == nullptr) {
425+
return;
426+
}
427+
_persistent.SetWeak(
428+
_parameter, FinalizeCallback, v8::WeakCallbackType::kParameter);
429+
_parameter->Reset(this);
430+
}
431+
388432
// The N-API finalizer callback may make calls into the engine. V8's heap is
389433
// not in a consistent state during the weak callback, and therefore it does
390434
// not support calls back into it. However, it provides a mechanism for adding
391435
// a finalizer which may make calls back into the engine by allowing us to
392436
// attach such a second-pass finalizer from the first pass finalizer. Thus,
393437
// we do that here to ensure that the N-API finalizer callback is free to call
394438
// into the engine.
395-
static void FinalizeCallback(const v8::WeakCallbackInfo<Reference>& data) {
396-
Reference* reference = data.GetParameter();
439+
static void FinalizeCallback(
440+
const v8::WeakCallbackInfo<PersistentWeakParameter>& data) {
441+
PersistentWeakParameter* parameter = data.GetParameter();
442+
Reference* reference = parameter->reference();
443+
if (reference == nullptr) {
444+
return;
445+
}
397446

398447
// The reference must be reset during the first pass.
399448
reference->_persistent.Reset();
449+
// Mark the parameter not delete-able until the second pass callback is
450+
// invoked.
451+
reference->_parameter = nullptr;
400452

401453
data.SetSecondPassCallback(SecondPassCallback);
402454
}
403455

404-
static void SecondPassCallback(const v8::WeakCallbackInfo<Reference>& data) {
405-
data.GetParameter()->Finalize();
456+
// Second pass callbacks are scheduled with platform tasks. At env teardown,
457+
// the tasks may have already be scheduled and we are unable to cancel the
458+
// second pass callback task. So we have to make sure that parameter is kept
459+
// alive until the second pass callback is been invoked.
460+
static void SecondPassCallback(
461+
const v8::WeakCallbackInfo<PersistentWeakParameter>& data) {
462+
PersistentWeakParameter* parameter = data.GetParameter();
463+
Reference* reference = parameter->reference();
464+
delete parameter;
465+
if (reference == nullptr) {
466+
return;
467+
}
468+
reference->Finalize();
406469
}
407470

408471
v8impl::Persistent<v8::Value> _persistent;
472+
PersistentWeakParameter* _parameter;
409473
};
410474

411475
enum UnwrapAction {

0 commit comments

Comments
 (0)