summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/base_object.h3
-rw-r--r--src/env-inl.h7
-rw-r--r--src/env.cc119
-rw-r--r--src/env.h143
-rw-r--r--src/memory_tracker-inl.h7
-rw-r--r--src/memory_tracker.h13
-rw-r--r--test/pummel/test-heapdump-env.js66
7 files changed, 292 insertions, 66 deletions
diff --git a/src/base_object.h b/src/base_object.h
index 091c3d5af0..f1c666224f 100644
--- a/src/base_object.h
+++ b/src/base_object.h
@@ -86,7 +86,6 @@ class BaseObject : public MemoryRetainer {
private:
v8::Local<v8::Object> WrappedObject() const override;
- bool IsRootNode() const override;
static void DeleteMe(void* data);
// 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
// refer to `doc/guides/node-postmortem-support.md`
friend int GenDebugSymbols();
- friend class Environment;
+ friend class CleanupHookCallback;
Persistent<v8::Object> persistent_handle_;
Environment* env_;
diff --git a/src/env-inl.h b/src/env-inl.h
index bce36c0f69..ef054be4cb 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -980,17 +980,17 @@ void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
cleanup_hooks_.erase(search);
}
-size_t Environment::CleanupHookCallback::Hash::operator()(
+size_t CleanupHookCallback::Hash::operator()(
const CleanupHookCallback& cb) const {
return std::hash<void*>()(cb.arg_);
}
-bool Environment::CleanupHookCallback::Equal::operator()(
+bool CleanupHookCallback::Equal::operator()(
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
}
-BaseObject* Environment::CleanupHookCallback::GetBaseObject() const {
+BaseObject* CleanupHookCallback::GetBaseObject() const {
if (fn_ == BaseObject::DeleteMe)
return static_cast<BaseObject*>(arg_);
else
@@ -1054,6 +1054,7 @@ void AsyncRequest::set_stopped(bool flag) {
PropertyName ## _.Reset(isolate(), value); \
}
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
+ ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
#undef V
} // namespace node
diff --git a/src/env.cc b/src/env.cc
index cc8554032e..1542638e77 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -112,6 +112,29 @@ IsolateData::IsolateData(Isolate* isolate,
#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() {
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)
+#undef V
if (comma_pos == std::string::npos)
break;
@@ -775,6 +799,21 @@ void Environment::CollectUVExceptionInfo(Local<Value> object,
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() {
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,
EmbedderGraph* graph,
void* data) {
MemoryTracker tracker(isolate, graph);
- static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
- tracker.Track(obj);
- });
+ Environment* env = static_cast<Environment*>(data);
+ 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) {
@@ -875,8 +984,4 @@ Local<Object> BaseObject::WrappedObject() const {
return object();
}
-bool BaseObject::IsRootNode() const {
- return !persistent_handle_.IsWeak();
-}
-
} // namespace node
diff --git a/src/env.h b/src/env.h
index 92b45ff563..9d28907212 100644
--- a/src/env.h
+++ b/src/env.h
@@ -38,6 +38,7 @@
#include "uv.h"
#include "v8.h"
+#include <array>
#include <atomic>
#include <cstdint>
#include <functional>
@@ -330,31 +331,48 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(zero_return_string, "ZERO_RETURN")
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
- V(as_callback_data, v8::Object) \
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_before_function, v8::Function) \
V(async_hooks_binding, v8::Object) \
V(async_hooks_destroy_function, v8::Function) \
V(async_hooks_init_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(coverage_connection, v8::Object) \
- V(context, v8::Context) \
V(crypto_key_object_constructor, v8::Function) \
V(domain_callback, 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(fsreqpromise_constructor_template, v8::ObjectTemplate) \
- V(handle_wrap_ctor_template, v8::FunctionTemplate) \
V(host_import_module_dynamically_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_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_stream_close_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(immediate_callback_function, v8::Function) \
V(inspector_console_extension_installer, v8::Function) \
- V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
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(on_coverage_message_function, v8::Function) \
V(performance_entry_callback, v8::Function) \
V(performance_entry_template, v8::Function) \
- V(pipe_constructor_template, v8::FunctionTemplate) \
V(process_object, v8::Object) \
V(primordials, v8::Object) \
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(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(timers_callback_function, v8::Function) \
V(tls_wrap_constructor_function, v8::Function) \
V(trace_category_state_function, v8::Function) \
- V(tty_constructor_template, v8::FunctionTemplate) \
V(udp_constructor_function, v8::Function) \
- V(url_constructor_function, v8::Function) \
- V(write_wrap_template, v8::ObjectTemplate)
+ V(url_constructor_function, v8::Function)
class Environment;
-class IsolateData {
+class IsolateData : public MemoryRetainer {
public:
IsolateData(v8::Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform = 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 MultiIsolatePlatform* platform() const;
inline std::shared_ptr<PerIsolateOptions> options();
@@ -563,8 +570,12 @@ namespace per_process {
extern std::shared_ptr<KVStore> system_environment;
}
-class AsyncHooks {
+class AsyncHooks : public MemoryRetainer {
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*
// and the other as a uint32_t*.
enum Fields {
@@ -626,7 +637,7 @@ class AsyncHooks {
friend class Environment; // So we can call the constructor.
inline AsyncHooks();
// 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.
AliasedBuffer<double, v8::Float64Array> async_ids_stack_;
// Attached to a Uint32Array that tracks the number of active hooks for
@@ -650,7 +661,7 @@ class AsyncCallbackScope {
Environment* env_;
};
-class ImmediateInfo {
+class ImmediateInfo : public MemoryRetainer {
public:
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
inline uint32_t count() const;
@@ -664,6 +675,10 @@ class ImmediateInfo {
ImmediateInfo(const ImmediateInfo&) = delete;
ImmediateInfo& operator=(const ImmediateInfo&) = delete;
+ SET_MEMORY_INFO_NAME(ImmediateInfo);
+ SET_SELF_SIZE(ImmediateInfo);
+ void MemoryInfo(MemoryTracker* tracker) const override;
+
private:
friend class Environment; // So we can call the constructor.
inline explicit ImmediateInfo(v8::Isolate* isolate);
@@ -673,12 +688,16 @@ class ImmediateInfo {
AliasedBuffer<uint32_t, v8::Uint32Array> fields_;
};
-class TickInfo {
+class TickInfo : public MemoryRetainer {
public:
inline AliasedBuffer<uint8_t, v8::Uint8Array>& fields();
inline bool has_tick_scheduled() 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& operator=(const TickInfo&) = delete;
@@ -720,11 +739,47 @@ class ShouldNotAbortOnUncaughtScope {
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:
Environment(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 void PushAsyncCallbackScope();
inline void PopAsyncCallbackScope();
@@ -994,6 +1049,7 @@ class Environment {
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> PropertyName() const; \
inline void set_ ## PropertyName(v8::Local<TypeName> value);
+ ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
@@ -1182,28 +1238,6 @@ class Environment {
void RunAndClearNativeImmediates();
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.
std::unordered_set<CleanupHookCallback,
CleanupHookCallback::Hash,
@@ -1219,6 +1253,7 @@ class Environment {
void ForEachBaseObject(T&& iterator);
#define V(PropertyName, TypeName) Persistent<TypeName> PropertyName ## _;
+ ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
};
diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h
index 95d623c2b4..72117d2f6a 100644
--- a/src/memory_tracker-inl.h
+++ b/src/memory_tracker-inl.h
@@ -177,6 +177,13 @@ void MemoryTracker::TrackField(const char* edge_name,
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>
void MemoryTracker::TrackField(const char* edge_name,
const v8::Persistent<T, Traits>& value,
diff --git a/src/memory_tracker.h b/src/memory_tracker.h
index c5d9b2106f..07b1b47216 100644
--- a/src/memory_tracker.h
+++ b/src/memory_tracker.h
@@ -35,6 +35,8 @@ namespace crypto {
class NodeBIO;
}
+class CleanupHookCallback;
+
/* Example:
*
* class ExampleRetainer : public MemoryRetainer {
@@ -179,6 +181,10 @@ class MemoryTracker {
inline void TrackField(const char* edge_name,
const T& value,
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>
inline void TrackField(const char* edge_name,
const v8::Persistent<T, Traits>& value,
@@ -191,6 +197,13 @@ class MemoryTracker {
inline void TrackField(const char* edge_name,
const MallocedBuffer<T>& value,
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,
const uv_buf_t& value,
const char* node_name = nullptr);
diff --git a/test/pummel/test-heapdump-env.js b/test/pummel/test-heapdump-env.js
new file mode 100644
index 0000000000..8175797cce
--- /dev/null
+++ b/test/pummel/test-heapdump-env.js
@@ -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