src: implement MemoryRetainer in Environment
This allows us to track the essentially-global objects in Environment in the heap snapshot. Note that this patch only tracks the fields that can be tracked correctly. There are still several types of fields that cannot be tracked: - v8::Data including v8::Private, v8::ObjectTemplate etc. - Internal types that do not implement MemoryRetainer yet - STL containers with MemoryRetainer* inside - STL containers with numeric types inside that should not have their nodes elided e.g. numeric keys in maps. The `BaseObject`s are now no longer globals. They are tracked as arguments in CleanupHookCallbacks referenced by the Environment node. This model is closer to how their lifetime is managed internally. To track the per-environment strong persistent properties, this patch divides them into those that are also `v8::Value` and those that are just `v8::Data`. The values can be tracked by the current memory tracker while the data cannot. This patch also implements the `MemoryRetainer` interface in several internal classes so that they can be tracked in the heap snapshot. PR-URL: https://github.com/nodejs/node/pull/27018 Refs: https://github.com/nodejs/node/issues/26776 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
ceb80f4157
commit
f59ec2abee
@ -86,7 +86,6 @@ class BaseObject : public MemoryRetainer {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
v8::Local<v8::Object> WrappedObject() const override;
|
v8::Local<v8::Object> WrappedObject() const override;
|
||||||
bool IsRootNode() const override;
|
|
||||||
static void DeleteMe(void* data);
|
static void DeleteMe(void* data);
|
||||||
|
|
||||||
// persistent_handle_ needs to be at a fixed offset from the start of the
|
// persistent_handle_ needs to be at a fixed offset from the start of the
|
||||||
@ -95,7 +94,7 @@ class BaseObject : public MemoryRetainer {
|
|||||||
// position of members in memory are predictable. For more information please
|
// position of members in memory are predictable. For more information please
|
||||||
// refer to `doc/guides/node-postmortem-support.md`
|
// refer to `doc/guides/node-postmortem-support.md`
|
||||||
friend int GenDebugSymbols();
|
friend int GenDebugSymbols();
|
||||||
friend class Environment;
|
friend class CleanupHookCallback;
|
||||||
|
|
||||||
Persistent<v8::Object> persistent_handle_;
|
Persistent<v8::Object> persistent_handle_;
|
||||||
Environment* env_;
|
Environment* env_;
|
||||||
|
@ -980,17 +980,17 @@ void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
|
|||||||
cleanup_hooks_.erase(search);
|
cleanup_hooks_.erase(search);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Environment::CleanupHookCallback::Hash::operator()(
|
size_t CleanupHookCallback::Hash::operator()(
|
||||||
const CleanupHookCallback& cb) const {
|
const CleanupHookCallback& cb) const {
|
||||||
return std::hash<void*>()(cb.arg_);
|
return std::hash<void*>()(cb.arg_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Environment::CleanupHookCallback::Equal::operator()(
|
bool CleanupHookCallback::Equal::operator()(
|
||||||
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
|
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
|
||||||
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
|
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseObject* Environment::CleanupHookCallback::GetBaseObject() const {
|
BaseObject* CleanupHookCallback::GetBaseObject() const {
|
||||||
if (fn_ == BaseObject::DeleteMe)
|
if (fn_ == BaseObject::DeleteMe)
|
||||||
return static_cast<BaseObject*>(arg_);
|
return static_cast<BaseObject*>(arg_);
|
||||||
else
|
else
|
||||||
@ -1054,6 +1054,7 @@ void AsyncRequest::set_stopped(bool flag) {
|
|||||||
PropertyName ## _.Reset(isolate(), value); \
|
PropertyName ## _.Reset(isolate(), value); \
|
||||||
}
|
}
|
||||||
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
|
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
|
||||||
|
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
|
||||||
#undef V
|
#undef V
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
119
src/env.cc
119
src/env.cc
@ -112,6 +112,29 @@ IsolateData::IsolateData(Isolate* isolate,
|
|||||||
#undef V
|
#undef V
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
|
#define V(PropertyName, StringValue) \
|
||||||
|
tracker->TrackField(#PropertyName, PropertyName(isolate()));
|
||||||
|
PER_ISOLATE_SYMBOL_PROPERTIES(V)
|
||||||
|
#undef V
|
||||||
|
|
||||||
|
#define V(PropertyName, StringValue) \
|
||||||
|
tracker->TrackField(#PropertyName, PropertyName(isolate()));
|
||||||
|
PER_ISOLATE_STRING_PROPERTIES(V)
|
||||||
|
#undef V
|
||||||
|
|
||||||
|
if (node_allocator_ != nullptr) {
|
||||||
|
tracker->TrackFieldWithSize(
|
||||||
|
"node_allocator", sizeof(*node_allocator_), "NodeArrayBufferAllocator");
|
||||||
|
} else {
|
||||||
|
tracker->TrackFieldWithSize(
|
||||||
|
"allocator", sizeof(*allocator_), "v8::ArrayBuffer::Allocator");
|
||||||
|
}
|
||||||
|
tracker->TrackFieldWithSize(
|
||||||
|
"platform", sizeof(*platform_), "MultiIsolatePlatform");
|
||||||
|
// TODO(joyeecheung): implement MemoryRetainer in the option classes.
|
||||||
|
}
|
||||||
|
|
||||||
void InitThreadLocalOnce() {
|
void InitThreadLocalOnce() {
|
||||||
CHECK_EQ(0, uv_key_create(&Environment::thread_local_env));
|
CHECK_EQ(0, uv_key_create(&Environment::thread_local_env));
|
||||||
}
|
}
|
||||||
@ -707,6 +730,7 @@ void Environment::set_debug_categories(const std::string& cats, bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_CATEGORY_NAMES(V)
|
DEBUG_CATEGORY_NAMES(V)
|
||||||
|
#undef V
|
||||||
|
|
||||||
if (comma_pos == std::string::npos)
|
if (comma_pos == std::string::npos)
|
||||||
break;
|
break;
|
||||||
@ -775,6 +799,21 @@ void Environment::CollectUVExceptionInfo(Local<Value> object,
|
|||||||
syscall, message, path, dest);
|
syscall, message, path, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImmediateInfo::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
|
tracker->TrackField("fields", fields_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TickInfo::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
|
tracker->TrackField("fields", fields_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
|
tracker->TrackField("providers", providers_);
|
||||||
|
tracker->TrackField("async_ids_stack", async_ids_stack_);
|
||||||
|
tracker->TrackField("fields", fields_);
|
||||||
|
tracker->TrackField("async_id_fields", async_id_fields_);
|
||||||
|
}
|
||||||
|
|
||||||
void AsyncHooks::grow_async_ids_stack() {
|
void AsyncHooks::grow_async_ids_stack() {
|
||||||
async_ids_stack_.reserve(async_ids_stack_.Length() * 3);
|
async_ids_stack_.reserve(async_ids_stack_.Length() * 3);
|
||||||
|
|
||||||
@ -805,13 +844,83 @@ void Environment::stop_sub_worker_contexts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemoryTracker::TrackField(const char* edge_name,
|
||||||
|
const CleanupHookCallback& value,
|
||||||
|
const char* node_name) {
|
||||||
|
v8::HandleScope handle_scope(isolate_);
|
||||||
|
// Here, we utilize the fact that CleanupHookCallback instances
|
||||||
|
// are all unique and won't be tracked twice in one BuildEmbedderGraph
|
||||||
|
// callback.
|
||||||
|
MemoryRetainerNode* n =
|
||||||
|
PushNode("CleanupHookCallback", sizeof(value), edge_name);
|
||||||
|
// TODO(joyeecheung): at the moment only arguments of type BaseObject will be
|
||||||
|
// identified and tracked here (based on their deleters),
|
||||||
|
// but we may convert and track other known types here.
|
||||||
|
BaseObject* obj = value.GetBaseObject();
|
||||||
|
if (obj != nullptr) {
|
||||||
|
this->TrackField("arg", obj);
|
||||||
|
}
|
||||||
|
CHECK_EQ(CurrentNode(), n);
|
||||||
|
CHECK_NE(n->size_, 0);
|
||||||
|
PopNode();
|
||||||
|
}
|
||||||
|
|
||||||
void Environment::BuildEmbedderGraph(Isolate* isolate,
|
void Environment::BuildEmbedderGraph(Isolate* isolate,
|
||||||
EmbedderGraph* graph,
|
EmbedderGraph* graph,
|
||||||
void* data) {
|
void* data) {
|
||||||
MemoryTracker tracker(isolate, graph);
|
MemoryTracker tracker(isolate, graph);
|
||||||
static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
|
Environment* env = static_cast<Environment*>(data);
|
||||||
tracker.Track(obj);
|
tracker.Track(env);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
inline size_t Environment::SelfSize() const {
|
||||||
|
size_t size = sizeof(*this);
|
||||||
|
// Remove non pointer fields that will be tracked in MemoryInfo()
|
||||||
|
// TODO(joyeecheung): refactor the MemoryTracker interface so
|
||||||
|
// this can be done for common types within the Track* calls automatically
|
||||||
|
// if a certain scope is entered.
|
||||||
|
size -= sizeof(thread_stopper_);
|
||||||
|
size -= sizeof(async_hooks_);
|
||||||
|
size -= sizeof(tick_info_);
|
||||||
|
size -= sizeof(immediate_info_);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Environment::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
|
// Iteratable STLs have their own sizes subtracted from the parent
|
||||||
|
// by default.
|
||||||
|
tracker->TrackField("isolate_data", isolate_data_);
|
||||||
|
tracker->TrackField("native_modules_with_cache", native_modules_with_cache);
|
||||||
|
tracker->TrackField("native_modules_without_cache",
|
||||||
|
native_modules_without_cache);
|
||||||
|
tracker->TrackField("destroy_async_id_list", destroy_async_id_list_);
|
||||||
|
tracker->TrackField("exec_argv", exec_argv_);
|
||||||
|
tracker->TrackField("should_abort_on_uncaught_toggle",
|
||||||
|
should_abort_on_uncaught_toggle_);
|
||||||
|
tracker->TrackField("stream_base_state", stream_base_state_);
|
||||||
|
tracker->TrackField("fs_stats_field_array", fs_stats_field_array_);
|
||||||
|
tracker->TrackField("fs_stats_field_bigint_array",
|
||||||
|
fs_stats_field_bigint_array_);
|
||||||
|
tracker->TrackField("thread_stopper", thread_stopper_);
|
||||||
|
tracker->TrackField("cleanup_hooks", cleanup_hooks_);
|
||||||
|
tracker->TrackField("async_hooks", async_hooks_);
|
||||||
|
tracker->TrackField("immediate_info", immediate_info_);
|
||||||
|
tracker->TrackField("tick_info", tick_info_);
|
||||||
|
|
||||||
|
#define V(PropertyName, TypeName) \
|
||||||
|
tracker->TrackField(#PropertyName, PropertyName());
|
||||||
|
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
|
||||||
|
#undef V
|
||||||
|
|
||||||
|
// FIXME(joyeecheung): track other fields in Environment.
|
||||||
|
// Currently MemoryTracker is unable to track these
|
||||||
|
// correctly:
|
||||||
|
// - Internal types that do not implement MemoryRetainer yet
|
||||||
|
// - STL containers with MemoryRetainer* inside
|
||||||
|
// - STL containers with numeric types inside that should not have their
|
||||||
|
// nodes elided e.g. numeric keys in maps.
|
||||||
|
// We also need to make sure that when we add a non-pointer field as its own
|
||||||
|
// node, we shift its sizeof() size out of the Environment node.
|
||||||
}
|
}
|
||||||
|
|
||||||
char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
|
char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
|
||||||
@ -875,8 +984,4 @@ Local<Object> BaseObject::WrappedObject() const {
|
|||||||
return object();
|
return object();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseObject::IsRootNode() const {
|
|
||||||
return !persistent_handle_.IsWeak();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
143
src/env.h
143
src/env.h
@ -38,6 +38,7 @@
|
|||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
#include "v8.h"
|
#include "v8.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -330,31 +331,48 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
|
|||||||
V(zero_return_string, "ZERO_RETURN")
|
V(zero_return_string, "ZERO_RETURN")
|
||||||
|
|
||||||
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
|
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
|
||||||
V(as_callback_data, v8::Object) \
|
|
||||||
V(as_callback_data_template, v8::FunctionTemplate) \
|
V(as_callback_data_template, v8::FunctionTemplate) \
|
||||||
|
V(async_wrap_ctor_template, v8::FunctionTemplate) \
|
||||||
|
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
|
||||||
|
V(context, v8::Context) \
|
||||||
|
V(fd_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(fdclose_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(filehandlereadwrap_template, v8::ObjectTemplate) \
|
||||||
|
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
|
||||||
|
V(http2settings_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(http2stream_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
|
||||||
|
V(message_event_object_template, v8::ObjectTemplate) \
|
||||||
|
V(message_port_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(pipe_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(promise_wrap_template, v8::ObjectTemplate) \
|
||||||
|
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(script_context_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(secure_context_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(shutdown_wrap_template, v8::ObjectTemplate) \
|
||||||
|
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
|
||||||
|
V(tcp_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(tty_constructor_template, v8::FunctionTemplate) \
|
||||||
|
V(write_wrap_template, v8::ObjectTemplate)
|
||||||
|
|
||||||
|
#define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \
|
||||||
|
V(as_callback_data, v8::Object) \
|
||||||
V(async_hooks_after_function, v8::Function) \
|
V(async_hooks_after_function, v8::Function) \
|
||||||
V(async_hooks_before_function, v8::Function) \
|
V(async_hooks_before_function, v8::Function) \
|
||||||
V(async_hooks_binding, v8::Object) \
|
V(async_hooks_binding, v8::Object) \
|
||||||
V(async_hooks_destroy_function, v8::Function) \
|
V(async_hooks_destroy_function, v8::Function) \
|
||||||
V(async_hooks_init_function, v8::Function) \
|
V(async_hooks_init_function, v8::Function) \
|
||||||
V(async_hooks_promise_resolve_function, v8::Function) \
|
V(async_hooks_promise_resolve_function, v8::Function) \
|
||||||
V(async_wrap_ctor_template, v8::FunctionTemplate) \
|
|
||||||
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
|
|
||||||
V(buffer_prototype_object, v8::Object) \
|
V(buffer_prototype_object, v8::Object) \
|
||||||
V(coverage_connection, v8::Object) \
|
V(coverage_connection, v8::Object) \
|
||||||
V(context, v8::Context) \
|
|
||||||
V(crypto_key_object_constructor, v8::Function) \
|
V(crypto_key_object_constructor, v8::Function) \
|
||||||
V(domain_callback, v8::Function) \
|
V(domain_callback, v8::Function) \
|
||||||
V(domexception_function, v8::Function) \
|
V(domexception_function, v8::Function) \
|
||||||
V(fd_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(fdclose_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(filehandlereadwrap_template, v8::ObjectTemplate) \
|
|
||||||
V(fs_use_promises_symbol, v8::Symbol) \
|
V(fs_use_promises_symbol, v8::Symbol) \
|
||||||
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
|
|
||||||
V(host_import_module_dynamically_callback, v8::Function) \
|
V(host_import_module_dynamically_callback, v8::Function) \
|
||||||
V(host_initialize_import_meta_object_callback, v8::Function) \
|
V(host_initialize_import_meta_object_callback, v8::Function) \
|
||||||
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(http2session_on_altsvc_function, v8::Function) \
|
V(http2session_on_altsvc_function, v8::Function) \
|
||||||
V(http2session_on_error_function, v8::Function) \
|
V(http2session_on_error_function, v8::Function) \
|
||||||
V(http2session_on_frame_error_function, v8::Function) \
|
V(http2session_on_frame_error_function, v8::Function) \
|
||||||
@ -367,48 +385,37 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
|
|||||||
V(http2session_on_settings_function, v8::Function) \
|
V(http2session_on_settings_function, v8::Function) \
|
||||||
V(http2session_on_stream_close_function, v8::Function) \
|
V(http2session_on_stream_close_function, v8::Function) \
|
||||||
V(http2session_on_stream_trailers_function, v8::Function) \
|
V(http2session_on_stream_trailers_function, v8::Function) \
|
||||||
V(http2settings_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(http2stream_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(internal_binding_loader, v8::Function) \
|
V(internal_binding_loader, v8::Function) \
|
||||||
V(immediate_callback_function, v8::Function) \
|
V(immediate_callback_function, v8::Function) \
|
||||||
V(inspector_console_extension_installer, v8::Function) \
|
V(inspector_console_extension_installer, v8::Function) \
|
||||||
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
|
|
||||||
V(message_port, v8::Object) \
|
V(message_port, v8::Object) \
|
||||||
V(message_event_object_template, v8::ObjectTemplate) \
|
|
||||||
V(message_port_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(native_module_require, v8::Function) \
|
V(native_module_require, v8::Function) \
|
||||||
V(on_coverage_message_function, v8::Function) \
|
V(on_coverage_message_function, v8::Function) \
|
||||||
V(performance_entry_callback, v8::Function) \
|
V(performance_entry_callback, v8::Function) \
|
||||||
V(performance_entry_template, v8::Function) \
|
V(performance_entry_template, v8::Function) \
|
||||||
V(pipe_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(process_object, v8::Object) \
|
V(process_object, v8::Object) \
|
||||||
V(primordials, v8::Object) \
|
V(primordials, v8::Object) \
|
||||||
V(promise_reject_callback, v8::Function) \
|
V(promise_reject_callback, v8::Function) \
|
||||||
V(promise_wrap_template, v8::ObjectTemplate) \
|
|
||||||
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(script_context_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(script_data_constructor_function, v8::Function) \
|
V(script_data_constructor_function, v8::Function) \
|
||||||
V(secure_context_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(shutdown_wrap_template, v8::ObjectTemplate) \
|
|
||||||
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
|
|
||||||
V(tcp_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(tick_callback_function, v8::Function) \
|
V(tick_callback_function, v8::Function) \
|
||||||
V(timers_callback_function, v8::Function) \
|
V(timers_callback_function, v8::Function) \
|
||||||
V(tls_wrap_constructor_function, v8::Function) \
|
V(tls_wrap_constructor_function, v8::Function) \
|
||||||
V(trace_category_state_function, v8::Function) \
|
V(trace_category_state_function, v8::Function) \
|
||||||
V(tty_constructor_template, v8::FunctionTemplate) \
|
|
||||||
V(udp_constructor_function, v8::Function) \
|
V(udp_constructor_function, v8::Function) \
|
||||||
V(url_constructor_function, v8::Function) \
|
V(url_constructor_function, v8::Function)
|
||||||
V(write_wrap_template, v8::ObjectTemplate)
|
|
||||||
|
|
||||||
class Environment;
|
class Environment;
|
||||||
|
|
||||||
class IsolateData {
|
class IsolateData : public MemoryRetainer {
|
||||||
public:
|
public:
|
||||||
IsolateData(v8::Isolate* isolate,
|
IsolateData(v8::Isolate* isolate,
|
||||||
uv_loop_t* event_loop,
|
uv_loop_t* event_loop,
|
||||||
MultiIsolatePlatform* platform = nullptr,
|
MultiIsolatePlatform* platform = nullptr,
|
||||||
ArrayBufferAllocator* node_allocator = nullptr);
|
ArrayBufferAllocator* node_allocator = nullptr);
|
||||||
|
SET_MEMORY_INFO_NAME(IsolateData);
|
||||||
|
SET_SELF_SIZE(IsolateData);
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
|
|
||||||
inline uv_loop_t* event_loop() const;
|
inline uv_loop_t* event_loop() const;
|
||||||
inline MultiIsolatePlatform* platform() const;
|
inline MultiIsolatePlatform* platform() const;
|
||||||
inline std::shared_ptr<PerIsolateOptions> options();
|
inline std::shared_ptr<PerIsolateOptions> options();
|
||||||
@ -563,8 +570,12 @@ namespace per_process {
|
|||||||
extern std::shared_ptr<KVStore> system_environment;
|
extern std::shared_ptr<KVStore> system_environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AsyncHooks {
|
class AsyncHooks : public MemoryRetainer {
|
||||||
public:
|
public:
|
||||||
|
SET_MEMORY_INFO_NAME(AsyncHooks);
|
||||||
|
SET_SELF_SIZE(AsyncHooks);
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
|
|
||||||
// Reason for both UidFields and Fields are that one is stored as a double*
|
// Reason for both UidFields and Fields are that one is stored as a double*
|
||||||
// and the other as a uint32_t*.
|
// and the other as a uint32_t*.
|
||||||
enum Fields {
|
enum Fields {
|
||||||
@ -626,7 +637,7 @@ class AsyncHooks {
|
|||||||
friend class Environment; // So we can call the constructor.
|
friend class Environment; // So we can call the constructor.
|
||||||
inline AsyncHooks();
|
inline AsyncHooks();
|
||||||
// Keep a list of all Persistent strings used for Provider types.
|
// Keep a list of all Persistent strings used for Provider types.
|
||||||
v8::Eternal<v8::String> providers_[AsyncWrap::PROVIDERS_LENGTH];
|
std::array<v8::Eternal<v8::String>, AsyncWrap::PROVIDERS_LENGTH> providers_;
|
||||||
// Stores the ids of the current execution context stack.
|
// Stores the ids of the current execution context stack.
|
||||||
AliasedBuffer<double, v8::Float64Array> async_ids_stack_;
|
AliasedBuffer<double, v8::Float64Array> async_ids_stack_;
|
||||||
// Attached to a Uint32Array that tracks the number of active hooks for
|
// Attached to a Uint32Array that tracks the number of active hooks for
|
||||||
@ -650,7 +661,7 @@ class AsyncCallbackScope {
|
|||||||
Environment* env_;
|
Environment* env_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ImmediateInfo {
|
class ImmediateInfo : public MemoryRetainer {
|
||||||
public:
|
public:
|
||||||
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
|
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
|
||||||
inline uint32_t count() const;
|
inline uint32_t count() const;
|
||||||
@ -664,6 +675,10 @@ class ImmediateInfo {
|
|||||||
ImmediateInfo(const ImmediateInfo&) = delete;
|
ImmediateInfo(const ImmediateInfo&) = delete;
|
||||||
ImmediateInfo& operator=(const ImmediateInfo&) = delete;
|
ImmediateInfo& operator=(const ImmediateInfo&) = delete;
|
||||||
|
|
||||||
|
SET_MEMORY_INFO_NAME(ImmediateInfo);
|
||||||
|
SET_SELF_SIZE(ImmediateInfo);
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Environment; // So we can call the constructor.
|
friend class Environment; // So we can call the constructor.
|
||||||
inline explicit ImmediateInfo(v8::Isolate* isolate);
|
inline explicit ImmediateInfo(v8::Isolate* isolate);
|
||||||
@ -673,12 +688,16 @@ class ImmediateInfo {
|
|||||||
AliasedBuffer<uint32_t, v8::Uint32Array> fields_;
|
AliasedBuffer<uint32_t, v8::Uint32Array> fields_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TickInfo {
|
class TickInfo : public MemoryRetainer {
|
||||||
public:
|
public:
|
||||||
inline AliasedBuffer<uint8_t, v8::Uint8Array>& fields();
|
inline AliasedBuffer<uint8_t, v8::Uint8Array>& fields();
|
||||||
inline bool has_tick_scheduled() const;
|
inline bool has_tick_scheduled() const;
|
||||||
inline bool has_rejection_to_warn() const;
|
inline bool has_rejection_to_warn() const;
|
||||||
|
|
||||||
|
SET_MEMORY_INFO_NAME(TickInfo);
|
||||||
|
SET_SELF_SIZE(TickInfo);
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
|
|
||||||
TickInfo(const TickInfo&) = delete;
|
TickInfo(const TickInfo&) = delete;
|
||||||
TickInfo& operator=(const TickInfo&) = delete;
|
TickInfo& operator=(const TickInfo&) = delete;
|
||||||
|
|
||||||
@ -720,11 +739,47 @@ class ShouldNotAbortOnUncaughtScope {
|
|||||||
Environment* env_;
|
Environment* env_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Environment {
|
class CleanupHookCallback {
|
||||||
|
public:
|
||||||
|
CleanupHookCallback(void (*fn)(void*),
|
||||||
|
void* arg,
|
||||||
|
uint64_t insertion_order_counter)
|
||||||
|
: fn_(fn), arg_(arg), insertion_order_counter_(insertion_order_counter) {}
|
||||||
|
|
||||||
|
// Only hashes `arg_`, since that is usually enough to identify the hook.
|
||||||
|
struct Hash {
|
||||||
|
inline size_t operator()(const CleanupHookCallback& cb) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compares by `fn_` and `arg_` being equal.
|
||||||
|
struct Equal {
|
||||||
|
inline bool operator()(const CleanupHookCallback& a,
|
||||||
|
const CleanupHookCallback& b) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline BaseObject* GetBaseObject() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Environment;
|
||||||
|
void (*fn_)(void*);
|
||||||
|
void* arg_;
|
||||||
|
|
||||||
|
// We keep track of the insertion order for these objects, so that we can
|
||||||
|
// call the callbacks in reverse order when we are cleaning up.
|
||||||
|
uint64_t insertion_order_counter_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Environment : public MemoryRetainer {
|
||||||
public:
|
public:
|
||||||
Environment(const Environment&) = delete;
|
Environment(const Environment&) = delete;
|
||||||
Environment& operator=(const Environment&) = delete;
|
Environment& operator=(const Environment&) = delete;
|
||||||
|
|
||||||
|
SET_MEMORY_INFO_NAME(Environment);
|
||||||
|
|
||||||
|
inline size_t SelfSize() const override;
|
||||||
|
bool IsRootNode() const override { return true; }
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
|
|
||||||
inline size_t async_callback_scope_depth() const;
|
inline size_t async_callback_scope_depth() const;
|
||||||
inline void PushAsyncCallbackScope();
|
inline void PushAsyncCallbackScope();
|
||||||
inline void PopAsyncCallbackScope();
|
inline void PopAsyncCallbackScope();
|
||||||
@ -994,6 +1049,7 @@ class Environment {
|
|||||||
#define V(PropertyName, TypeName) \
|
#define V(PropertyName, TypeName) \
|
||||||
inline v8::Local<TypeName> PropertyName() const; \
|
inline v8::Local<TypeName> PropertyName() const; \
|
||||||
inline void set_ ## PropertyName(v8::Local<TypeName> value);
|
inline void set_ ## PropertyName(v8::Local<TypeName> value);
|
||||||
|
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
|
||||||
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
|
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
|
||||||
#undef V
|
#undef V
|
||||||
|
|
||||||
@ -1182,28 +1238,6 @@ class Environment {
|
|||||||
void RunAndClearNativeImmediates();
|
void RunAndClearNativeImmediates();
|
||||||
static void CheckImmediate(uv_check_t* handle);
|
static void CheckImmediate(uv_check_t* handle);
|
||||||
|
|
||||||
struct CleanupHookCallback {
|
|
||||||
void (*fn_)(void*);
|
|
||||||
void* arg_;
|
|
||||||
|
|
||||||
// We keep track of the insertion order for these objects, so that we can
|
|
||||||
// call the callbacks in reverse order when we are cleaning up.
|
|
||||||
uint64_t insertion_order_counter_;
|
|
||||||
|
|
||||||
// Only hashes `arg_`, since that is usually enough to identify the hook.
|
|
||||||
struct Hash {
|
|
||||||
inline size_t operator()(const CleanupHookCallback& cb) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compares by `fn_` and `arg_` being equal.
|
|
||||||
struct Equal {
|
|
||||||
inline bool operator()(const CleanupHookCallback& a,
|
|
||||||
const CleanupHookCallback& b) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline BaseObject* GetBaseObject() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use an unordered_set, so that we have efficient insertion and removal.
|
// Use an unordered_set, so that we have efficient insertion and removal.
|
||||||
std::unordered_set<CleanupHookCallback,
|
std::unordered_set<CleanupHookCallback,
|
||||||
CleanupHookCallback::Hash,
|
CleanupHookCallback::Hash,
|
||||||
@ -1219,6 +1253,7 @@ class Environment {
|
|||||||
void ForEachBaseObject(T&& iterator);
|
void ForEachBaseObject(T&& iterator);
|
||||||
|
|
||||||
#define V(PropertyName, TypeName) Persistent<TypeName> PropertyName ## _;
|
#define V(PropertyName, TypeName) Persistent<TypeName> PropertyName ## _;
|
||||||
|
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
|
||||||
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
|
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
|
||||||
#undef V
|
#undef V
|
||||||
};
|
};
|
||||||
|
@ -177,6 +177,13 @@ void MemoryTracker::TrackField(const char* edge_name,
|
|||||||
TrackFieldWithSize(edge_name, value.size() * sizeof(T), "std::basic_string");
|
TrackFieldWithSize(edge_name, value.size() * sizeof(T), "std::basic_string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void MemoryTracker::TrackField(const char* edge_name,
|
||||||
|
const v8::Eternal<T>& value,
|
||||||
|
const char* node_name) {
|
||||||
|
TrackField(edge_name, value.Get(isolate_));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename Traits>
|
template <typename T, typename Traits>
|
||||||
void MemoryTracker::TrackField(const char* edge_name,
|
void MemoryTracker::TrackField(const char* edge_name,
|
||||||
const v8::Persistent<T, Traits>& value,
|
const v8::Persistent<T, Traits>& value,
|
||||||
|
@ -35,6 +35,8 @@ namespace crypto {
|
|||||||
class NodeBIO;
|
class NodeBIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CleanupHookCallback;
|
||||||
|
|
||||||
/* Example:
|
/* Example:
|
||||||
*
|
*
|
||||||
* class ExampleRetainer : public MemoryRetainer {
|
* class ExampleRetainer : public MemoryRetainer {
|
||||||
@ -179,6 +181,10 @@ class MemoryTracker {
|
|||||||
inline void TrackField(const char* edge_name,
|
inline void TrackField(const char* edge_name,
|
||||||
const T& value,
|
const T& value,
|
||||||
const char* node_name = nullptr);
|
const char* node_name = nullptr);
|
||||||
|
template <typename T>
|
||||||
|
void TrackField(const char* edge_name,
|
||||||
|
const v8::Eternal<T>& value,
|
||||||
|
const char* node_name);
|
||||||
template <typename T, typename Traits>
|
template <typename T, typename Traits>
|
||||||
inline void TrackField(const char* edge_name,
|
inline void TrackField(const char* edge_name,
|
||||||
const v8::Persistent<T, Traits>& value,
|
const v8::Persistent<T, Traits>& value,
|
||||||
@ -191,6 +197,13 @@ class MemoryTracker {
|
|||||||
inline void TrackField(const char* edge_name,
|
inline void TrackField(const char* edge_name,
|
||||||
const MallocedBuffer<T>& value,
|
const MallocedBuffer<T>& value,
|
||||||
const char* node_name = nullptr);
|
const char* node_name = nullptr);
|
||||||
|
// We do not implement CleanupHookCallback as MemoryRetainer
|
||||||
|
// but instead specialize the method here to avoid the cost of
|
||||||
|
// virtual pointers.
|
||||||
|
// TODO(joyeecheung): do this for BaseObject and remove WrappedObject()
|
||||||
|
void TrackField(const char* edge_name,
|
||||||
|
const CleanupHookCallback& value,
|
||||||
|
const char* node_name = nullptr);
|
||||||
inline void TrackField(const char* edge_name,
|
inline void TrackField(const char* edge_name,
|
||||||
const uv_buf_t& value,
|
const uv_buf_t& value,
|
||||||
const char* node_name = nullptr);
|
const char* node_name = nullptr);
|
||||||
|
66
test/pummel/test-heapdump-env.js
Normal file
66
test/pummel/test-heapdump-env.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Flags: --expose-internals
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// This tests that Environment is tracked in heap snapshots.
|
||||||
|
|
||||||
|
require('../common');
|
||||||
|
const { validateSnapshotNodes } = require('../common/heap');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
// This is just using ContextifyScript as an example here, it can be replaced
|
||||||
|
// with any BaseObject that we can easily instantiate here and register in
|
||||||
|
// cleanup hooks.
|
||||||
|
// These can all be changed to reflect the status of how these objects
|
||||||
|
// are captured in the snapshot.
|
||||||
|
const context = require('vm').createScript('const foo = 123');
|
||||||
|
|
||||||
|
validateSnapshotNodes('Node / Environment', [{
|
||||||
|
children: [
|
||||||
|
cleanupHooksFilter,
|
||||||
|
{ node_name: 'Node / cleanup_hooks', edge_name: 'cleanup_hooks' },
|
||||||
|
{ node_name: 'process', edge_name: 'process_object' },
|
||||||
|
{ node_name: 'Node / IsolateData', edge_name: 'isolate_data' },
|
||||||
|
]
|
||||||
|
}]);
|
||||||
|
|
||||||
|
function cleanupHooksFilter(edge) {
|
||||||
|
if (edge.name !== 'cleanup_hooks') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (edge.to.type === 'native') {
|
||||||
|
verifyCleanupHooksInSnapshot(edge.to);
|
||||||
|
} else {
|
||||||
|
verifyCleanupHooksInGraph(edge.to);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyCleanupHooksInSnapshot(node) {
|
||||||
|
assert.strictEqual(node.name, 'Node / cleanup_hooks');
|
||||||
|
const baseObjects = [];
|
||||||
|
for (const hook of node.outgoingEdges) {
|
||||||
|
for (const hookEdge of hook.to.outgoingEdges) {
|
||||||
|
if (hookEdge.name === 'arg') {
|
||||||
|
baseObjects.push(hookEdge.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure our ContextifyScript show up.
|
||||||
|
assert(baseObjects.some((node) => node.name === 'Node / ContextifyScript'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyCleanupHooksInGraph(node) {
|
||||||
|
assert.strictEqual(node.name, 'Node / cleanup_hooks');
|
||||||
|
const baseObjects = [];
|
||||||
|
for (const hook of node.edges) {
|
||||||
|
for (const hookEdge of hook.to.edges) {
|
||||||
|
if (hookEdge.name === 'arg') {
|
||||||
|
baseObjects.push(hookEdge.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure our ContextifyScript show up.
|
||||||
|
assert(baseObjects.some((node) => node.name === 'Node / ContextifyScript'));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(context); // Make sure it's not GC'ed
|
Loading…
x
Reference in New Issue
Block a user