Skip to content

Commit 81b4dc8

Browse files
legendecasrichardlau
authored andcommitted
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: #38000 Backport-PR-URL: #42512 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]>
1 parent 2aa9ca1 commit 81b4dc8

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

315315
class Reference : public RefBase {
316+
using SecondPassCallParameterRef = Reference*;
317+
316318
protected:
317319
template <typename... Args>
318-
Reference(napi_env env,
319-
v8::Local<v8::Value> value,
320-
Args&&... args)
320+
Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args)
321321
: RefBase(env, std::forward<Args>(args)...),
322-
_persistent(env->isolate, value) {
322+
_persistent(env->isolate, value),
323+
_secondPassParameter(new SecondPassCallParameterRef(this)) {
323324
if (RefCount() == 0) {
324-
_persistent.SetWeak(
325-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
325+
SetWeak();
326326
}
327327
}
328328

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

346+
virtual ~Reference() {
347+
// If the second pass callback is scheduled, it will delete the
348+
// parameter passed to it, otherwise it will never be scheduled
349+
// and we need to delete it here.
350+
if (_secondPassParameter != nullptr) {
351+
delete _secondPassParameter;
352+
}
353+
}
354+
346355
inline uint32_t Ref() {
347356
uint32_t refcount = RefBase::Ref();
348357
if (refcount == 1) {
349-
_persistent.ClearWeak();
358+
ClearWeak();
350359
}
351360
return refcount;
352361
}
@@ -355,8 +364,7 @@ class Reference : public RefBase {
355364
uint32_t old_refcount = RefCount();
356365
uint32_t refcount = RefBase::Unref();
357366
if (old_refcount == 1 && refcount == 0) {
358-
_persistent.SetWeak(
359-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
367+
SetWeak();
360368
}
361369
return refcount;
362370
}
@@ -373,38 +381,94 @@ class Reference : public RefBase {
373381
inline void Finalize(bool is_env_teardown = false) override {
374382
// During env teardown, `~napi_env()` alone is responsible for finalizing.
375383
// Thus, we don't want any stray gc passes to trigger a second call to
376-
// `Finalize()`, so let's reset the persistent here if nothing is
377-
// keeping it alive.
378-
if (is_env_teardown && _persistent.IsWeak()) {
379-
_persistent.ClearWeak();
384+
// `RefBase::Finalize()`. ClearWeak will ensure that even if the
385+
// gc is in progress no Finalization will be run for this Reference
386+
// by the gc.
387+
if (is_env_teardown) {
388+
ClearWeak();
380389
}
381390

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

386395
private:
396+
// ClearWeak is marking the Reference so that the gc should not
397+
// collect it, but it is possible that a second pass callback
398+
// may have been scheduled already if we are in shutdown. We clear
399+
// the secondPassParameter so that even if it has been
400+
// secheduled no Finalization will be run.
401+
inline void ClearWeak() {
402+
if (!_persistent.IsEmpty()) {
403+
_persistent.ClearWeak();
404+
}
405+
if (_secondPassParameter != nullptr) {
406+
*_secondPassParameter = nullptr;
407+
}
408+
}
409+
410+
// Mark the reference as weak and eligible for collection
411+
// by the gc.
412+
inline void SetWeak() {
413+
if (_secondPassParameter == nullptr) {
414+
// This means that the Reference has already been processed
415+
// by the second pass calback, so its already been Finalized, do
416+
// nothing
417+
return;
418+
}
419+
_persistent.SetWeak(
420+
_secondPassParameter, FinalizeCallback,
421+
v8::WeakCallbackType::kParameter);
422+
*_secondPassParameter = this;
423+
}
424+
387425
// The N-API finalizer callback may make calls into the engine. V8's heap is
388426
// not in a consistent state during the weak callback, and therefore it does
389427
// not support calls back into it. However, it provides a mechanism for adding
390428
// a finalizer which may make calls back into the engine by allowing us to
391429
// attach such a second-pass finalizer from the first pass finalizer. Thus,
392430
// we do that here to ensure that the N-API finalizer callback is free to call
393431
// into the engine.
394-
static void FinalizeCallback(const v8::WeakCallbackInfo<Reference>& data) {
395-
Reference* reference = data.GetParameter();
432+
static void FinalizeCallback(
433+
const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
434+
SecondPassCallParameterRef* parameter = data.GetParameter();
435+
Reference* reference = *parameter;
436+
if (reference == nullptr) {
437+
return;
438+
}
396439

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

400446
data.SetSecondPassCallback(SecondPassCallback);
401447
}
402448

403-
static void SecondPassCallback(const v8::WeakCallbackInfo<Reference>& data) {
404-
data.GetParameter()->Finalize();
449+
// Second pass callbacks are scheduled with platform tasks. At env teardown,
450+
// the tasks may have already be scheduled and we are unable to cancel the
451+
// second pass callback task. We have to make sure that parameter is kept
452+
// alive until the second pass callback is been invoked. In order to do
453+
// this and still allow our code to Finalize/delete the Reference during
454+
// shutdown we have to use a seperately allocated parameter instead
455+
// of a parameter within the Reference object itself. This is what
456+
// is stored in _secondPassParameter and it is alocated in the
457+
// constructor for the Reference.
458+
static void SecondPassCallback(
459+
const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data) {
460+
SecondPassCallParameterRef* parameter = data.GetParameter();
461+
Reference* reference = *parameter;
462+
delete parameter;
463+
if (reference == nullptr) {
464+
// the reference itself has already been deleted so nothing to do
465+
return;
466+
}
467+
reference->Finalize();
405468
}
406469

407470
v8impl::Persistent<v8::Value> _persistent;
471+
SecondPassCallParameterRef* _secondPassParameter;
408472
};
409473

410474
class ArrayBufferReference final : public Reference {

0 commit comments

Comments
 (0)