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: https://github.com/nodejs/node/pull/44348
Refs: https://github.com/nodejs/node/issues/42528
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Chengzhong Wu 2022-09-27 16:39:53 +08:00 committed by legendecas
parent 97959769fd
commit 717465433c
15 changed files with 381 additions and 288 deletions

View File

@ -465,6 +465,7 @@
'src/api/hooks.cc',
'src/api/utils.cc',
'src/async_wrap.cc',
'src/base_object.cc',
'src/cares_wrap.cc',
'src/cleanup_queue.cc',
'src/connect_wrap.cc',

View File

@ -68,7 +68,7 @@ Maybe<int> SpinEventLoop(Environment* env) {
env->set_snapshot_serialize_callback(Local<Function>());
env->PrintInfoForSnapshotIfDebug();
env->VerifyNoStrongBaseObjects();
env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); });
return EmitProcessExit(env);
}

View File

@ -32,6 +32,9 @@
namespace node {
BaseObject::BaseObject(Environment* env, v8::Local<v8::Object> object)
: BaseObject(env->principal_realm(), object) {}
// static
v8::Local<v8::FunctionTemplate> BaseObject::GetConstructorTemplate(
Environment* env) {
@ -63,7 +66,11 @@ v8::Local<v8::Object> BaseObject::object(v8::Isolate* isolate) const {
}
Environment* BaseObject::env() const {
return env_;
return realm_->env();
}
Realm* BaseObject::realm() const {
return realm_;
}
BaseObject* BaseObject::FromJSObject(v8::Local<v8::Value> value) {

164
src/base_object.cc Normal file
View File

@ -0,0 +1,164 @@
#include "base_object.h"
#include "env-inl.h"
#include "node_realm-inl.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Local;
using v8::Object;
using v8::Value;
using v8::WeakCallbackInfo;
using v8::WeakCallbackType;
BaseObject::BaseObject(Realm* realm, Local<Object> object)
: persistent_handle_(realm->isolate(), object), realm_(realm) {
CHECK_EQ(false, object.IsEmpty());
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
&kNodeEmbedderId);
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
static_cast<void*>(this));
realm->AddCleanupHook(DeleteMe, static_cast<void*>(this));
realm->modify_base_object_count(1);
}
BaseObject::~BaseObject() {
realm()->modify_base_object_count(-1);
realm()->RemoveCleanupHook(DeleteMe, static_cast<void*>(this));
if (UNLIKELY(has_pointer_data())) {
PointerData* metadata = pointer_data();
CHECK_EQ(metadata->strong_ptr_count, 0);
metadata->self = nullptr;
if (metadata->weak_ptr_count == 0) delete metadata;
}
if (persistent_handle_.IsEmpty()) {
// This most likely happened because the weak callback below cleared it.
return;
}
{
HandleScope handle_scope(realm()->isolate());
object()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
}
}
void BaseObject::MakeWeak() {
if (has_pointer_data()) {
pointer_data()->wants_weak_jsobj = true;
if (pointer_data()->strong_ptr_count > 0) return;
}
persistent_handle_.SetWeak(
this,
[](const WeakCallbackInfo<BaseObject>& data) {
BaseObject* obj = data.GetParameter();
// Clear the persistent handle so that ~BaseObject() doesn't attempt
// to mess with internal fields, since the JS object may have
// transitioned into an invalid state.
// Refs: https://github.com/nodejs/node/issues/18897
obj->persistent_handle_.Reset();
CHECK_IMPLIES(obj->has_pointer_data(),
obj->pointer_data()->strong_ptr_count == 0);
obj->OnGCCollect();
},
WeakCallbackType::kParameter);
}
// This just has to be different from the Chromium ones:
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
// misinterpret the data stored in the embedder fields and try to garbage
// collect them.
uint16_t kNodeEmbedderId = 0x90de;
void BaseObject::LazilyInitializedJSTemplateConstructor(
const FunctionCallbackInfo<Value>& args) {
DCHECK(args.IsConstructCall());
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
&kNodeEmbedderId);
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
}
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
Environment* env) {
Local<FunctionTemplate> t = NewFunctionTemplate(
env->isolate(), LazilyInitializedJSTemplateConstructor);
t->Inherit(BaseObject::GetConstructorTemplate(env));
t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
return t;
}
BaseObject::PointerData* BaseObject::pointer_data() {
if (!has_pointer_data()) {
PointerData* metadata = new PointerData();
metadata->wants_weak_jsobj = persistent_handle_.IsWeak();
metadata->self = this;
pointer_data_ = metadata;
}
CHECK(has_pointer_data());
return pointer_data_;
}
void BaseObject::decrease_refcount() {
CHECK(has_pointer_data());
PointerData* metadata = pointer_data();
CHECK_GT(metadata->strong_ptr_count, 0);
unsigned int new_refcount = --metadata->strong_ptr_count;
if (new_refcount == 0) {
if (metadata->is_detached) {
OnGCCollect();
} else if (metadata->wants_weak_jsobj && !persistent_handle_.IsEmpty()) {
MakeWeak();
}
}
}
void BaseObject::increase_refcount() {
unsigned int prev_refcount = pointer_data()->strong_ptr_count++;
if (prev_refcount == 0 && !persistent_handle_.IsEmpty())
persistent_handle_.ClearWeak();
}
void BaseObject::DeleteMe(void* data) {
BaseObject* self = static_cast<BaseObject*>(data);
if (self->has_pointer_data() && self->pointer_data()->strong_ptr_count > 0) {
return self->Detach();
}
delete self;
}
bool BaseObject::IsDoneInitializing() const {
return true;
}
Local<Object> BaseObject::WrappedObject() const {
return object();
}
bool BaseObject::IsRootNode() const {
return !persistent_handle_.IsWeak();
}
Local<FunctionTemplate> BaseObject::GetConstructorTemplate(
IsolateData* isolate_data) {
Local<FunctionTemplate> tmpl = isolate_data->base_object_ctor_template();
if (tmpl.IsEmpty()) {
tmpl = NewFunctionTemplate(isolate_data->isolate(), nullptr);
tmpl->SetClassName(
FIXED_ONE_BYTE_STRING(isolate_data->isolate(), "BaseObject"));
isolate_data->set_base_object_ctor_template(tmpl);
}
return tmpl;
}
bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
return IsWeakOrDetached();
}
} // namespace node

