mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
perf_hooks: restore GC tracking after snapshot deserialization
V8 GC callbacks do not survive a snapshot, so a `'gc'` PerformanceObserver that was active while building a user-land snapshot received no entries after deserialization, unless another `'gc'` observer was created. When `'gc'` is observed while building a snapshot, add a deserialize callback that registers the GC callbacks again if `'gc'` observers are still active. Assisted-by: OpenCode Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66097 Reviewed-By: Xuguang Mei <meixuguang@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
1 parent
10559877f4
commit
a293ec7785
3 files changed
+59
-23
No files matched your search
@@ -149,6 +149,23 @@ function maybeDecrementObserverCounts(entryTypes) {
|
||||
}
|
||||
}
|
||||
|
||||
let gcTrackingDeserializeCallbackAdded = false;
|
||||
|
||||
// V8 GC callbacks do not survive a snapshot. When building one with active
|
||||
// 'gc' observers, register the callbacks again after deserialization.
|
||||
function maybeRestoreGarbageCollectionTrackingOnDeserialize() {
|
||||
if (gcTrackingDeserializeCallbackAdded) return;
|
||||
const {
|
||||
namespace: {
|
||||
addDeserializeCallback,
|
||||
isBuildingSnapshot,
|
||||
},
|
||||
} = require('internal/v8/startup_snapshot');
|
||||
if (!isBuildingSnapshot()) return;
|
||||
gcTrackingDeserializeCallbackAdded = true;
|
||||
addDeserializeCallback(updateGarbageCollectionTracking);
|
||||
}
|
||||
|
||||
function maybeIncrementObserverCount(type) {
|
||||
const observerType = getObserverType(type);
|
||||
|
||||
@@ -158,6 +175,7 @@ function maybeIncrementObserverCount(type) {
|
||||
// idempotent, so it is called whenever the 'gc' observer count changes.
|
||||
if (observerType === NODE_PERFORMANCE_ENTRY_TYPE_GC) {
|
||||
updateGarbageCollectionTracking();
|
||||
maybeRestoreGarbageCollectionTrackingOnDeserialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-13
@@ -4,7 +4,10 @@ const { PerformanceObserver } = require('node:perf_hooks');
|
||||
const { setDeserializeMainFunction } = require('node:v8').startupSnapshot;
|
||||
|
||||
// Observe 'gc' entries while building the snapshot.
|
||||
const observer = new PerformanceObserver(() => {});
|
||||
let received = 0;
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
received += list.getEntries().length;
|
||||
});
|
||||
observer.observe({ type: 'gc' });
|
||||
|
||||
// Performance entries are dispatched asynchronously, so trigger GCs until the
|
||||
@@ -24,17 +27,26 @@ function waitForEntries(getCount, callback, attempts = 10) {
|
||||
|
||||
setDeserializeMainFunction(() => {
|
||||
// The GC callbacks registered while building the snapshot do not survive
|
||||
// it. Observing 'gc' after deserialization must register them again.
|
||||
let received = 0;
|
||||
const newObserver = new PerformanceObserver((list) => {
|
||||
received += list.getEntries().length;
|
||||
});
|
||||
newObserver.observe({ type: 'gc' });
|
||||
// it, so they must be registered again after deserialization.
|
||||
if (process.env.TEST_NEW_OBSERVER) {
|
||||
// Observing 'gc' again after deserialization.
|
||||
let newReceived = 0;
|
||||
const newObserver = new PerformanceObserver((list) => {
|
||||
newReceived += list.getEntries().length;
|
||||
});
|
||||
newObserver.observe({ type: 'gc' });
|
||||
|
||||
waitForEntries(() => received, () => {
|
||||
// Disconnecting must only remove GC callbacks that are registered.
|
||||
newObserver.disconnect();
|
||||
observer.disconnect();
|
||||
console.log('ok');
|
||||
});
|
||||
waitForEntries(() => newReceived, () => {
|
||||
// Disconnecting must only remove GC callbacks that are registered.
|
||||
newObserver.disconnect();
|
||||
observer.disconnect();
|
||||
console.log('ok');
|
||||
});
|
||||
} else {
|
||||
// The observer that was active while building the snapshot.
|
||||
waitForEntries(() => received, () => {
|
||||
observer.disconnect();
|
||||
console.log('ok');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -26,13 +26,19 @@ spawnSyncAndExitWithoutError(process.execPath, [
|
||||
cwd: tmpdir.path,
|
||||
});
|
||||
|
||||
spawnSyncAndAssert(process.execPath, [
|
||||
'--expose-gc',
|
||||
'--snapshot-blob',
|
||||
blobPath,
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
stdout: 'ok',
|
||||
trim: true,
|
||||
});
|
||||
// The observer that was active while building the snapshot receives entries
|
||||
// after deserialization. With TEST_NEW_OBSERVER, a new observer is created
|
||||
// after deserialization instead.
|
||||
for (const env of [{}, { TEST_NEW_OBSERVER: '1' }]) {
|
||||
spawnSyncAndAssert(process.execPath, [
|
||||
'--expose-gc',
|
||||
'--snapshot-blob',
|
||||
blobPath,
|
||||
], {
|
||||
cwd: tmpdir.path,
|
||||
env: { ...process.env, ...env },
|
||||
}, {
|
||||
stdout: 'ok',
|
||||
trim: true,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user