Skip to content

Commit f59ec2a

Browse files
committedApr 4, 2019
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: #27018 Refs: #26776 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent ceb80f4 commit f59ec2a

File tree

7 files changed

+292
-66
lines changed

7 files changed

+292
-66
lines changed
 

‎src/base_object.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ class BaseObject : public MemoryRetainer {
8686

8787
private:
8888
v8::Local<v8::Object> WrappedObject() const override;
89-
bool IsRootNode() const override;
9089
static void DeleteMe(void* data);
9190

9291
// persistent_handle_ needs to be at a fixed offset from the start of the
@@ -95,7 +94,7 @@ class BaseObject : public MemoryRetainer {
9594
// position of members in memory are predictable. For more information please
9695
// refer to `doc/guides/node-postmortem-support.md`
9796
friend int GenDebugSymbols();
98-
friend class Environment;
97+
friend class CleanupHookCallback;
9998

10099
Persistent<v8::Object> persistent_handle_;
101100
Environment* env_;

‎src/env-inl.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -980,17 +980,17 @@ void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
980980
cleanup_hooks_.erase(search);
981981
}
982982

983-
size_t Environment::CleanupHookCallback::Hash::operator()(
983+
size_t CleanupHookCallback::Hash::operator()(
984984
const CleanupHookCallback& cb) const {
985985
return std::hash<void*>()(cb.arg_);
986986
}
987987

988-
bool Environment::CleanupHookCallback::Equal::operator()(
988+
bool CleanupHookCallback::Equal::operator()(
989989
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
990990
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
991991
}
992992

993-
BaseObject* Environment::CleanupHookCallback::GetBaseObject() const {
993+
BaseObject* CleanupHookCallback::GetBaseObject() const {
994994
if (fn_ == BaseObject::DeleteMe)
995995
return static_cast<BaseObject*>(arg_);
996996
else
@@ -1054,6 +1054,7 @@ void AsyncRequest::set_stopped(bool flag) {
10541054
PropertyName ## _.Reset(isolate(), value); \
10551055
}
10561056
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
1057+
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
10571058
#undef V
10581059

10591060
} // namespace node

‎src/env.cc

+112-7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ IsolateData::IsolateData(Isolate* isolate,
112112
#undef V
113113
}
114114

115+
void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
116+
#define V(PropertyName, StringValue) \
117+
tracker->TrackField(#PropertyName, PropertyName(isolate()));
118+
PER_ISOLATE_SYMBOL_PROPERTIES(V)
119+
#undef V
120+
121+
#define V(PropertyName, StringValue) \
122+
tracker->TrackField(#PropertyName, PropertyName(isolate()));
123+
PER_ISOLATE_STRING_PROPERTIES(V)
124+
#undef V
125+
126+
if (node_allocator_ != nullptr) {
127+
tracker->TrackFieldWithSize(
128+
"node_allocator", sizeof(*node_allocator_), "NodeArrayBufferAllocator");
129+
} else {
130+
tracker->TrackFieldWithSize(
131+
"allocator", sizeof(*allocator_), "v8::ArrayBuffer::Allocator");
132+
}
133+
tracker->TrackFieldWithSize(
134+
"platform", sizeof(*platform_), "MultiIsolatePlatform");
135+
// TODO(joyeecheung): implement MemoryRetainer in the option classes.
136+
}
137+
115138
void InitThreadLocalOnce() {
116139
CHECK_EQ(0, uv_key_create(&Environment::thread_local_env));
117140
}
@@ -707,6 +730,7 @@ void Environment::set_debug_categories(const std::string& cats, bool enabled) {
707730
}
708731

709732
DEBUG_CATEGORY_NAMES(V)
733+
#undef V
710734

711735
if (comma_pos == std::string::npos)
712736
break;
@@ -775,6 +799,21 @@ void Environment::CollectUVExceptionInfo(Local<Value> object,
775799
syscall, message, path, dest);
776800
}
777801

802+
void ImmediateInfo::MemoryInfo(MemoryTracker* tracker) const {
803+
tracker->TrackField("fields", fields_);
804+
}
805+
806+
void TickInfo::MemoryInfo(MemoryTracker* tracker) const {
807+
tracker->TrackField("fields", fields_);
808+
}
809+
810+
void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const {
811+
tracker->TrackField("providers", providers_);
812+
tracker->TrackField("async_ids_stack", async_ids_stack_);
813+
tracker->TrackField("fields", fields_);
814+
tracker->TrackField("async_id_fields", async_id_fields_);
815+
}
816+
778817
void AsyncHooks::grow_async_ids_stack() {
779818
async_ids_stack_.reserve(async_ids_stack_.Length() * 3);
780819

@@ -805,13 +844,83 @@ void Environment::stop_sub_worker_contexts() {
805844
}
806845
}
807846

847+
void MemoryTracker::TrackField(const char* edge_name,
848+
const CleanupHookCallback& value,
849+
const char* node_name) {
850+
v8::HandleScope handle_scope(isolate_);
851+
// Here, we utilize the fact that CleanupHookCallback instances
852+
// are all unique and won't be tracked twice in one BuildEmbedderGraph
853+
// callback.
854+
MemoryRetainerNode* n =
855+
PushNode("CleanupHookCallback", sizeof(value), edge_name);
856+
// TODO(joyeecheung): at the moment only arguments of type BaseObject will be
857+
// identified and tracked here (based on their deleters),
858+
// but we may convert and track other known types here.
859+
BaseObject* obj = value.GetBaseObject();
860+
if (obj != nullptr) {
861+
this->TrackField("arg", obj);
862+
}
863+
CHECK_EQ(CurrentNode(), n);
864+
CHECK_NE(n->size_, 0);
865+
PopNode();
866+
}
867+
808868
void Environment::BuildEmbedderGraph(Isolate* isolate,
809869
EmbedderGraph* graph,
810870
void* data) {
811871
MemoryTracker tracker(isolate, graph);
812-
static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
813-
tracker.Track(obj);
814-
});
872+
Environment* env = static_cast<Environment*>(data);
873+
tracker.Track(env);
874+
}
875+
876+
inline size_t Environment::SelfSize() const {
877+
size_t size = sizeof(*this);
878+
// Remove non pointer fields that will be tracked in MemoryInfo()
879+
// TODO(joyeecheung): refactor the MemoryTracker interface so
880+
// this can be done for common types within the Track* calls automatically
881+
// if a certain scope is entered.
882+
size -= sizeof(thread_stopper_);
883+
size -= sizeof(async_hooks_);
884+
size -= sizeof(tick_info_);
885+
size -= sizeof(immediate_info_);
886+
return size;
887+
}
888+
889+
void Environment::MemoryInfo(MemoryTracker* tracker) const {
890+
// Iteratable STLs have their own sizes subtracted from the parent
891+
// by default.
892+
tracker->TrackField("isolate_data", isolate_data_);
893+
tracker->TrackField("native_modules_with_cache", native_modules_with_cache);
894+
tracker->TrackField("native_modules_without_cache",
895+
native_modules_without_cache);
896+
tracker->TrackField("destroy_async_id_list", destroy_async_id_list_);
897+
tracker->TrackField("exec_argv", exec_argv_);
898+
tracker->TrackField("should_abort_on_uncaught_toggle",
899+
should_abort_on_uncaught_toggle_);
900+
tracker->TrackField("stream_base_state", stream_base_state_);
901+
tracker->TrackField("fs_stats_field_array", fs_stats_field_array_);
902+
tracker->TrackField("fs_stats_field_bigint_array",
903+
fs_stats_field_bigint_array_);
904+
tracker->TrackField("thread_stopper", thread_stopper_);
905+
tracker->TrackField("cleanup_hooks", cleanup_hooks_);
906+
tracker->TrackField("async_hooks", async_hooks_);
907+
tracker->TrackField("immediate_info", immediate_info_);
908+
tracker->TrackField("tick_info", tick_info_);
909+
910+
#define V(PropertyName, TypeName) \
911+
tracker->TrackField(#PropertyName, PropertyName());
912+
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
913+
#undef V
914+
915+
// FIXME(joyeecheung): track other fields in Environment.
916+
// Currently MemoryTracker is unable to track these
917+
// correctly:
918+
// - Internal types that do not implement MemoryRetainer yet
919+
// - STL containers with MemoryRetainer* inside
920+
// - STL containers with numeric types inside that should not have their
921+
// nodes elided e.g. numeric keys in maps.
922+
// We also need to make sure that when we add a non-pointer field as its own
923+
// node, we shift its sizeof() size out of the Environment node.
815924
}
816925

817926
char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
@@ -875,8 +984,4 @@ Local<Object> BaseObject::WrappedObject() const {
875984
return object();
876985
}
877986

878-
bool BaseObject::IsRootNode() const {
879-
return !persistent_handle_.IsWeak();
880-
}
881-
882987
} // namespace node

‎src/env.h

+89-54
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "uv.h"
3939
#include "v8.h"
4040

41+
#include <array>
4142
#include <atomic>
4243
#include <cstdint>
4344
#include <functional>
@@ -330,31 +331,48 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
330331
V(zero_return_string, "ZERO_RETURN")
331332

332333
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
333-
V(as_callback_data, v8::Object) \
334334
V(as_callback_data_template, v8::FunctionTemplate) \
335+
V(async_wrap_ctor_template, v8::FunctionTemplate) \
336+
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
337+
V(context, v8::Context) \
338+
V(fd_constructor_template, v8::ObjectTemplate) \
339+
V(fdclose_constructor_template, v8::ObjectTemplate) \
340+
V(filehandlereadwrap_template, v8::ObjectTemplate) \
341+
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
342+
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
343+
V(http2settings_constructor_template, v8::ObjectTemplate) \
344+
V(http2stream_constructor_template, v8::ObjectTemplate) \
345+
V(http2ping_constructor_template, v8::ObjectTemplate) \
346+
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
347+
V(message_event_object_template, v8::ObjectTemplate) \
348+
V(message_port_constructor_template, v8::FunctionTemplate) \
349+
V(pipe_constructor_template, v8::FunctionTemplate) \
350+
V(promise_wrap_template, v8::ObjectTemplate) \
351+
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
352+
V(script_context_constructor_template, v8::FunctionTemplate) \
353+
V(secure_context_constructor_template, v8::FunctionTemplate) \
354+
V(shutdown_wrap_template, v8::ObjectTemplate) \
355+
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
356+
V(tcp_constructor_template, v8::FunctionTemplate) \
357+
V(tty_constructor_template, v8::FunctionTemplate) \
358+
V(write_wrap_template, v8::ObjectTemplate)
359+
360+
#define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \
361+
V(as_callback_data, v8::Object) \
335362
V(async_hooks_after_function, v8::Function) \
336363
V(async_hooks_before_function, v8::Function) \
337364
V(async_hooks_binding, v8::Object) \
338365
V(async_hooks_destroy_function, v8::Function) \
339366
V(async_hooks_init_function, v8::Function) \
340367
V(async_hooks_promise_resolve_function, v8::Function) \
341-
V(async_wrap_ctor_template, v8::FunctionTemplate) \
342-
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
343368
V(buffer_prototype_object, v8::Object) \
344369
V(coverage_connection, v8::Object) \
345-
V(context, v8::Context) \
346370
V(crypto_key_object_constructor, v8::Function) \
347371
V(domain_callback, v8::Function) \
348372
V(domexception_function, v8::Function) \
349-
V(fd_constructor_template, v8::ObjectTemplate) \
350-
V(fdclose_constructor_template, v8::ObjectTemplate) \
351-
V(filehandlereadwrap_template, v8::ObjectTemplate) \
352373
V(fs_use_promises_symbol, v8::Symbol) \
353-
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
354-
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
355374
V(host_import_module_dynamically_callback, v8::Function) \
356375
V(host_initialize_import_meta_object_callback, v8::Function) \
357-
V(http2ping_constructor_template, v8::ObjectTemplate) \
358376
V(http2session_on_altsvc_function, v8::Function) \
359377
V(http2session_on_error_function, v8::Function) \
360378
V(http2session_on_frame_error_function, v8::Function) \
@@ -367,48 +385,37 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
367385
V(http2session_on_settings_function, v8::Function) \
368386
V(http2session_on_stream_close_function, v8::Function) \
369387
V(http2session_on_stream_trailers_function, v8::Function) \
370-
V(http2settings_constructor_template, v8::ObjectTemplate) \
371-
V(http2stream_constructor_template, v8::ObjectTemplate) \
372388
V(internal_binding_loader, v8::Function) \
373389
V(immediate_callback_function, v8::Function) \
374390
V(inspector_console_extension_installer, v8::Function) \
375-
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
376391
V(message_port, v8::Object) \
377-
V(message_event_object_template, v8::ObjectTemplate) \
378-
V(message_port_constructor_template, v8::FunctionTemplate) \
379392
V(native_module_require, v8::Function) \
380393
V(on_coverage_message_function, v8::Function) \
381394
V(performance_entry_callback, v8::Function) \
382395
V(performance_entry_template, v8::Function) \
383-
V(pipe_constructor_template, v8::FunctionTemplate) \
384396
V(process_object, v8::Object) \
385397
V(primordials, v8::Object) \
386398
V(promise_reject_callback, v8::Function) \
387-
V(promise_wrap_template, v8::ObjectTemplate) \
388-
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
389-
V(script_context_constructor_template, v8::FunctionTemplate) \
390399
V(script_data_constructor_function, v8::Function) \
391-
V(secure_context_constructor_template, v8::FunctionTemplate) \
392-
V(shutdown_wrap_template, v8::ObjectTemplate) \
393-
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
394-
V(tcp_constructor_template, v8::FunctionTemplate) \
395400
V(tick_callback_function, v8::Function) \
396401
V(timers_callback_function, v8::Function) \
397402
V(tls_wrap_constructor_function, v8::Function) \
398403
V(trace_category_state_function, v8::Function) \
399-
V(tty_constructor_template, v8::FunctionTemplate) \
400404
V(udp_constructor_function, v8::Function) \
401-
V(url_constructor_function, v8::Function) \
402-
V(write_wrap_template, v8::ObjectTemplate)
405+
V(url_constructor_function, v8::Function)
403406

404407
class Environment;
405408

406-
class IsolateData {
409+
class IsolateData : public MemoryRetainer {
407410
public:
408411
IsolateData(v8::Isolate* isolate,
409412
uv_loop_t* event_loop,
410413
MultiIsolatePlatform* platform = nullptr,
411414
ArrayBufferAllocator* node_allocator = nullptr);
415+
SET_MEMORY_INFO_NAME(IsolateData);
416+
SET_SELF_SIZE(IsolateData);
417+
void MemoryInfo(MemoryTracker* tracker) const override;
418+
412419
inline uv_loop_t* event_loop() const;
413420
inline MultiIsolatePlatform* platform() const;
414421
inline std::shared_ptr<PerIsolateOptions> options();
@@ -563,8 +570,12 @@ namespace per_process {
563570
extern std::shared_ptr<KVStore> system_environment;
564571
}
565572

566-
class AsyncHooks {
573+
class AsyncHooks : public MemoryRetainer {
567574
public:
575+
SET_MEMORY_INFO_NAME(AsyncHooks);
576+
SET_SELF_SIZE(AsyncHooks);
577+
void MemoryInfo(MemoryTracker* tracker) const override;
578+
568579
// Reason for both UidFields and Fields are that one is stored as a double*
569580
// and the other as a uint32_t*.
570581
enum Fields {
@@ -626,7 +637,7 @@ class AsyncHooks {
626637
friend class Environment; // So we can call the constructor.
627638
inline AsyncHooks();
628639
// Keep a list of all Persistent strings used for Provider types.
629-
v8::Eternal<v8::String> providers_[AsyncWrap::PROVIDERS_LENGTH];
640+
std::array<v8::Eternal<v8::String>, AsyncWrap::PROVIDERS_LENGTH> providers_;
630641
// Stores the ids of the current execution context stack.
631642
AliasedBuffer<double, v8::Float64Array> async_ids_stack_;
632643
// Attached to a Uint32Array that tracks the number of active hooks for
@@ -650,7 +661,7 @@ class AsyncCallbackScope {
650661
Environment* env_;
651662
};
652663

653-
class ImmediateInfo {
664+
class ImmediateInfo : public MemoryRetainer {
654665
public:
655666
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
656667
inline uint32_t count() const;
@@ -664,6 +675,10 @@ class ImmediateInfo {
664675
ImmediateInfo(const ImmediateInfo&) = delete;
665676
ImmediateInfo& operator=(const ImmediateInfo&) = delete;
666677

678+
SET_MEMORY_INFO_NAME(ImmediateInfo);
679+
SET_SELF_SIZE(ImmediateInfo);
680+
void MemoryInfo(MemoryTracker* tracker) const override;
681+
667682
private:
668683
friend class Environment; // So we can call the constructor.
669684
inline explicit ImmediateInfo(v8::Isolate* isolate);
@@ -673,12 +688,16 @@ class ImmediateInfo {
673688
AliasedBuffer<uint32_t, v8::Uint32Array> fields_;
674689
};
675690

676-
class TickInfo {
691+
class TickInfo : public MemoryRetainer {
677692
public:
678693
inline AliasedBuffer<uint8_t, v8::Uint8Array>& fields();
679694
inline bool has_tick_scheduled() const;
680695
inline bool has_rejection_to_warn() const;
681696

697+
SET_MEMORY_INFO_NAME(TickInfo);
698+
SET_SELF_SIZE(TickInfo);
699+
void MemoryInfo(MemoryTracker* tracker) const override;
700+
682701
TickInfo(const TickInfo&) = delete;
683702
TickInfo& operator=(const TickInfo&) = delete;
684703

@@ -720,11 +739,47 @@ class ShouldNotAbortOnUncaughtScope {
720739
Environment* env_;
721740
};
722741

723-
class Environment {
742+
class CleanupHookCallback {
743+
public:
744+
CleanupHookCallback(void (*fn)(void*),
745+
void* arg,
746+
uint64_t insertion_order_counter)
747+
: fn_(fn), arg_(arg), insertion_order_counter_(insertion_order_counter) {}
748+
749+
// Only hashes `arg_`, since that is usually enough to identify the hook.
750+
struct Hash {
751+
inline size_t operator()(const CleanupHookCallback& cb) const;
752+
};
753+
754+
// Compares by `fn_` and `arg_` being equal.
755+
struct Equal {
756+
inline bool operator()(const CleanupHookCallback& a,
757+
const CleanupHookCallback& b) const;
758+
};
759+
760+
inline BaseObject* GetBaseObject() const;
761+
762+
private:
763+
friend class Environment;
764+
void (*fn_)(void*);
765+
void* arg_;
766+
767+
// We keep track of the insertion order for these objects, so that we can
768+
// call the callbacks in reverse order when we are cleaning up.
769+
uint64_t insertion_order_counter_;
770+
};
771+
772+
class Environment : public MemoryRetainer {
724773
public:
725774
Environment(const Environment&) = delete;
726775
Environment& operator=(const Environment&) = delete;
727776

777+
SET_MEMORY_INFO_NAME(Environment);
778+
779+
inline size_t SelfSize() const override;
780+
bool IsRootNode() const override { return true; }
781+
void MemoryInfo(MemoryTracker* tracker) const override;
782+
728783
inline size_t async_callback_scope_depth() const;
729784
inline void PushAsyncCallbackScope();
730785
inline void PopAsyncCallbackScope();
@@ -994,6 +1049,7 @@ class Environment {
9941049
#define V(PropertyName, TypeName) \
9951050
inline v8::Local<TypeName> PropertyName() const; \
9961051
inline void set_ ## PropertyName(v8::Local<TypeName> value);
1052+
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
9971053
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
9981054
#undef V
9991055

@@ -1182,28 +1238,6 @@ class Environment {
11821238
void RunAndClearNativeImmediates();
11831239
static void CheckImmediate(uv_check_t* handle);
11841240

1185-
struct CleanupHookCallback {
1186-
void (*fn_)(void*);
1187-
void* arg_;
1188-
1189-
// We keep track of the insertion order for these objects, so that we can
1190-
// call the callbacks in reverse order when we are cleaning up.
1191-
uint64_t insertion_order_counter_;
1192-
1193-
// Only hashes `arg_`, since that is usually enough to identify the hook.
1194-
struct Hash {
1195-
inline size_t operator()(const CleanupHookCallback& cb) const;
1196-
};
1197-
1198-
// Compares by `fn_` and `arg_` being equal.
1199-
struct Equal {
1200-
inline bool operator()(const CleanupHookCallback& a,
1201-
const CleanupHookCallback& b) const;
1202-
};
1203-
1204-
inline BaseObject* GetBaseObject() const;
1205-
};
1206-
12071241
// Use an unordered_set, so that we have efficient insertion and removal.
12081242
std::unordered_set<CleanupHookCallback,
12091243
CleanupHookCallback::Hash,
@@ -1219,6 +1253,7 @@ class Environment {
12191253
void ForEachBaseObject(T&& iterator);
12201254

12211255
#define V(PropertyName, TypeName) Persistent<TypeName> PropertyName ## _;
1256+
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
12221257
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
12231258
#undef V
12241259
};

‎src/memory_tracker-inl.h

+7
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ void MemoryTracker::TrackField(const char* edge_name,
177177
TrackFieldWithSize(edge_name, value.size() * sizeof(T), "std::basic_string");
178178
}
179179

180+
template <typename T>
181+
void MemoryTracker::TrackField(const char* edge_name,
182+
const v8::Eternal<T>& value,
183+
const char* node_name) {
184+
TrackField(edge_name, value.Get(isolate_));
185+
}
186+
180187
template <typename T, typename Traits>
181188
void MemoryTracker::TrackField(const char* edge_name,
182189
const v8::Persistent<T, Traits>& value,

‎src/memory_tracker.h

+13
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ namespace crypto {
3535
class NodeBIO;
3636
}
3737

38+
class CleanupHookCallback;
39+
3840
/* Example:
3941
*
4042
* class ExampleRetainer : public MemoryRetainer {
@@ -179,6 +181,10 @@ class MemoryTracker {
179181
inline void TrackField(const char* edge_name,
180182
const T& value,
181183
const char* node_name = nullptr);
184+
template <typename T>
185+
void TrackField(const char* edge_name,
186+
const v8::Eternal<T>& value,
187+
const char* node_name);
182188
template <typename T, typename Traits>
183189
inline void TrackField(const char* edge_name,
184190
const v8::Persistent<T, Traits>& value,
@@ -191,6 +197,13 @@ class MemoryTracker {
191197
inline void TrackField(const char* edge_name,
192198
const MallocedBuffer<T>& value,
193199
const char* node_name = nullptr);
200+
// We do not implement CleanupHookCallback as MemoryRetainer
201+
// but instead specialize the method here to avoid the cost of
202+
// virtual pointers.
203+
// TODO(joyeecheung): do this for BaseObject and remove WrappedObject()
204+
void TrackField(const char* edge_name,
205+
const CleanupHookCallback& value,
206+
const char* node_name = nullptr);
194207
inline void TrackField(const char* edge_name,
195208
const uv_buf_t& value,
196209
const char* node_name = nullptr);

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

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
// This tests that Environment is tracked in heap snapshots.
5+
6+
require('../common');
7+
const { validateSnapshotNodes } = require('../common/heap');
8+
const assert = require('assert');
9+
10+
// This is just using ContextifyScript as an example here, it can be replaced
11+
// with any BaseObject that we can easily instantiate here and register in
12+
// cleanup hooks.
13+
// These can all be changed to reflect the status of how these objects
14+
// are captured in the snapshot.
15+
const context = require('vm').createScript('const foo = 123');
16+
17+
validateSnapshotNodes('Node / Environment', [{
18+
children: [
19+
cleanupHooksFilter,
20+
{ node_name: 'Node / cleanup_hooks', edge_name: 'cleanup_hooks' },
21+
{ node_name: 'process', edge_name: 'process_object' },
22+
{ node_name: 'Node / IsolateData', edge_name: 'isolate_data' },
23+
]
24+
}]);
25+
26+
function cleanupHooksFilter(edge) {
27+
if (edge.name !== 'cleanup_hooks') {
28+
return false;
29+
}
30+
if (edge.to.type === 'native') {
31+
verifyCleanupHooksInSnapshot(edge.to);
32+
} else {
33+
verifyCleanupHooksInGraph(edge.to);
34+
}
35+
return true;
36+
}
37+
38+
function verifyCleanupHooksInSnapshot(node) {
39+
assert.strictEqual(node.name, 'Node / cleanup_hooks');
40+
const baseObjects = [];
41+
for (const hook of node.outgoingEdges) {
42+
for (const hookEdge of hook.to.outgoingEdges) {
43+
if (hookEdge.name === 'arg') {
44+
baseObjects.push(hookEdge.to);
45+
}
46+
}
47+
}
48+
// Make sure our ContextifyScript show up.
49+
assert(baseObjects.some((node) => node.name === 'Node / ContextifyScript'));
50+
}
51+
52+
function verifyCleanupHooksInGraph(node) {
53+
assert.strictEqual(node.name, 'Node / cleanup_hooks');
54+
const baseObjects = [];
55+
for (const hook of node.edges) {
56+
for (const hookEdge of hook.to.edges) {
57+
if (hookEdge.name === 'arg') {
58+
baseObjects.push(hookEdge.to);
59+
}
60+
}
61+
}
62+
// Make sure our ContextifyScript show up.
63+
assert(baseObjects.some((node) => node.name === 'Node / ContextifyScript'));
64+
}
65+
66+
console.log(context); // Make sure it's not GC'ed

0 commit comments

Comments
 (0)
Please sign in to comment.