Skip to content

Commit 7b992cc

Browse files
legendecasdanielleadams
authored andcommittedJan 3, 2023
src: create BaseObject with node::Realm
BaseObject is a wrapper around JS objects. These objects should be created in a node::Realm and destroyed when their associated realm is cleaning up. PR-URL: #44348 Refs: #42528 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent a7f3bc0 commit 7b992cc

15 files changed

+381
-288
lines changed
 

‎node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@
477477
'src/api/hooks.cc',
478478
'src/api/utils.cc',
479479
'src/async_wrap.cc',
480+
'src/base_object.cc',
480481
'src/cares_wrap.cc',
481482
'src/cleanup_queue.cc',
482483
'src/connect_wrap.cc',

‎src/api/embed_helpers.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Maybe<int> SpinEventLoop(Environment* env) {
6868
env->set_snapshot_serialize_callback(Local<Function>());
6969

7070
env->PrintInfoForSnapshotIfDebug();
71-
env->VerifyNoStrongBaseObjects();
71+
env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); });
7272
return EmitProcessExit(env);
7373
}
7474

‎src/base_object-inl.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232

3333
namespace node {
3434

35+
BaseObject::BaseObject(Environment* env, v8::Local<v8::Object> object)
36+
: BaseObject(env->principal_realm(), object) {}
37+
3538
// static
3639
v8::Local<v8::FunctionTemplate> BaseObject::GetConstructorTemplate(
3740
Environment* env) {
@@ -63,7 +66,11 @@ v8::Local<v8::Object> BaseObject::object(v8::Isolate* isolate) const {
6366
}
6467

6568
Environment* BaseObject::env() const {
66-
return env_;
69+
return realm_->env();
70+
}
71+
72+
Realm* BaseObject::realm() const {
73+
return realm_;
6774
}
6875

6976
BaseObject* BaseObject::FromJSObject(v8::Local<v8::Value> value) {

‎src/base_object.cc

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#include "base_object.h"
2+
#include "env-inl.h"
3+
#include "node_realm-inl.h"
4+
5+
namespace node {
6+
7+
using v8::FunctionCallbackInfo;
8+
using v8::FunctionTemplate;
9+
using v8::HandleScope;
10+
using v8::Local;
11+
using v8::Object;
12+
using v8::Value;
13+
using v8::WeakCallbackInfo;
14+
using v8::WeakCallbackType;
15+
16+
BaseObject::BaseObject(Realm* realm, Local<Object> object)
17+
: persistent_handle_(realm->isolate(), object), realm_(realm) {
18+
CHECK_EQ(false, object.IsEmpty());
19+
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
20+
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
21+
&kNodeEmbedderId);
22+
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
23+
static_cast<void*>(this));
24+
realm->AddCleanupHook(DeleteMe, static_cast<void*>(this));
25+
realm->modify_base_object_count(1);
26+
}
27+
28+
BaseObject::~BaseObject() {
29+
realm()->modify_base_object_count(-1);
30+
realm()->RemoveCleanupHook(DeleteMe, static_cast<void*>(this));
31+
32+
if (UNLIKELY(has_pointer_data())) {
33+
PointerData* metadata = pointer_data();
34+
CHECK_EQ(metadata->strong_ptr_count, 0);
35+
metadata->self = nullptr;
36+
if (metadata->weak_ptr_count == 0) delete metadata;
37+
}
38+
39+
if (persistent_handle_.IsEmpty()) {
40+
// This most likely happened because the weak callback below cleared it.
41+
return;
42+
}
43+
44+
{
45+
HandleScope handle_scope(realm()->isolate());
46+
object()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
47+
}
48+
}
49+
50+
void BaseObject::MakeWeak() {
51+
if (has_pointer_data()) {
52+
pointer_data()->wants_weak_jsobj = true;
53+
if (pointer_data()->strong_ptr_count > 0) return;
54+
}
55+
56+
persistent_handle_.SetWeak(
57+
this,
58+
[](const WeakCallbackInfo<BaseObject>& data) {
59+
BaseObject* obj = data.GetParameter();
60+
// Clear the persistent handle so that ~BaseObject() doesn't attempt
61+
// to mess with internal fields, since the JS object may have
62+
// transitioned into an invalid state.
63+
// Refs: https://github.com/nodejs/node/issues/18897
64+
obj->persistent_handle_.Reset();
65+
CHECK_IMPLIES(obj->has_pointer_data(),
66+
obj->pointer_data()->strong_ptr_count == 0);
67+
obj->OnGCCollect();
68+
},
69+
WeakCallbackType::kParameter);
70+
}
71+
72+
// This just has to be different from the Chromium ones:
73+
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
74+
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
75+
// misinterpret the data stored in the embedder fields and try to garbage
76+
// collect them.
77+
uint16_t kNodeEmbedderId = 0x90de;
78+
79+
void BaseObject::LazilyInitializedJSTemplateConstructor(
80+
const FunctionCallbackInfo<Value>& args) {
81+
DCHECK(args.IsConstructCall());
82+
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
83+
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
84+
&kNodeEmbedderId);
85+
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
86+
}
87+
88+
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
89+
Environment* env) {
90+
Local<FunctionTemplate> t = NewFunctionTemplate(
91+
env->isolate(), LazilyInitializedJSTemplateConstructor);
92+
t->Inherit(BaseObject::GetConstructorTemplate(env));
93+
t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
94+
return t;
95+
}
96+
97+
BaseObject::PointerData* BaseObject::pointer_data() {
98+
if (!has_pointer_data()) {
99+
PointerData* metadata = new PointerData();
100+
metadata->wants_weak_jsobj = persistent_handle_.IsWeak();
101+
metadata->self = this;
102+
pointer_data_ = metadata;
103+
}
104+
CHECK(has_pointer_data());
105+
return pointer_data_;
106+
}
107+
108+
void BaseObject::decrease_refcount() {
109+
CHECK(has_pointer_data());
110+
PointerData* metadata = pointer_data();
111+
CHECK_GT(metadata->strong_ptr_count, 0);
112+
unsigned int new_refcount = --metadata->strong_ptr_count;
113+
if (new_refcount == 0) {
114+
if (metadata->is_detached) {
115+
OnGCCollect();
116+
} else if (metadata->wants_weak_jsobj && !persistent_handle_.IsEmpty()) {
117+
MakeWeak();
118+
}
119+
}
120+
}
121+
122+
void BaseObject::increase_refcount() {
123+
unsigned int prev_refcount = pointer_data()->strong_ptr_count++;
124+
if (prev_refcount == 0 && !persistent_handle_.IsEmpty())
125+
persistent_handle_.ClearWeak();
126+
}
127+
128+
void BaseObject::DeleteMe(void* data) {
129+
BaseObject* self = static_cast<BaseObject*>(data);
130+
if (self->has_pointer_data() && self->pointer_data()->strong_ptr_count > 0) {
131+
return self->Detach();
132+
}
133+
delete self;
134+
}
135+
136+
bool BaseObject::IsDoneInitializing() const {
137+
return true;
138+
}
139+
140+
Local<Object> BaseObject::WrappedObject() const {
141+
return object();
142+
}
143+
144+
bool BaseObject::IsRootNode() const {
145+
return !persistent_handle_.IsWeak();
146+
}
147+
148+
Local<FunctionTemplate> BaseObject::GetConstructorTemplate(
149+
IsolateData* isolate_data) {
150+
Local<FunctionTemplate> tmpl = isolate_data->base_object_ctor_template();
151+
if (tmpl.IsEmpty()) {
152+
tmpl = NewFunctionTemplate(isolate_data->isolate(), nullptr);
153+
tmpl->SetClassName(
154+
FIXED_ONE_BYTE_STRING(isolate_data->isolate(), "BaseObject"));
155+
isolate_data->set_base_object_ctor_template(tmpl);
156+
}
157+
return tmpl;
158+
}
159+
160+
bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
161+
return IsWeakOrDetached();
162+
}
163+
164+
} // namespace node

