Skip to content

Commit f987c3b

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. PR-URL: nodejs#38000 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]>
1 parent 8d4936d commit f987c3b

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,16 @@ class RefBase : protected Finalizer, RefTracker {
314314
};
315315

316316
class Reference : public RefBase {
317+
using SecondPassCallParameterRef = Reference*;
318+
317319
protected:
318320
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)
322322
: RefBase(env, std::forward<Args>(args)...),
323-
_persistent(env->isolate, value) {
323+
_persistent(env->isolate, value),
324+
_secondPassParameter(new SecondPassCallParameterRef(this)) {
324325
if (RefCount() == 0) {
325-
_persistent.SetWeak(
326-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
326+
SetWeak();
327327
}
328328
}
329329

@@ -344,10 +344,19 @@ class Reference : public RefBase {
344344
finalize_hint);
345345
}
346346

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+
347356
inline uint32_t Ref() {
348357
uint32_t refcount = RefBase::Ref();
349358
if (refcount == 1) {
350-
_persistent.ClearWeak();
359+
ClearWeak();
351360
}
352361
return refcount;
353362
}
@@ -356,8 +365,7 @@ class Reference : public RefBase {
356365
uint32_t old_refcount = RefCount();
357366
uint32_t refcount = RefBase::Unref();
358367
if (old_refcount == 1 && refcount == 0) {
359-
_persistent.SetWeak(
360-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
368+
SetWeak();
361369
}
362370
return refcount;
363371
}
@@ -374,38 +382,94 @@ class Reference : public RefBase {
374382
inline void Finalize(bool is_env_teardown = false) override {
375383
// During env teardown, `~napi_env()` alone is responsible for finalizing.
376384
// 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();
381390
}
382391

383392
// Chain up to perform the rest of the finalization.
384393
RefBase::Finalize(is_env_teardown);
385394
}
386395

387396
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+
388426
// The N-API finalizer callback may make calls into the engine. V8's heap is
389427
// not in a consistent state during the weak callback, and therefore it does
390428
// not support calls back into it. However, it provides a mechanism for adding
391429
// a finalizer which may make calls back into the engine by allowing us to
392430
// attach such a second-pass finalizer from the first pass finalizer. Thus,
393431
// we do that here to ensure that the N-API finalizer callback is free to call
394432
// 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+
}
397440

398441
// The reference must be reset during the first pass.
399442
reference->_persistent.Reset();
443+
// Mark the parameter not delete-able until the second pass callback is
444+
// invoked.
445+
reference->_secondPassParameter = nullptr;
400446

401447
data.SetSecondPassCallback(SecondPassCallback);
402448
}
403449

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();
406469
}
407470

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

411475
enum UnwrapAction {

0 commit comments

Comments
 (0)