Skip to content

Commit d8acec4

Browse files
legendecastargos
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 Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com>
1 parent bd0d964 commit d8acec4

File tree

1 file changed

+81
-17
lines changed

1 file changed

+81
-17
lines changed

src/js_native_api_v8.cc

+81-17
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
}
@@ -377,39 +385,95 @@ class Reference : public RefBase {
377385

378386
// During env teardown, `~napi_env()` alone is responsible for finalizing.
379387
// Thus, we don't want any stray gc passes to trigger a second call to
380-
// `Finalize()`, so let's reset the persistent here if nothing is
381-
// keeping it alive.
382-
if (is_env_teardown && _persistent.IsWeak()) {
383-
_persistent.ClearWeak();
388+
// `RefBase::Finalize()`. ClearWeak will ensure that even if the
389+
// gc is in progress no Finalization will be run for this Reference
390+
// by the gc.
391+
if (is_env_teardown) {
392+
ClearWeak();
384393
}
385394

386395
// Chain up to perform the rest of the finalization.
387396
RefBase::Finalize(is_env_teardown);
388397
}
389398

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

401444
// The reference must be reset during the first pass.
402445
reference->_persistent.Reset();
446+
// Mark the parameter not delete-able until the second pass callback is
447+
// invoked.
448+
reference->_secondPassParameter = nullptr;
403449

404450
data.SetSecondPassCallback(SecondPassCallback);
405451
}
406452

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

411474
bool env_teardown_finalize_started_ = false;
412475
v8impl::Persistent<v8::Value> _persistent;
476+
SecondPassCallParameterRef* _secondPassParameter;
413477
};
414478

415479
enum UnwrapAction {

0 commit comments

Comments
 (0)