‎src/base_object.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace node {
3232

3333
class Environment;
3434
class IsolateData;
35+
class Realm;
3536
template <typename T, bool kIsWeak>
3637
class BaseObjectPtrImpl;
3738

@@ -47,7 +48,10 @@ class BaseObject : public MemoryRetainer {
4748

4849
// Associates this object with `object`. It uses the 1st internal field for
4950
// that, and in particular aborts if there is no such field.
50-
BaseObject(Environment* env, v8::Local<v8::Object> object);
51+
// This is the designated constructor.
52+
BaseObject(Realm* realm, v8::Local<v8::Object> object);
53+
// Convenient constructor for constructing BaseObject in the principal realm.
54+
inline BaseObject(Environment* env, v8::Local<v8::Object> object);
5155
~BaseObject() override;
5256

5357
BaseObject() = delete;
@@ -63,6 +67,7 @@ class BaseObject : public MemoryRetainer {
6367
inline v8::Global<v8::Object>& persistent();
6468

6569
inline Environment* env() const;
70+
inline Realm* realm() const;
6671

6772
// Get a BaseObject* pointer, or subclass pointer, for the JS object that
6873
// was also passed to the `BaseObject()` constructor initially.
@@ -93,6 +98,7 @@ class BaseObject : public MemoryRetainer {
9398
// Utility to create a FunctionTemplate with one internal field (used for
9499
// the `BaseObject*` pointer) and a constructor that initializes that field
95100
// to `nullptr`.
101+
// TODO(legendecas): Disentangle template with env.
96102
static v8::Local<v8::FunctionTemplate> MakeLazilyInitializedJSTemplate(
97103
Environment* env);
98104

@@ -215,7 +221,7 @@ class BaseObject : public MemoryRetainer {
215221
void decrease_refcount();
216222
void increase_refcount();
217223

218-
Environment* env_;
224+
Realm* realm_;
219225
PointerData* pointer_data_ = nullptr;
220226
};
221227

‎src/env-inl.h

+6-21
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,12 @@ inline IsolateData* Environment::isolate_data() const {
745745
return isolate_data_;
746746
}
747747

748+
template <typename T>
749+
inline void Environment::ForEachRealm(T&& iterator) const {
750+
// TODO(legendecas): iterate over more realms bound to the environment.
751+
iterator(principal_realm());
752+
}
753+
748754
inline void Environment::ThrowError(const char* errmsg) {
749755
ThrowError(v8::Exception::Error, errmsg);
750756
}
@@ -789,27 +795,6 @@ void Environment::RemoveCleanupHook(CleanupQueue::Callback fn, void* arg) {
789795
cleanup_queue_.Remove(fn, arg);
790796
}
791797

792-
template <typename T>
793-
void Environment::ForEachBaseObject(T&& iterator) {
794-
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
795-
}
796-
797-
void Environment::modify_base_object_count(int64_t delta) {
798-
base_object_count_ += delta;
799-
}
800-
801-
int64_t Environment::base_object_count() const {
802-
return base_object_count_;
803-
}
804-
805-
inline void Environment::set_base_object_created_by_bootstrap(int64_t count) {
806-
base_object_created_by_bootstrap_ = base_object_count_;
807-
}
808-
809-
int64_t Environment::base_object_created_after_bootstrap() const {
810-
return base_object_count_ - base_object_created_by_bootstrap_;
811-
}
812-
813798
void Environment::set_main_utf16(std::unique_ptr<v8::String::Value> str) {
814799
CHECK(!main_utf16_);
815800
main_utf16_ = std::move(str);

‎src/env.cc

+9-200
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ using v8::Context;
3737
using v8::EmbedderGraph;
3838
using v8::EscapableHandleScope;
3939
using v8::Function;
40-
using v8::FunctionCallbackInfo;
4140
using v8::FunctionTemplate;
4241
using v8::HandleScope;
4342
using v8::HeapSpaceStatistics;
@@ -58,8 +57,6 @@ using v8::TracingController;
5857
using v8::TryCatch;
5958
using v8::Undefined;
6059
using v8::Value;
61-
using v8::WeakCallbackInfo;
62-
using v8::WeakCallbackType;
6360
using worker::Worker;
6461

6562
int const ContextEmbedderTag::kNodeContextTag = 0x6e6f64;
@@ -840,8 +837,6 @@ Environment::~Environment() {
840837
addon.Close();
841838
}
842839
}
843-
844-
CHECK_EQ(base_object_count_, 0);
845840
}
846841

847842
void Environment::InitializeLibuv() {
@@ -1002,11 +997,16 @@ void Environment::RunCleanup() {
1002997
started_cleanup_ = true;
1003998
TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "RunCleanup");
1004999
bindings_.clear();
1000+
// Only BaseObject's cleanups are registered as per-realm cleanup hooks now.
1001+
// Defer the BaseObject cleanup after handles are cleaned up.
10051002
CleanupHandles();
10061003

1007-
while (!cleanup_queue_.empty() || native_immediates_.size() > 0 ||
1004+
while (!cleanup_queue_.empty() || principal_realm_->HasCleanupHooks() ||
1005+
native_immediates_.size() > 0 ||
10081006
native_immediates_threadsafe_.size() > 0 ||
10091007
native_immediates_interrupts_.size() > 0) {
1008+
// TODO(legendecas): cleanup handles in per-realm cleanup hooks as well.
1009+
principal_realm_->RunCleanup();
10101010
cleanup_queue_.Drain();
10111011
CleanupHandles();
10121012
}
@@ -1565,8 +1565,8 @@ void Environment::RemoveUnmanagedFd(int fd) {
15651565

15661566
void Environment::PrintInfoForSnapshotIfDebug() {
15671567
if (enabled_debug_list()->enabled(DebugCategory::MKSNAPSHOT)) {
1568-
fprintf(stderr, "BaseObjects at the exit of the Environment:\n");
1569-
PrintAllBaseObjects();
1568+
fprintf(stderr, "At the exit of the Environment:\n");
1569+
principal_realm()->PrintInfoForSnapshot();
15701570
fprintf(stderr, "\nNative modules without cache:\n");
15711571
for (const auto& s : builtins_without_cache) {
15721572
fprintf(stderr, "%s\n", s.c_str());
@@ -1582,45 +1582,6 @@ void Environment::PrintInfoForSnapshotIfDebug() {
15821582
}
15831583
}
15841584

1585-
void Environment::PrintAllBaseObjects() {
1586-
size_t i = 0;
1587-
std::cout << "BaseObjects\n";
1588-
ForEachBaseObject([&](BaseObject* obj) {
1589-
std::cout << "#" << i++ << " " << obj << ": " <<
1590-
obj->MemoryInfoName() << "\n";
1591-
});
1592-
}
1593-
1594-
void Environment::VerifyNoStrongBaseObjects() {
1595-
// When a process exits cleanly, i.e. because the event loop ends up without
1596-
// things to wait for, the Node.js objects that are left on the heap should
1597-
// be:
1598-
//
1599-
// 1. weak, i.e. ready for garbage collection once no longer referenced, or
1600-
// 2. detached, i.e. scheduled for destruction once no longer referenced, or
1601-
// 3. an unrefed libuv handle, i.e. does not keep the event loop alive, or
1602-
// 4. an inactive libuv handle (essentially the same here)
1603-
//
1604-
// There are a few exceptions to this rule, but generally, if there are
1605-
// C++-backed Node.js objects on the heap that do not fall into the above
1606-
// categories, we may be looking at a potential memory leak. Most likely,
1607-
// the cause is a missing MakeWeak() call on the corresponding object.
1608-
//
1609-
// In order to avoid this kind of problem, we check the list of BaseObjects
1610-
// for these criteria. Currently, we only do so when explicitly instructed to
1611-
// or when in debug mode (where --verify-base-objects is always-on).
1612-
1613-
if (!options()->verify_base_objects) return;
1614-
1615-
ForEachBaseObject([](BaseObject* obj) {
1616-
if (obj->IsNotIndicativeOfMemoryLeakAtExit()) return;
1617-
fprintf(stderr, "Found bad BaseObject during clean exit: %s\n",
1618-
obj->MemoryInfoName().c_str());
1619-
fflush(stderr);
1620-
ABORT();
1621-
});
1622-
}
1623-
16241585
EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) {
16251586
EnvSerializeInfo info;
16261587
Local<Context> ctx = context();
@@ -1639,10 +1600,6 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) {
16391600
info.should_abort_on_uncaught_toggle =
16401601
should_abort_on_uncaught_toggle_.Serialize(ctx, creator);
16411602

1642-
// Do this after other creator->AddData() calls so that Snapshotable objects
1643-
// can use 0 to indicate that a SnapshotIndex is invalid.
1644-
SerializeSnapshotableObjects(this, creator, &info);
1645-
16461603
info.principal_realm = principal_realm_->Serialize(creator);
16471604
return info;
16481605
}
@@ -1716,6 +1673,7 @@ void Environment::BuildEmbedderGraph(Isolate* isolate,
17161673
void* data) {
17171674
MemoryTracker tracker(isolate, graph);
17181675
Environment* env = static_cast<Environment*>(data);
1676+
// Start traversing embedder objects from the root Environment object.
17191677
tracker.Track(env);
17201678
}
17211679

@@ -1890,153 +1848,4 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
18901848
void Environment::RunWeakRefCleanup() {
18911849
isolate()->ClearKeptObjects();
18921850
}
1893-
1894-
// Not really any better place than env.cc at this moment.
1895-
BaseObject::BaseObject(Environment* env, Local<Object> object)
1896-
: persistent_handle_(env->isolate(), object), env_(env) {
1897-
CHECK_EQ(false, object.IsEmpty());
1898-
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
1899-
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
1900-
&kNodeEmbedderId);
1901-
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
1902-
static_cast<void*>(this));
1903-
env->AddCleanupHook(DeleteMe, static_cast<void*>(this));
1904-
env->modify_base_object_count(1);
1905-
}
1906-
1907-
BaseObject::~BaseObject() {
1908-
env()->modify_base_object_count(-1);
1909-
env()->RemoveCleanupHook(DeleteMe, static_cast<void*>(this));
1910-
1911-
if (UNLIKELY(has_pointer_data())) {
1912-
PointerData* metadata = pointer_data();
1913-
CHECK_EQ(metadata->strong_ptr_count, 0);
1914-
metadata->self = nullptr;
1915-
if (metadata->weak_ptr_count == 0) delete metadata;
1916-
}
1917-
1918-
if (persistent_handle_.IsEmpty()) {
1919-
// This most likely happened because the weak callback below cleared it.
1920-
return;
1921-
}
1922-
1923-
{
1924-
HandleScope handle_scope(env()->isolate());
1925-
object()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
1926-
}
1927-
}
1928-
1929-
void BaseObject::MakeWeak() {
1930-
if (has_pointer_data()) {
1931-
pointer_data()->wants_weak_jsobj = true;
1932-
if (pointer_data()->strong_ptr_count > 0) return;
1933-
}
1934-
1935-
persistent_handle_.SetWeak(
1936-
this,
1937-
[](const WeakCallbackInfo<BaseObject>& data) {
1938-
BaseObject* obj = data.GetParameter();
1939-
// Clear the persistent handle so that ~BaseObject() doesn't attempt
1940-
// to mess with internal fields, since the JS object may have
1941-
// transitioned into an invalid state.
1942-
// Refs: https://github.com/nodejs/node/issues/18897
1943-
obj->persistent_handle_.Reset();
1944-
CHECK_IMPLIES(obj->has_pointer_data(),
1945-
obj->pointer_data()->strong_ptr_count == 0);
1946-
obj->OnGCCollect();
1947-
},
1948-
WeakCallbackType::kParameter);
1949-
}
1950-
1951-
// This just has to be different from the Chromium ones:
1952-
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
1953-
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
1954-
// misinterpret the data stored in the embedder fields and try to garbage
1955-
// collect them.
1956-
uint16_t kNodeEmbedderId = 0x90de;
1957-
1958-
void BaseObject::LazilyInitializedJSTemplateConstructor(
1959-
const FunctionCallbackInfo<Value>& args) {
1960-
DCHECK(args.IsConstructCall());
1961-
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
1962-
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
1963-
&kNodeEmbedderId);
1964-
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
1965-
}
1966-
1967-
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
1968-
Environment* env) {
1969-
Local<FunctionTemplate> t = NewFunctionTemplate(
1970-
env->isolate(), LazilyInitializedJSTemplateConstructor);
1971-
t->Inherit(BaseObject::GetConstructorTemplate(env));
1972-
t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
1973-
return t;
1974-
}
1975-
1976-
BaseObject::PointerData* BaseObject::pointer_data() {
1977-
if (!has_pointer_data()) {
1978-
PointerData* metadata = new PointerData();
1979-
metadata->wants_weak_jsobj = persistent_handle_.IsWeak();
1980-
metadata->self = this;
1981-
pointer_data_ = metadata;
1982-
}
1983-
CHECK(has_pointer_data());
1984-
return pointer_data_;
1985-
}
1986-
1987-
void BaseObject::decrease_refcount() {
1988-
CHECK(has_pointer_data());
1989-
PointerData* metadata = pointer_data();
1990-
CHECK_GT(metadata->strong_ptr_count, 0);
1991-
unsigned int new_refcount = --metadata->strong_ptr_count;
1992-
if (new_refcount == 0) {
1993-
if (metadata->is_detached) {
1994-
OnGCCollect();
1995-
} else if (metadata->wants_weak_jsobj && !persistent_handle_.IsEmpty()) {
1996-
MakeWeak();
1997-
}
1998-
}
1999-
}
2000-
2001-
void BaseObject::increase_refcount() {
2002-
unsigned int prev_refcount = pointer_data()->strong_ptr_count++;
2003-
if (prev_refcount == 0 && !persistent_handle_.IsEmpty())
2004-
persistent_handle_.ClearWeak();
2005-
}
2006-
2007-
void BaseObject::DeleteMe(void* data) {
2008-
BaseObject* self = static_cast<BaseObject*>(data);
2009-
if (self->has_pointer_data() &&
2010-
self->pointer_data()->strong_ptr_count > 0) {
2011-
return self->Detach();
2012-
}
2013-
delete self;
2014-
}
2015-
2016-
bool BaseObject::IsDoneInitializing() const { return true; }
2017-
2018-
Local<Object> BaseObject::WrappedObject() const {
2019-
return object();
2020-
}
2021-
2022-
bool BaseObject::IsRootNode() const {
2023-
return !persistent_handle_.IsWeak();
2024-
}
2025-
2026-
Local<FunctionTemplate> BaseObject::GetConstructorTemplate(
2027-
IsolateData* isolate_data) {
2028-
Local<FunctionTemplate> tmpl = isolate_data->base_object_ctor_template();
2029-
if (tmpl.IsEmpty()) {
2030-
tmpl = NewFunctionTemplate(isolate_data->isolate(), nullptr);
2031-
tmpl->SetClassName(
2032-
FIXED_ONE_BYTE_STRING(isolate_data->isolate(), "BaseObject"));
2033-
isolate_data->set_base_object_ctor_template(tmpl);
2034-
}
2035-
return tmpl;
2036-
}
2037-
2038-
bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
2039-
return IsWeakOrDetached();
2040-
}
2041-
20421851
} // namespace node

‎src/env.h

+1-19
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,6 @@ struct DeserializeRequest {
508508
};
509509

510510
struct EnvSerializeInfo {
511-
std::vector<PropInfo> native_objects;
512511
std::vector<std::string> builtins;
513512
AsyncHooks::SerializeInfo async_hooks;
514513
TickInfo::SerializeInfo tick_info;
@@ -600,8 +599,6 @@ class Environment : public MemoryRetainer {
600599
void DeserializeProperties(const EnvSerializeInfo* info);
601600

602601
void PrintInfoForSnapshotIfDebug();
603-
void PrintAllBaseObjects();
604-
void VerifyNoStrongBaseObjects();
605602
void EnqueueDeserializeRequest(DeserializeRequestCallback cb,
606603
v8::Local<v8::Object> holder,
607604
int index,
@@ -966,19 +963,6 @@ class Environment : public MemoryRetainer {
966963
inline std::shared_ptr<EnvironmentOptions> options();
967964
inline std::shared_ptr<ExclusiveAccess<HostPort>> inspector_host_port();
968965

969-
// The BaseObject count is a debugging helper that makes sure that there are
970-
// no memory leaks caused by BaseObjects staying alive longer than expected
971-
// (in particular, no circular BaseObjectPtr references).
972-
inline void modify_base_object_count(int64_t delta);
973-
inline int64_t base_object_count() const;
974-
975-
// Base object count created in bootstrap of the principal realm.
976-
// This adjusts the return value of base_object_created_after_bootstrap() so
977-
// that tests that check the count do not have to account for internally
978-
// created BaseObjects.
979-
inline void set_base_object_created_by_bootstrap(int64_t count);
980-
inline int64_t base_object_created_after_bootstrap() const;
981-
982966
inline int32_t stack_trace_limit() const { return 10; }
983967

984968
#if HAVE_INSPECTOR
@@ -1031,7 +1015,7 @@ class Environment : public MemoryRetainer {
10311015
void RemoveUnmanagedFd(int fd);
10321016

10331017
template <typename T>
1034-
void ForEachBaseObject(T&& iterator);
1018+
void ForEachRealm(T&& iterator) const;
10351019

10361020
inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
10371021
inline bool is_in_heapsnapshot_heap_limit_callback() const;
@@ -1178,8 +1162,6 @@ class Environment : public MemoryRetainer {
11781162
CleanupQueue cleanup_queue_;
11791163
bool started_cleanup_ = false;
11801164

1181-
int64_t base_object_count_ = 0;
1182-
int64_t base_object_created_by_bootstrap_ = 0;
11831165
std::atomic_bool is_stopping_ { false };
11841166

11851167
std::unordered_set<int> unmanaged_fds_;

‎src/node_realm-inl.h

+30
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

6+
#include "cleanup_queue-inl.h"
67
#include "node_realm.h"
78

89
namespace node {
@@ -41,6 +42,23 @@ inline bool Realm::has_run_bootstrapping_code() const {
4142
return has_run_bootstrapping_code_;
4243
}
4344

45+
template <typename T>
46+
void Realm::ForEachBaseObject(T&& iterator) const {
47+
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
48+
}
49+
50+
void Realm::modify_base_object_count(int64_t delta) {
51+
base_object_count_ += delta;
52+
}
53+
54+
int64_t Realm::base_object_created_after_bootstrap() const {
55+
return base_object_count_ - base_object_created_by_bootstrap_;
56+
}
57+
58+
int64_t Realm::base_object_count() const {
59+
return base_object_count_;
60+
}
61+
4462
#define V(PropertyName, TypeName) \
4563
inline v8::Local<TypeName> Realm::PropertyName() const { \
4664
return PersistentToLocal::Strong(PropertyName##_); \
@@ -55,6 +73,18 @@ v8::Local<v8::Context> Realm::context() const {
5573
return PersistentToLocal::Strong(context_);
5674
}
5775

76+
void Realm::AddCleanupHook(CleanupQueue::Callback fn, void* arg) {
77+
cleanup_queue_.Add(fn, arg);
78+
}
79+
80+
void Realm::RemoveCleanupHook(CleanupQueue::Callback fn, void* arg) {
81+
cleanup_queue_.Remove(fn, arg);
82+
}
83+
84+
bool Realm::HasCleanupHooks() const {
85+
return !cleanup_queue_.empty();
86+
}
87+
5888
} // namespace node
5989

6090
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎src/node_realm.cc

+70-4
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,24 @@ Realm::Realm(Environment* env,
3535
}
3636
}
3737

38+
Realm::~Realm() {
39+
CHECK_EQ(base_object_count_, 0);
40+
}
41+
3842
void Realm::MemoryInfo(MemoryTracker* tracker) const {
3943
#define V(PropertyName, TypeName) \
4044
tracker->TrackField(#PropertyName, PropertyName());
4145
PER_REALM_STRONG_PERSISTENT_VALUES(V)
4246
#undef V
4347

4448
tracker->TrackField("env", env_);
49+
tracker->TrackField("cleanup_queue", cleanup_queue_);
50+
51+
ForEachBaseObject([&](BaseObject* obj) {
52+
if (obj->IsDoneInitializing()) {
53+
tracker->Track(obj);
54+
}
55+
});
4556
}
4657

4758
void Realm::CreateProperties() {
@@ -105,6 +116,10 @@ RealmSerializeInfo Realm::Serialize(SnapshotCreator* creator) {
105116
PER_REALM_STRONG_PERSISTENT_VALUES(V)
106117
#undef V
107118

119+
// Do this after other creator->AddData() calls so that Snapshotable objects
120+
// can use 0 to indicate that a SnapshotIndex is invalid.
121+
SerializeSnapshotableObjects(this, creator, &info);
122+
108123
info.context = creator->AddData(ctx, ctx);
109124
return info;
110125
}
@@ -289,8 +304,6 @@ MaybeLocal<Value> Realm::RunBootstrapping() {
289304
}
290305

291306
void Realm::DoneBootstrapping() {
292-
has_run_bootstrapping_code_ = true;
293-
294307
// Make sure that no request or handle is created during bootstrap -
295308
// if necessary those should be done in pre-execution.
296309
// Usually, doing so would trigger the checks present in the ReqWrap and
@@ -301,8 +314,61 @@ void Realm::DoneBootstrapping() {
301314
CHECK(env_->req_wrap_queue()->IsEmpty());
302315
CHECK(env_->handle_wrap_queue()->IsEmpty());
303316

304-
// TODO(legendecas): track base object count by realms.
305-
env_->set_base_object_created_by_bootstrap(env_->base_object_count());
317+
has_run_bootstrapping_code_ = true;
318+
319+
// This adjusts the return value of base_object_created_after_bootstrap() so
320+
// that tests that check the count do not have to account for internally
321+
// created BaseObjects.
322+
base_object_created_by_bootstrap_ = base_object_count_;
323+
}
324+
325+
void Realm::RunCleanup() {
326+
TRACE_EVENT0(TRACING_CATEGORY_NODE1(realm), "RunCleanup");
327+
328+
cleanup_queue_.Drain();
329+
}
330+
331+
void Realm::PrintInfoForSnapshot() {
332+
fprintf(stderr, "Realm = %p\n", this);
333+
fprintf(stderr, "BaseObjects of the Realm:\n");
334+
size_t i = 0;
335+
ForEachBaseObject([&](BaseObject* obj) {
336+
std::cout << "#" << i++ << " " << obj << ": " << obj->MemoryInfoName()
337+
<< "\n";
338+
});
339+
fprintf(stderr, "End of the Realm.\n");
340+
}
341+
342+
void Realm::VerifyNoStrongBaseObjects() {
343+
// When a process exits cleanly, i.e. because the event loop ends up without
344+
// things to wait for, the Node.js objects that are left on the heap should
345+
// be:
346+
//
347+
// 1. weak, i.e. ready for garbage collection once no longer referenced, or
348+
// 2. detached, i.e. scheduled for destruction once no longer referenced, or
349+
// 3. an unrefed libuv handle, i.e. does not keep the event loop alive, or
350+
// 4. an inactive libuv handle (essentially the same here)
351+
//
352+
// There are a few exceptions to this rule, but generally, if there are
353+
// C++-backed Node.js objects on the heap that do not fall into the above
354+
// categories, we may be looking at a potential memory leak. Most likely,
355+
// the cause is a missing MakeWeak() call on the corresponding object.
356+
//
357+
// In order to avoid this kind of problem, we check the list of BaseObjects
358+
// for these criteria. Currently, we only do so when explicitly instructed to
359+
// or when in debug mode (where --verify-base-objects is always-on).
360+
361+
// TODO(legendecas): introduce per-realm options.
362+
if (!env()->options()->verify_base_objects) return;
363+
364+
ForEachBaseObject([](BaseObject* obj) {
365+
if (obj->IsNotIndicativeOfMemoryLeakAtExit()) return;
366+
fprintf(stderr,
367+
"Found bad BaseObject during clean exit: %s\n",
368+
obj->MemoryInfoName().c_str());
369+
fflush(stderr);
370+
ABORT();
371+
});
306372
}
307373

308374
} // namespace node

‎src/node_realm.h

+29-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include <v8.h>
7+
#include "cleanup_queue.h"
78
#include "env_properties.h"
89
#include "memory_tracker.h"
910
#include "node_snapshotable.h"
@@ -12,6 +13,7 @@ namespace node {
1213

1314
struct RealmSerializeInfo {
1415
std::vector<PropInfo> persistent_values;
16+
std::vector<PropInfo> native_objects;
1517

1618
SnapshotIndex context;
1719
friend std::ostream& operator<<(std::ostream& o, const RealmSerializeInfo& i);
@@ -45,7 +47,7 @@ class Realm : public MemoryRetainer {
4547
Realm(Environment* env,
4648
v8::Local<v8::Context> context,
4749
const RealmSerializeInfo* realm_info);
48-
~Realm() = default;
50+
~Realm();
4951

5052
Realm(const Realm&) = delete;
5153
Realm& operator=(const Realm&) = delete;
@@ -66,11 +68,32 @@ class Realm : public MemoryRetainer {
6668
v8::MaybeLocal<v8::Value> BootstrapNode();
6769
v8::MaybeLocal<v8::Value> RunBootstrapping();
6870

71+
inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg);
72+
inline void RemoveCleanupHook(CleanupQueue::Callback cb, void* arg);
73+
inline bool HasCleanupHooks() const;
74+
void RunCleanup();
75+
76+
template <typename T>
77+
void ForEachBaseObject(T&& iterator) const;
78+
79+
void PrintInfoForSnapshot();
80+
void VerifyNoStrongBaseObjects();
81+
82+
inline IsolateData* isolate_data() const;
6983
inline Environment* env() const;
7084
inline v8::Isolate* isolate() const;
7185
inline v8::Local<v8::Context> context() const;
7286
inline bool has_run_bootstrapping_code() const;
7387

88+
// The BaseObject count is a debugging helper that makes sure that there are
89+
// no memory leaks caused by BaseObjects staying alive longer than expected
90+
// (in particular, no circular BaseObjectPtr references).
91+
inline void modify_base_object_count(int64_t delta);
92+
inline int64_t base_object_count() const;
93+
94+
// Base object count created after the bootstrap of the realm.
95+
inline int64_t base_object_created_after_bootstrap() const;
96+
7497
#define V(PropertyName, TypeName) \
7598
inline v8::Local<TypeName> PropertyName() const; \
7699
inline void set_##PropertyName(v8::Local<TypeName> value);
@@ -88,6 +111,11 @@ class Realm : public MemoryRetainer {
88111
v8::Global<v8::Context> context_;
89112
bool has_run_bootstrapping_code_ = false;
90113

114+
int64_t base_object_count_ = 0;
115+
int64_t base_object_created_by_bootstrap_ = 0;
116+
117+
CleanupQueue cleanup_queue_;
118+
91119
#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName##_;
92120
PER_REALM_STRONG_PERSISTENT_VALUES(V)
93121
#undef V

‎src/node_snapshotable.cc

+14-12
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,16 @@ std::ostream& operator<<(std::ostream& output, const RealmSerializeInfo& i) {
102102
<< "// -- persistent_values begins --\n"
103103
<< i.persistent_values << ",\n"
104104
<< "// -- persistent_values ends --\n"
105+
<< "// -- native_objects begins --\n"
106+
<< i.native_objects << ",\n"
107+
<< "// -- native_objects ends --\n"
105108
<< i.context << ", // context\n"
106109
<< "}";
107110
return output;
108111
}
109112

110113
std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) {
111114
output << "{\n"
112-
<< "// -- native_objects begins --\n"
113-
<< i.native_objects << ",\n"
114-
<< "// -- native_objects ends --\n"
115115
<< "// -- builtins begins --\n"
116116
<< i.builtins << ",\n"
117117
<< "// -- builtins ends --\n"
@@ -705,6 +705,7 @@ RealmSerializeInfo FileReader::Read() {
705705
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<RealmSerializeInfo>()\n");
706706
RealmSerializeInfo result;
707707
result.persistent_values = ReadVector<PropInfo>();
708+
result.native_objects = ReadVector<PropInfo>();
708709
result.context = Read<SnapshotIndex>();
709710
return result;
710711
}
@@ -718,6 +719,7 @@ size_t FileWriter::Write(const RealmSerializeInfo& data) {
718719

719720
// Use += here to ensure order of evaluation.
720721
size_t written_total = WriteVector<PropInfo>(data.persistent_values);
722+
written_total += WriteVector<PropInfo>(data.native_objects);
721723
written_total += Write<SnapshotIndex>(data.context);
722724

723725
Debug("Write<RealmSerializeInfo>() wrote %d bytes\n", written_total);
@@ -728,7 +730,6 @@ template <>
728730
EnvSerializeInfo FileReader::Read() {
729731
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<EnvSerializeInfo>()\n");
730732
EnvSerializeInfo result;
731-
result.native_objects = ReadVector<PropInfo>();
732733
result.builtins = ReadVector<std::string>();
733734
result.async_hooks = Read<AsyncHooks::SerializeInfo>();
734735
result.tick_info = Read<TickInfo::SerializeInfo>();
@@ -750,8 +751,7 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) {
750751
}
751752

752753
// Use += here to ensure order of evaluation.
753-
size_t written_total = WriteVector<PropInfo>(data.native_objects);
754-
written_total += WriteVector<std::string>(data.builtins);
754+
size_t written_total = WriteVector<std::string>(data.builtins);
755755
written_total += Write<AsyncHooks::SerializeInfo>(data.async_hooks);
756756
written_total += Write<TickInfo::SerializeInfo>(data.tick_info);
757757
written_total += Write<ImmediateInfo::SerializeInfo>(data.immediate_info);
@@ -1194,7 +1194,7 @@ int SnapshotBuilder::Generate(SnapshotData* out,
11941194
}
11951195

11961196
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
1197-
env->PrintAllBaseObjects();
1197+
env->ForEachRealm([](Realm* realm) { realm->PrintInfoForSnapshot(); });
11981198
printf("Environment = %p\n", env);
11991199
}
12001200

@@ -1400,11 +1400,13 @@ StartupData SerializeNodeContextInternalFields(Local<Object> holder,
14001400
static_cast<int>(info->length)};
14011401
}
14021402

1403-
void SerializeSnapshotableObjects(Environment* env,
1403+
void SerializeSnapshotableObjects(Realm* realm,
14041404
SnapshotCreator* creator,
1405-
EnvSerializeInfo* info) {
1405+
RealmSerializeInfo* info) {
1406+
HandleScope scope(realm->isolate());
1407+
Local<Context> context = realm->context();
14061408
uint32_t i = 0;
1407-
env->ForEachBaseObject([&](BaseObject* obj) {
1409+
realm->ForEachBaseObject([&](BaseObject* obj) {
14081410
// If there are any BaseObjects that are not snapshotable left
14091411
// during context serialization, V8 would crash due to unregistered
14101412
// global handles and print detailed information about them.
@@ -1422,8 +1424,8 @@ void SerializeSnapshotableObjects(Environment* env,
14221424
*(ptr->object()),
14231425
type_name);
14241426

1425-
if (ptr->PrepareForSerialization(env->context(), creator)) {
1426-
SnapshotIndex index = creator->AddData(env->context(), obj->object());
1427+
if (ptr->PrepareForSerialization(context, creator)) {
1428+
SnapshotIndex index = creator->AddData(context, obj->object());
14271429
per_process::Debug(DebugCategory::MKSNAPSHOT,
14281430
"Serialized with index=%d\n",
14291431
static_cast<int>(index));

‎src/node_snapshotable.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace node {
1111

1212
class Environment;
13-
struct EnvSerializeInfo;
13+
struct RealmSerializeInfo;
1414
struct SnapshotData;
1515
class ExternalReferenceRegistry;
1616

@@ -131,9 +131,9 @@ void DeserializeNodeInternalFields(v8::Local<v8::Object> holder,
131131
int index,
132132
v8::StartupData payload,
133133
void* env);
134-
void SerializeSnapshotableObjects(Environment* env,
134+
void SerializeSnapshotableObjects(Realm* realm,
135135
v8::SnapshotCreator* creator,
136-
EnvSerializeInfo* info);
136+
RealmSerializeInfo* info);
137137
} // namespace node
138138

139139
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎test/cctest/test_base_object_ptr.cc

+27-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
#include "base_object-inl.h"
12
#include "gtest/gtest.h"
23
#include "node.h"
3-
#include "base_object-inl.h"
4+
#include "node_realm-inl.h"
45
#include "node_test_fixture.h"
56

67
using node::BaseObject;
@@ -9,6 +10,7 @@ using node::BaseObjectWeakPtr;
910
using node::Environment;
1011
using node::MakeBaseObject;
1112
using node::MakeDetachedBaseObject;
13+
using node::Realm;
1214
using v8::HandleScope;
1315
using v8::Isolate;
1416
using v8::Local;
@@ -46,55 +48,59 @@ TEST_F(BaseObjectPtrTest, ScopedDetached) {
4648
const Argv argv;
4749
Env env_{handle_scope, argv};
4850
Environment* env = *env_;
51+
Realm* realm = env->principal_realm();
4952

50-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
53+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
5154
{
5255
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
53-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
56+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
5457
}
55-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
58+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
5659
}
5760

5861
TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) {
5962
const HandleScope handle_scope(isolate_);
6063
const Argv argv;
6164
Env env_{handle_scope, argv};
6265
Environment* env = *env_;
66+
Realm* realm = env->principal_realm();
6367

6468
BaseObjectWeakPtr<DummyBaseObject> weak_ptr;
6569

66-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
70+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
6771
{
6872
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
6973
weak_ptr = ptr;
70-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
74+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
7175
}
7276
EXPECT_EQ(weak_ptr.get(), nullptr);
73-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
77+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
7478
}
7579

7680
TEST_F(BaseObjectPtrTest, Undetached) {
7781
const HandleScope handle_scope(isolate_);
7882
const Argv argv;
7983
Env env_{handle_scope, argv};
8084
Environment* env = *env_;
85+
Realm* realm = env->principal_realm();
8186

8287
node::AddEnvironmentCleanupHook(
8388
isolate_,
8489
[](void* arg) {
85-
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(), 0);
90+
EXPECT_EQ(static_cast<Realm*>(arg)->base_object_count(), 0);
8691
},
87-
env);
92+
realm);
8893

8994
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::New(env);
90-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
95+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
9196
}
9297

9398
TEST_F(BaseObjectPtrTest, GCWeak) {
9499
const HandleScope handle_scope(isolate_);
95100
const Argv argv;
96101
Env env_{handle_scope, argv};
97102
Environment* env = *env_;
103+
Realm* realm = env->principal_realm();
98104

99105
BaseObjectWeakPtr<DummyBaseObject> weak_ptr;
100106

@@ -104,21 +110,21 @@ TEST_F(BaseObjectPtrTest, GCWeak) {
104110
weak_ptr = ptr;
105111
ptr->MakeWeak();
106112

107-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
113+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
108114
EXPECT_EQ(weak_ptr.get(), ptr.get());
109115
EXPECT_EQ(weak_ptr->persistent().IsWeak(), false);
110116

111117
ptr.reset();
112118
}
113119

114-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
120+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
115121
EXPECT_NE(weak_ptr.get(), nullptr);
116122
EXPECT_EQ(weak_ptr->persistent().IsWeak(), true);
117123

118124
v8::V8::SetFlagsFromString("--expose-gc");
119125
isolate_->RequestGarbageCollectionForTesting(Isolate::kFullGarbageCollection);
120126

121-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
127+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
122128
EXPECT_EQ(weak_ptr.get(), nullptr);
123129
}
124130

@@ -127,9 +133,10 @@ TEST_F(BaseObjectPtrTest, Moveable) {
127133
const Argv argv;
128134
Env env_{handle_scope, argv};
129135
Environment* env = *env_;
136+
Realm* realm = env->principal_realm();
130137

131138
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
132-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
139+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
133140
BaseObjectWeakPtr<DummyBaseObject> weak_ptr { ptr };
134141
EXPECT_EQ(weak_ptr.get(), ptr.get());
135142

@@ -140,12 +147,12 @@ TEST_F(BaseObjectPtrTest, Moveable) {
140147
BaseObjectWeakPtr<DummyBaseObject> weak_ptr2 = std::move(weak_ptr);
141148
EXPECT_EQ(weak_ptr2.get(), ptr2.get());
142149
EXPECT_EQ(weak_ptr.get(), nullptr);
143-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
150+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
144151

145152
ptr2.reset();
146153

147154
EXPECT_EQ(weak_ptr2.get(), nullptr);
148-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
155+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
149156
}
150157

151158
TEST_F(BaseObjectPtrTest, NestedClasses) {
@@ -165,18 +172,19 @@ TEST_F(BaseObjectPtrTest, NestedClasses) {
165172
const Argv argv;
166173
Env env_{handle_scope, argv};
167174
Environment* env = *env_;
175+
Realm* realm = env->principal_realm();
168176

169177
node::AddEnvironmentCleanupHook(
170178
isolate_,
171179
[](void* arg) {
172-
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(), 0);
180+
EXPECT_EQ(static_cast<Realm*>(arg)->base_object_count(), 0);
173181
},
174-
env);
182+
realm);
175183

176184
ObjectWithPtr* obj =
177185
new ObjectWithPtr(env, DummyBaseObject::MakeJSObject(env));
178186
obj->ptr1 = DummyBaseObject::NewDetached(env);
179187
obj->ptr2 = DummyBaseObject::New(env);
180188

181-
EXPECT_EQ(env->base_object_created_after_bootstrap(), 3);
189+
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 3);
182190
}

‎test/pummel/test-heapdump-env.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ validateSnapshotNodes('Node / Environment', [{
2121
]
2222
}]);
2323

24-
validateSnapshotNodes('Node / CleanupQueue', [{
25-
children: [
26-
{ node_name: 'Node / ContextifyScript' },
27-
]
28-
}]);
24+
validateSnapshotNodes('Node / CleanupQueue', [
25+
// The first one is the cleanup_queue of the Environment.
26+
{},
27+
// The second one is the cleanup_queue of the principal realm.
28+
{
29+
children: [
30+
{ node_name: 'Node / ContextifyScript' },
31+
]
32+
},
33+
]);
2934

3035
validateSnapshotNodes('Node / Realm', [{
3136
children: [

0 commit comments

Comments
 (0)
Please sign in to comment.