mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
src: do not track BaseObjects via cleanup hooks
Sincef59ec2abee, `BaseObject` instances were tracked in heap snapshots through their associated `CleanupHookCallback`s which were stored on the `Environment`; however, this is inaccurate, because: - Edges in heap dumps imply a keeps-alive relationship, but cleanup hooks do not keep the `BaseObject`s that they point to alive. - It loses information about whether `BaseObject` instances are GC roots: Even weak `BaseObject`s are now, practically speaking, showing up as hanging off a GC root when that isn’t actually the case (e.g. in the description of nodejs/node#33468). Thus, this is a partial revert off59ec2abee. Refs: https://github.com/nodejs/node/issues/33468 Refs: https://github.com/nodejs/node/pull/27018 PR-URL: https://github.com/nodejs/node/pull/33809 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
@@ -103,6 +103,7 @@ 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
|
||||
|
||||
32
src/env.cc
32
src/env.cc
@@ -1030,33 +1030,16 @@ Environment* Environment::worker_parent_env() const {
|
||||
return worker_context()->env();
|
||||
}
|
||||
|
||||
void MemoryTracker::TrackField(const char* edge_name,
|
||||
const CleanupHookCallback& value,
|
||||
const char* node_name) {
|
||||
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 && obj->IsDoneInitializing()) {
|
||||
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);
|
||||
Environment* env = static_cast<Environment*>(data);
|
||||
tracker.Track(env);
|
||||
env->ForEachBaseObject([&](BaseObject* obj) {
|
||||
if (obj->IsDoneInitializing())
|
||||
tracker.Track(obj);
|
||||
});
|
||||
}
|
||||
|
||||
inline size_t Environment::SelfSize() const {
|
||||
@@ -1083,7 +1066,8 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackField("should_abort_on_uncaught_toggle",
|
||||
should_abort_on_uncaught_toggle_);
|
||||
tracker->TrackField("stream_base_state", stream_base_state_);
|
||||
tracker->TrackField("cleanup_hooks", cleanup_hooks_);
|
||||
tracker->TrackFieldWithSize(
|
||||
"cleanup_hooks", cleanup_hooks_.size() * sizeof(CleanupHookCallback));
|
||||
tracker->TrackField("async_hooks", async_hooks_);
|
||||
tracker->TrackField("immediate_info", immediate_info_);
|
||||
tracker->TrackField("tick_info", tick_info_);
|
||||
@@ -1124,4 +1108,8 @@ Local<Object> BaseObject::WrappedObject() const {
|
||||
return object();
|
||||
}
|
||||
|
||||
bool BaseObject::IsRootNode() const {
|
||||
return !persistent_handle_.IsWeak();
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
|
||||
@@ -216,13 +216,6 @@ class MemoryTracker {
|
||||
inline void TrackField(const char* edge_name,
|
||||
const v8::BackingStore* 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);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
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
|
||||
@@ -16,51 +15,10 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user