diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2019-03-27 15:46:03 -0400 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2019-04-04 11:09:29 +0800 |
commit | f59ec2abee82f22822b7b3231ca2056fc028a279 (patch) | |
tree | 3be1fa8fa8b2be425ecc29254b9e8fcb1df111af | |
parent | ceb80f415798818a059051537132bba691c68db7 (diff) | |
download | node-new-f59ec2abee82f22822b7b3231ca2056fc028a279.tar.gz |
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>
-rw-r--r-- | src/base_object.h | 3 | ||||
-rw-r--r-- | src/env-inl.h | 7 | ||||
-rw-r--r-- | src/env.cc | 119 | ||||
-rw-r--r-- | src/env.h | 143 | ||||
-rw-r--r-- | src/memory_tracker-inl.h | 7 | ||||
-rw-r--r-- | src/memory_tracker.h | 13 | ||||
-rw-r--r-- | test/pummel/test-heapdump-env.js | 66 |
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 @@ -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 |