View File

@ -32,6 +32,7 @@ namespace node {
class Environment;
class IsolateData;
class Realm;
template <typename T, bool kIsWeak>
class BaseObjectPtrImpl;
@ -47,7 +48,10 @@ class BaseObject : public MemoryRetainer {
// Associates this object with `object`. It uses the 1st internal field for
// that, and in particular aborts if there is no such field.
BaseObject(Environment* env, v8::Local<v8::Object> object);
// This is the designated constructor.
BaseObject(Realm* realm, v8::Local<v8::Object> object);
// Convenient constructor for constructing BaseObject in the principal realm.
inline BaseObject(Environment* env, v8::Local<v8::Object> object);
~BaseObject() override;
BaseObject() = delete;
@ -63,6 +67,7 @@ class BaseObject : public MemoryRetainer {
inline v8::Global<v8::Object>& persistent();
inline Environment* env() const;
inline Realm* realm() const;
// Get a BaseObject* pointer, or subclass pointer, for the JS object that
// was also passed to the `BaseObject()` constructor initially.
@ -91,6 +96,7 @@ class BaseObject : public MemoryRetainer {
// Utility to create a FunctionTemplate with one internal field (used for
// the `BaseObject*` pointer) and a constructor that initializes that field
// to `nullptr`.
// TODO(legendecas): Disentangle template with env.
static v8::Local<v8::FunctionTemplate> MakeLazilyInitializedJSTemplate(
Environment* env);
@ -213,7 +219,7 @@ class BaseObject : public MemoryRetainer {
void decrease_refcount();
void increase_refcount();
Environment* env_;
Realm* realm_;
PointerData* pointer_data_ = nullptr;
};

View File

@ -745,6 +745,12 @@ inline IsolateData* Environment::isolate_data() const {
return isolate_data_;
}
template <typename T>
inline void Environment::ForEachRealm(T&& iterator) const {
// TODO(legendecas): iterate over more realms bound to the environment.
iterator(principal_realm());
}
inline void Environment::ThrowError(const char* errmsg) {
ThrowError(v8::Exception::Error, errmsg);
}
@ -789,27 +795,6 @@ void Environment::RemoveCleanupHook(CleanupQueue::Callback fn, void* arg) {
cleanup_queue_.Remove(fn, arg);
}
template <typename T>
void Environment::ForEachBaseObject(T&& iterator) {
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
}
void Environment::modify_base_object_count(int64_t delta) {
base_object_count_ += delta;
}
int64_t Environment::base_object_count() const {
return base_object_count_;
}
inline void Environment::set_base_object_created_by_bootstrap(int64_t count) {
base_object_created_by_bootstrap_ = base_object_count_;
}
int64_t Environment::base_object_created_after_bootstrap() const {
return base_object_count_ - base_object_created_by_bootstrap_;
}
void Environment::set_main_utf16(std::unique_ptr<v8::String::Value> str) {
CHECK(!main_utf16_);
main_utf16_ = std::move(str);

View File

@ -37,7 +37,6 @@ using v8::Context;
using v8::EmbedderGraph;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::HeapSpaceStatistics;
@ -58,8 +57,6 @@ using v8::TracingController;
using v8::TryCatch;
using v8::Undefined;
using v8::Value;
using v8::WeakCallbackInfo;
using v8::WeakCallbackType;
using worker::Worker;
int const ContextEmbedderTag::kNodeContextTag = 0x6e6f64;
@ -841,8 +838,6 @@ Environment::~Environment() {
addon.Close();
}
}
CHECK_EQ(base_object_count_, 0);
}
void Environment::InitializeLibuv() {
@ -1003,11 +998,16 @@ void Environment::RunCleanup() {
started_cleanup_ = true;
TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "RunCleanup");
bindings_.clear();
// Only BaseObject's cleanups are registered as per-realm cleanup hooks now.
// Defer the BaseObject cleanup after handles are cleaned up.
CleanupHandles();
while (!cleanup_queue_.empty() || native_immediates_.size() > 0 ||
while (!cleanup_queue_.empty() || principal_realm_->HasCleanupHooks() ||
native_immediates_.size() > 0 ||
native_immediates_threadsafe_.size() > 0 ||
native_immediates_interrupts_.size() > 0) {
// TODO(legendecas): cleanup handles in per-realm cleanup hooks as well.
principal_realm_->RunCleanup();
cleanup_queue_.Drain();
CleanupHandles();
}
@ -1566,8 +1566,8 @@ void Environment::RemoveUnmanagedFd(int fd) {
void Environment::PrintInfoForSnapshotIfDebug() {
if (enabled_debug_list()->enabled(DebugCategory::MKSNAPSHOT)) {
fprintf(stderr, "BaseObjects at the exit of the Environment:\n");
PrintAllBaseObjects();
fprintf(stderr, "At the exit of the Environment:\n");
principal_realm()->PrintInfoForSnapshot();
fprintf(stderr, "\nNative modules without cache:\n");
for (const auto& s : builtins_without_cache) {
fprintf(stderr, "%s\n", s.c_str());
@ -1583,45 +1583,6 @@ void Environment::PrintInfoForSnapshotIfDebug() {
}
}
void Environment::PrintAllBaseObjects() {
size_t i = 0;
std::cout << "BaseObjects\n";
ForEachBaseObject([&](BaseObject* obj) {
std::cout << "#" << i++ << " " << obj << ": " <<
obj->MemoryInfoName() << "\n";
});
}
void Environment::VerifyNoStrongBaseObjects() {
// When a process exits cleanly, i.e. because the event loop ends up without
// things to wait for, the Node.js objects that are left on the heap should
// be:
//
// 1. weak, i.e. ready for garbage collection once no longer referenced, or
// 2. detached, i.e. scheduled for destruction once no longer referenced, or
// 3. an unrefed libuv handle, i.e. does not keep the event loop alive, or
// 4. an inactive libuv handle (essentially the same here)
//
// There are a few exceptions to this rule, but generally, if there are
// C++-backed Node.js objects on the heap that do not fall into the above
// categories, we may be looking at a potential memory leak. Most likely,
// the cause is a missing MakeWeak() call on the corresponding object.
//
// In order to avoid this kind of problem, we check the list of BaseObjects
// for these criteria. Currently, we only do so when explicitly instructed to
// or when in debug mode (where --verify-base-objects is always-on).
if (!options()->verify_base_objects) return;
ForEachBaseObject([](BaseObject* obj) {
if (obj->IsNotIndicativeOfMemoryLeakAtExit()) return;
fprintf(stderr, "Found bad BaseObject during clean exit: %s\n",
obj->MemoryInfoName().c_str());
fflush(stderr);
ABORT();
});
}
EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) {
EnvSerializeInfo info;
Local<Context> ctx = context();
@ -1640,10 +1601,6 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) {
info.should_abort_on_uncaught_toggle =
should_abort_on_uncaught_toggle_.Serialize(ctx, creator);
// Do this after other creator->AddData() calls so that Snapshotable objects
// can use 0 to indicate that a SnapshotIndex is invalid.
SerializeSnapshotableObjects(this, creator, &info);
info.principal_realm = principal_realm_->Serialize(creator);
return info;
}
@ -1717,6 +1674,7 @@ void Environment::BuildEmbedderGraph(Isolate* isolate,
void* data) {
MemoryTracker tracker(isolate, graph);
Environment* env = static_cast<Environment*>(data);
// Start traversing embedder objects from the root Environment object.
tracker.Track(env);
}
@ -1891,153 +1849,4 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
void Environment::RunWeakRefCleanup() {
isolate()->ClearKeptObjects();
}
// Not really any better place than env.cc at this moment.
BaseObject::BaseObject(Environment* env, Local<Object> object)
: persistent_handle_(env->isolate(), object), env_(env) {
CHECK_EQ(false, object.IsEmpty());
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
&kNodeEmbedderId);
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
static_cast<void*>(this));
env->AddCleanupHook(DeleteMe, static_cast<void*>(this));
env->modify_base_object_count(1);
}
BaseObject::~BaseObject() {
env()->modify_base_object_count(-1);
env()->RemoveCleanupHook(DeleteMe, static_cast<void*>(this));
if (UNLIKELY(has_pointer_data())) {
PointerData* metadata = pointer_data();
CHECK_EQ(metadata->strong_ptr_count, 0);
metadata->self = nullptr;
if (metadata->weak_ptr_count == 0) delete metadata;
}
if (persistent_handle_.IsEmpty()) {
// This most likely happened because the weak callback below cleared it.
return;
}
{
HandleScope handle_scope(env()->isolate());
object()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
}
}
void BaseObject::MakeWeak() {
if (has_pointer_data()) {
pointer_data()->wants_weak_jsobj = true;
if (pointer_data()->strong_ptr_count > 0) return;
}
persistent_handle_.SetWeak(
this,
[](const WeakCallbackInfo<BaseObject>& data) {
BaseObject* obj = data.GetParameter();
// Clear the persistent handle so that ~BaseObject() doesn't attempt
// to mess with internal fields, since the JS object may have
// transitioned into an invalid state.
// Refs: https://github.com/nodejs/node/issues/18897
obj->persistent_handle_.Reset();
CHECK_IMPLIES(obj->has_pointer_data(),
obj->pointer_data()->strong_ptr_count == 0);
obj->OnGCCollect();
},
WeakCallbackType::kParameter);
}
// This just has to be different from the Chromium ones:
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
// misinterpret the data stored in the embedder fields and try to garbage
// collect them.
uint16_t kNodeEmbedderId = 0x90de;
void BaseObject::LazilyInitializedJSTemplateConstructor(
const FunctionCallbackInfo<Value>& args) {
DCHECK(args.IsConstructCall());
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
&kNodeEmbedderId);
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
}
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
Environment* env) {
Local<FunctionTemplate> t = NewFunctionTemplate(
env->isolate(), LazilyInitializedJSTemplateConstructor);
t->Inherit(BaseObject::GetConstructorTemplate(env));
t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
return t;
}
BaseObject::PointerData* BaseObject::pointer_data() {
if (!has_pointer_data()) {
PointerData* metadata = new PointerData();
metadata->wants_weak_jsobj = persistent_handle_.IsWeak();
metadata->self = this;
pointer_data_ = metadata;
}
CHECK(has_pointer_data());
return pointer_data_;
}
void BaseObject::decrease_refcount() {
CHECK(has_pointer_data());
PointerData* metadata = pointer_data();
CHECK_GT(metadata->strong_ptr_count, 0);
unsigned int new_refcount = --metadata->strong_ptr_count;
if (new_refcount == 0) {
if (metadata->is_detached) {
OnGCCollect();
} else if (metadata->wants_weak_jsobj && !persistent_handle_.IsEmpty()) {
MakeWeak();
}
}
}
void BaseObject::increase_refcount() {
unsigned int prev_refcount = pointer_data()->strong_ptr_count++;
if (prev_refcount == 0 && !persistent_handle_.IsEmpty())
persistent_handle_.ClearWeak();
}
void BaseObject::DeleteMe(void* data) {
BaseObject* self = static_cast<BaseObject*>(data);
if (self->has_pointer_data() &&
self->pointer_data()->strong_ptr_count > 0) {
return self->Detach();
}
delete self;
}
bool BaseObject::IsDoneInitializing() const { return true; }
Local<Object> BaseObject::WrappedObject() const {
return object();
}
bool BaseObject::IsRootNode() const {
return !persistent_handle_.IsWeak();
}
Local<FunctionTemplate> BaseObject::GetConstructorTemplate(
IsolateData* isolate_data) {
Local<FunctionTemplate> tmpl = isolate_data->base_object_ctor_template();
if (tmpl.IsEmpty()) {
tmpl = NewFunctionTemplate(isolate_data->isolate(), nullptr);
tmpl->SetClassName(
FIXED_ONE_BYTE_STRING(isolate_data->isolate(), "BaseObject"));
isolate_data->set_base_object_ctor_template(tmpl);
}
return tmpl;
}
bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
return IsWeakOrDetached();
}
} // namespace node

View File

@ -508,7 +508,6 @@ struct DeserializeRequest {
};
struct EnvSerializeInfo {
std::vector<PropInfo> native_objects;
std::vector<std::string> builtins;
AsyncHooks::SerializeInfo async_hooks;
TickInfo::SerializeInfo tick_info;
@ -600,8 +599,6 @@ class Environment : public MemoryRetainer {
void DeserializeProperties(const EnvSerializeInfo* info);
void PrintInfoForSnapshotIfDebug();
void PrintAllBaseObjects();
void VerifyNoStrongBaseObjects();
void EnqueueDeserializeRequest(DeserializeRequestCallback cb,
v8::Local<v8::Object> holder,
int index,
@ -973,19 +970,6 @@ class Environment : public MemoryRetainer {
inline std::shared_ptr<EnvironmentOptions> options();
inline std::shared_ptr<ExclusiveAccess<HostPort>> inspector_host_port();
// The BaseObject count is a debugging helper that makes sure that there are
// no memory leaks caused by BaseObjects staying alive longer than expected
// (in particular, no circular BaseObjectPtr references).
inline void modify_base_object_count(int64_t delta);
inline int64_t base_object_count() const;
// Base object count created in bootstrap of the principal realm.
// This adjusts the return value of base_object_created_after_bootstrap() so
// that tests that check the count do not have to account for internally
// created BaseObjects.
inline void set_base_object_created_by_bootstrap(int64_t count);
inline int64_t base_object_created_after_bootstrap() const;
inline int32_t stack_trace_limit() const { return 10; }
#if HAVE_INSPECTOR
@ -1038,7 +1022,7 @@ class Environment : public MemoryRetainer {
void RemoveUnmanagedFd(int fd);
template <typename T>
void ForEachBaseObject(T&& iterator);
void ForEachRealm(T&& iterator) const;
inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
inline bool is_in_heapsnapshot_heap_limit_callback() const;
@ -1188,8 +1172,6 @@ class Environment : public MemoryRetainer {
CleanupQueue cleanup_queue_;
bool started_cleanup_ = false;
int64_t base_object_count_ = 0;
int64_t base_object_created_by_bootstrap_ = 0;
std::atomic_bool is_stopping_ { false };
std::unordered_set<int> unmanaged_fds_;

View File

@ -3,6 +3,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "cleanup_queue-inl.h"
#include "node_realm.h"
namespace node {
@ -41,6 +42,23 @@ inline bool Realm::has_run_bootstrapping_code() const {
return has_run_bootstrapping_code_;
}
template <typename T>
void Realm::ForEachBaseObject(T&& iterator) const {
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
}
void Realm::modify_base_object_count(int64_t delta) {
base_object_count_ += delta;
}
int64_t Realm::base_object_created_after_bootstrap() const {
return base_object_count_ - base_object_created_by_bootstrap_;
}
int64_t Realm::base_object_count() const {
return base_object_count_;
}
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> Realm::PropertyName() const { \
return PersistentToLocal::Strong(PropertyName##_); \
@ -55,6 +73,18 @@ v8::Local<v8::Context> Realm::context() const {
return PersistentToLocal::Strong(context_);
}
void Realm::AddCleanupHook(CleanupQueue::Callback fn, void* arg) {
cleanup_queue_.Add(fn, arg);
}
void Realm::RemoveCleanupHook(CleanupQueue::Callback fn, void* arg) {
cleanup_queue_.Remove(fn, arg);
}
bool Realm::HasCleanupHooks() const {
return !cleanup_queue_.empty();
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@ -34,6 +34,10 @@ Realm::Realm(Environment* env,
}
}
Realm::~Realm() {
CHECK_EQ(base_object_count_, 0);
}
void Realm::MemoryInfo(MemoryTracker* tracker) const {
#define V(PropertyName, TypeName) \
tracker->TrackField(#PropertyName, PropertyName());
@ -41,6 +45,13 @@ void Realm::MemoryInfo(MemoryTracker* tracker) const {
#undef V
tracker->TrackField("env", env_);
tracker->TrackField("cleanup_queue", cleanup_queue_);
ForEachBaseObject([&](BaseObject* obj) {
if (obj->IsDoneInitializing()) {
tracker->Track(obj);
}
});
}
void Realm::CreateProperties() {
@ -104,6 +115,10 @@ RealmSerializeInfo Realm::Serialize(SnapshotCreator* creator) {
PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V
// Do this after other creator->AddData() calls so that Snapshotable objects
// can use 0 to indicate that a SnapshotIndex is invalid.
SerializeSnapshotableObjects(this, creator, &info);
info.context = creator->AddData(ctx, ctx);
return info;
}
@ -257,8 +272,6 @@ MaybeLocal<Value> Realm::RunBootstrapping() {
}
void Realm::DoneBootstrapping() {
has_run_bootstrapping_code_ = true;
// Make sure that no request or handle is created during bootstrap -
// if necessary those should be done in pre-execution.
// Usually, doing so would trigger the checks present in the ReqWrap and
@ -269,8 +282,61 @@ void Realm::DoneBootstrapping() {
CHECK(env_->req_wrap_queue()->IsEmpty());
CHECK(env_->handle_wrap_queue()->IsEmpty());
// TODO(legendecas): track base object count by realms.
env_->set_base_object_created_by_bootstrap(env_->base_object_count());
has_run_bootstrapping_code_ = true;
// This adjusts the return value of base_object_created_after_bootstrap() so
// that tests that check the count do not have to account for internally
// created BaseObjects.
base_object_created_by_bootstrap_ = base_object_count_;
}
void Realm::RunCleanup() {
TRACE_EVENT0(TRACING_CATEGORY_NODE1(realm), "RunCleanup");
cleanup_queue_.Drain();
}
void Realm::PrintInfoForSnapshot() {
fprintf(stderr, "Realm = %p\n", this);
fprintf(stderr, "BaseObjects of the Realm:\n");
size_t i = 0;
ForEachBaseObject([&](BaseObject* obj) {
std::cout << "#" << i++ << " " << obj << ": " << obj->MemoryInfoName()
<< "\n";
});
fprintf(stderr, "End of the Realm.\n");
}
void Realm::VerifyNoStrongBaseObjects() {
// When a process exits cleanly, i.e. because the event loop ends up without
// things to wait for, the Node.js objects that are left on the heap should
// be:
//
// 1. weak, i.e. ready for garbage collection once no longer referenced, or
// 2. detached, i.e. scheduled for destruction once no longer referenced, or
// 3. an unrefed libuv handle, i.e. does not keep the event loop alive, or
// 4. an inactive libuv handle (essentially the same here)
//
// There are a few exceptions to this rule, but generally, if there are
// C++-backed Node.js objects on the heap that do not fall into the above
// categories, we may be looking at a potential memory leak. Most likely,
// the cause is a missing MakeWeak() call on the corresponding object.
//
// In order to avoid this kind of problem, we check the list of BaseObjects
// for these criteria. Currently, we only do so when explicitly instructed to
// or when in debug mode (where --verify-base-objects is always-on).
// TODO(legendecas): introduce per-realm options.
if (!env()->options()->verify_base_objects) return;
ForEachBaseObject([](BaseObject* obj) {
if (obj->IsNotIndicativeOfMemoryLeakAtExit()) return;
fprintf(stderr,
"Found bad BaseObject during clean exit: %s\n",
obj->MemoryInfoName().c_str());
fflush(stderr);
ABORT();
});
}
} // namespace node

View File

@ -4,6 +4,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <v8.h>
#include "cleanup_queue.h"
#include "env_properties.h"
#include "memory_tracker.h"
#include "node_snapshotable.h"
@ -12,6 +13,7 @@ namespace node {
struct RealmSerializeInfo {
std::vector<PropInfo> persistent_values;
std::vector<PropInfo> native_objects;
SnapshotIndex context;
friend std::ostream& operator<<(std::ostream& o, const RealmSerializeInfo& i);
@ -45,7 +47,7 @@ class Realm : public MemoryRetainer {
Realm(Environment* env,
v8::Local<v8::Context> context,
const RealmSerializeInfo* realm_info);
~Realm() = default;
~Realm();
Realm(const Realm&) = delete;
Realm& operator=(const Realm&) = delete;
@ -65,11 +67,32 @@ class Realm : public MemoryRetainer {
v8::MaybeLocal<v8::Value> BootstrapNode();
v8::MaybeLocal<v8::Value> RunBootstrapping();
inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg);
inline void RemoveCleanupHook(CleanupQueue::Callback cb, void* arg);
inline bool HasCleanupHooks() const;
void RunCleanup();
template <typename T>
void ForEachBaseObject(T&& iterator) const;
void PrintInfoForSnapshot();
void VerifyNoStrongBaseObjects();
inline IsolateData* isolate_data() const;
inline Environment* env() const;
inline v8::Isolate* isolate() const;
inline v8::Local<v8::Context> context() const;
inline bool has_run_bootstrapping_code() const;
// The BaseObject count is a debugging helper that makes sure that there are
// no memory leaks caused by BaseObjects staying alive longer than expected
// (in particular, no circular BaseObjectPtr references).
inline void modify_base_object_count(int64_t delta);
inline int64_t base_object_count() const;
// Base object count created after the bootstrap of the realm.
inline int64_t base_object_created_after_bootstrap() const;
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> PropertyName() const; \
inline void set_##PropertyName(v8::Local<TypeName> value);
@ -87,6 +110,11 @@ class Realm : public MemoryRetainer {
v8::Global<v8::Context> context_;
bool has_run_bootstrapping_code_ = false;
int64_t base_object_count_ = 0;
int64_t base_object_created_by_bootstrap_ = 0;
CleanupQueue cleanup_queue_;
#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName##_;
PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V

View File

@ -102,6 +102,9 @@ std::ostream& operator<<(std::ostream& output, const RealmSerializeInfo& i) {
<< "// -- persistent_values begins --\n"
<< i.persistent_values << ",\n"
<< "// -- persistent_values ends --\n"
<< "// -- native_objects begins --\n"
<< i.native_objects << ",\n"
<< "// -- native_objects ends --\n"
<< i.context << ", // context\n"
<< "}";
return output;
@ -109,9 +112,6 @@ std::ostream& operator<<(std::ostream& output, const RealmSerializeInfo& i) {
std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) {
output << "{\n"
<< "// -- native_objects begins --\n"
<< i.native_objects << ",\n"
<< "// -- native_objects ends --\n"
<< "// -- builtins begins --\n"
<< i.builtins << ",\n"
<< "// -- builtins ends --\n"
@ -705,6 +705,7 @@ RealmSerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<RealmSerializeInfo>()\n");
RealmSerializeInfo result;
result.persistent_values = ReadVector<PropInfo>();
result.native_objects = ReadVector<PropInfo>();
result.context = Read<SnapshotIndex>();
return result;
}
@ -718,6 +719,7 @@ size_t FileWriter::Write(const RealmSerializeInfo& data) {
// Use += here to ensure order of evaluation.
size_t written_total = WriteVector<PropInfo>(data.persistent_values);
written_total += WriteVector<PropInfo>(data.native_objects);
written_total += Write<SnapshotIndex>(data.context);
Debug("Write<RealmSerializeInfo>() wrote %d bytes\n", written_total);
@ -728,7 +730,6 @@ template <>
EnvSerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<EnvSerializeInfo>()\n");
EnvSerializeInfo result;
result.native_objects = ReadVector<PropInfo>();
result.builtins = ReadVector<std::string>();
result.async_hooks = Read<AsyncHooks::SerializeInfo>();
result.tick_info = Read<TickInfo::SerializeInfo>();
@ -750,8 +751,7 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) {
}
// Use += here to ensure order of evaluation.
size_t written_total = WriteVector<PropInfo>(data.native_objects);
written_total += WriteVector<std::string>(data.builtins);
size_t written_total = WriteVector<std::string>(data.builtins);
written_total += Write<AsyncHooks::SerializeInfo>(data.async_hooks);
written_total += Write<TickInfo::SerializeInfo>(data.tick_info);
written_total += Write<ImmediateInfo::SerializeInfo>(data.immediate_info);
@ -1194,7 +1194,7 @@ int SnapshotBuilder::Generate(SnapshotData* out,
}
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
env->PrintAllBaseObjects();
env->ForEachRealm([](Realm* realm) { realm->PrintInfoForSnapshot(); });
printf("Environment = %p\n", env);
}
@ -1400,11 +1400,13 @@ StartupData SerializeNodeContextInternalFields(Local<Object> holder,
static_cast<int>(info->length)};
}
void SerializeSnapshotableObjects(Environment* env,
void SerializeSnapshotableObjects(Realm* realm,
SnapshotCreator* creator,
EnvSerializeInfo* info) {
RealmSerializeInfo* info) {
HandleScope scope(realm->isolate());
Local<Context> context = realm->context();
uint32_t i = 0;
env->ForEachBaseObject([&](BaseObject* obj) {
realm->ForEachBaseObject([&](BaseObject* obj) {
// If there are any BaseObjects that are not snapshotable left
// during context serialization, V8 would crash due to unregistered
// global handles and print detailed information about them.
@ -1422,8 +1424,8 @@ void SerializeSnapshotableObjects(Environment* env,
*(ptr->object()),
type_name);
if (ptr->PrepareForSerialization(env->context(), creator)) {
SnapshotIndex index = creator->AddData(env->context(), obj->object());
if (ptr->PrepareForSerialization(context, creator)) {
SnapshotIndex index = creator->AddData(context, obj->object());
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialized with index=%d\n",
static_cast<int>(index));

View File

@ -10,7 +10,7 @@
namespace node {
class Environment;
struct EnvSerializeInfo;
struct RealmSerializeInfo;
struct SnapshotData;
class ExternalReferenceRegistry;
@ -131,9 +131,9 @@ void DeserializeNodeInternalFields(v8::Local<v8::Object> holder,
int index,
v8::StartupData payload,
void* env);
void SerializeSnapshotableObjects(Environment* env,
void SerializeSnapshotableObjects(Realm* realm,
v8::SnapshotCreator* creator,
EnvSerializeInfo* info);
RealmSerializeInfo* info);
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@ -1,6 +1,7 @@
#include "base_object-inl.h"
#include "gtest/gtest.h"
#include "node.h"
#include "base_object-inl.h"
#include "node_realm-inl.h"
#include "node_test_fixture.h"
using node::BaseObject;
@ -9,6 +10,7 @@ using node::BaseObjectWeakPtr;
using node::Environment;
using node::MakeBaseObject;
using node::MakeDetachedBaseObject;
using node::Realm;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
@ -46,13 +48,14 @@ TEST_F(BaseObjectPtrTest, ScopedDetached) {
const Argv argv;
Env env_{handle_scope, argv};
Environment* env = *env_;
Realm* realm = env->principal_realm();
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
{
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
}
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
}
TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) {
@ -60,17 +63,18 @@ TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) {
const Argv argv;
Env env_{handle_scope, argv};
Environment* env = *env_;
Realm* realm = env->principal_realm();
BaseObjectWeakPtr<DummyBaseObject> weak_ptr;
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
{
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
weak_ptr = ptr;
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
}
EXPECT_EQ(weak_ptr.get(), nullptr);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
}
TEST_F(BaseObjectPtrTest, Undetached) {
@ -78,16 +82,17 @@ TEST_F(BaseObjectPtrTest, Undetached) {
const Argv argv;
Env env_{handle_scope, argv};
Environment* env = *env_;
Realm* realm = env->principal_realm();
node::AddEnvironmentCleanupHook(
isolate_,
[](void* arg) {
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(), 0);
EXPECT_EQ(static_cast<Realm*>(arg)->base_object_count(), 0);
},
env);
realm);
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::New(env);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
}
TEST_F(BaseObjectPtrTest, GCWeak) {
@ -95,6 +100,7 @@ TEST_F(BaseObjectPtrTest, GCWeak) {
const Argv argv;
Env env_{handle_scope, argv};
Environment* env = *env_;
Realm* realm = env->principal_realm();
BaseObjectWeakPtr<DummyBaseObject> weak_ptr;
@ -104,21 +110,21 @@ TEST_F(BaseObjectPtrTest, GCWeak) {
weak_ptr = ptr;
ptr->MakeWeak();
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(weak_ptr.get(), ptr.get());
EXPECT_EQ(weak_ptr->persistent().IsWeak(), false);
ptr.reset();
}
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
EXPECT_NE(weak_ptr.get(), nullptr);
EXPECT_EQ(weak_ptr->persistent().IsWeak(), true);
v8::V8::SetFlagsFromString("--expose-gc");
isolate_->RequestGarbageCollectionForTesting(Isolate::kFullGarbageCollection);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(weak_ptr.get(), nullptr);
}
@ -127,9 +133,10 @@ TEST_F(BaseObjectPtrTest, Moveable) {
const Argv argv;
Env env_{handle_scope, argv};
Environment* env = *env_;
Realm* realm = env->principal_realm();
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
BaseObjectWeakPtr<DummyBaseObject> weak_ptr { ptr };
EXPECT_EQ(weak_ptr.get(), ptr.get());
@ -140,12 +147,12 @@ TEST_F(BaseObjectPtrTest, Moveable) {
BaseObjectWeakPtr<DummyBaseObject> weak_ptr2 = std::move(weak_ptr);
EXPECT_EQ(weak_ptr2.get(), ptr2.get());
EXPECT_EQ(weak_ptr.get(), nullptr);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 1);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 1);
ptr2.reset();
EXPECT_EQ(weak_ptr2.get(), nullptr);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 0);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 0);
}
TEST_F(BaseObjectPtrTest, NestedClasses) {
@ -165,18 +172,19 @@ TEST_F(BaseObjectPtrTest, NestedClasses) {
const Argv argv;
Env env_{handle_scope, argv};
Environment* env = *env_;
Realm* realm = env->principal_realm();
node::AddEnvironmentCleanupHook(
isolate_,
[](void* arg) {
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(), 0);
EXPECT_EQ(static_cast<Realm*>(arg)->base_object_count(), 0);
},
env);
realm);
ObjectWithPtr* obj =
new ObjectWithPtr(env, DummyBaseObject::MakeJSObject(env));
obj->ptr1 = DummyBaseObject::NewDetached(env);
obj->ptr2 = DummyBaseObject::New(env);
EXPECT_EQ(env->base_object_created_after_bootstrap(), 3);
EXPECT_EQ(realm->base_object_created_after_bootstrap(), 3);
}

View File

@ -21,11 +21,16 @@ validateSnapshotNodes('Node / Environment', [{
]
}]);
validateSnapshotNodes('Node / CleanupQueue', [{
children: [
{ node_name: 'Node / ContextifyScript' },
]
}]);
validateSnapshotNodes('Node / CleanupQueue', [
// The first one is the cleanup_queue of the Environment.
{},
// The second one is the cleanup_queue of the principal realm.
{
children: [
{ node_name: 'Node / ContextifyScript' },
]
},
]);
validateSnapshotNodes('Node / Realm', [{
children: [