Skip to content

Commit 8b4e754

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 8b4e754

File tree

1 file changed

+84
-17
lines changed

1 file changed

+84
-17
lines changed

src/js_native_api_v8.cc

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -314,16 +314,38 @@ 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), _second_pass_callback_scheduled(false) {}
321+
322+
inline Reference* reference() { return _reference; }
323+
324+
inline void Reset(Reference* reference = nullptr) {
325+
_reference = reference;
326+
}
327+
328+
inline bool second_pass_callback_scheduled() {
329+
return _second_pass_callback_scheduled;
330+
}
331+
332+
inline void set_second_pass_callback_scheduled() {
333+
_second_pass_callback_scheduled = true;
334+
}
335+
336+
private:
337+
Reference* _reference;
338+
bool _second_pass_callback_scheduled;
339+
};
340+
317341
protected:
318342
template <typename... Args>
319-
Reference(napi_env env,
320-
v8::Local<v8::Value> value,
321-
Args&&... args)
343+
Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args)
322344
: RefBase(env, std::forward<Args>(args)...),
323-
_persistent(env->isolate, value) {
345+
_persistent(env->isolate, value),
346+
_parameter(new PersistentWeakParameter(this)) {
324347
if (RefCount() == 0) {
325-
_persistent.SetWeak(
326-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
348+
SetWeak();
327349
}
328350
}
329351

@@ -344,10 +366,18 @@ class Reference : public RefBase {
344366
finalize_hint);
345367
}
346368

369+
virtual ~Reference() {
370+
if (!_parameter->second_pass_callback_scheduled()) {
371+
// The second pass weak callback is not scheduled, there is no pending
372+
// weak callbacks to release the weak parameter. Just deleting it.
373+
delete _parameter;
374+
}
375+
}
376+
347377
inline uint32_t Ref() {
348378
uint32_t refcount = RefBase::Ref();
349379
if (refcount == 1) {
350-
_persistent.ClearWeak();
380+
ClearWeak();
351381
}
352382
return refcount;
353383
}
@@ -356,8 +386,7 @@ class Reference : public RefBase {
356386
uint32_t old_refcount = RefCount();
357387
uint32_t refcount = RefBase::Unref();
358388
if (old_refcount == 1 && refcount == 0) {
359-
_persistent.SetWeak(
360-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
389+
SetWeak();
361390
}
362391
return refcount;
363392
}
@@ -374,38 +403,76 @@ class Reference : public RefBase {
374403
inline void Finalize(bool is_env_teardown = false) override {
375404
// During env teardown, `~napi_env()` alone is responsible for finalizing.
376405
// 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();
406+
// `RefBase::Finalize()`, so let's clear the weakness here.
407+
if (is_env_teardown) {
408+
ClearWeak();
381409
}
382410

383411
// Chain up to perform the rest of the finalization.
384412
RefBase::Finalize(is_env_teardown);
385413
}
386414

387415
private:
416+
// The persistent may have been reset in the first pass weak callback.
417+
// However a second pass callback may have been scheduled already, we
418+
// must ensure that the weak parameter no longer link to this reference
419+
// so that the second pass callback is not able to calling into the
420+
// `Reference::Finalize()`.
421+
inline void ClearWeak() {
422+
if (!_persistent.IsEmpty()) {
423+
_persistent.ClearWeak();
424+
}
425+
_parameter->Reset();
426+
}
427+
428+
// Setup the weakness callback and parameter.
429+
inline void SetWeak() {
430+
_persistent.SetWeak(
431+
_parameter, FinalizeCallback, v8::WeakCallbackType::kParameter);
432+
_parameter->Reset(this);
433+
}
434+
388435
// The N-API finalizer callback may make calls into the engine. V8's heap is
389436
// not in a consistent state during the weak callback, and therefore it does
390437
// not support calls back into it. However, it provides a mechanism for adding
391438
// a finalizer which may make calls back into the engine by allowing us to
392439
// attach such a second-pass finalizer from the first pass finalizer. Thus,
393440
// we do that here to ensure that the N-API finalizer callback is free to call
394441
// into the engine.
395-
static void FinalizeCallback(const v8::WeakCallbackInfo<Reference>& data) {
396-
Reference* reference = data.GetParameter();
442+
static void FinalizeCallback(
443+
const v8::WeakCallbackInfo<PersistentWeakParameter>& data) {
444+
PersistentWeakParameter* parameter = data.GetParameter();
445+
Reference* reference = parameter->reference();
446+
if (reference == nullptr) {
447+
return;
448+
}
397449

398450
// The reference must be reset during the first pass.
399451
reference->_persistent.Reset();
452+
// Mark the parameter not delete-able until the second pass callback is
453+
// invoked.
454+
parameter->set_second_pass_callback_scheduled();
400455

401456
data.SetSecondPassCallback(SecondPassCallback);
402457
}
403458

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

408474
v8impl::Persistent<v8::Value> _persistent;
475+
PersistentWeakParameter* _parameter;
409476
};
410477

411478
enum UnwrapAction {

0 commit comments

Comments
 (0)