diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js index a4a962a8537..4a1931dd299 100644 --- a/lib/internal/perf/observe.js +++ b/lib/internal/perf/observe.js @@ -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(); } } } diff --git a/test/fixtures/snapshot/perf-hooks-gc-observer.js b/test/fixtures/snapshot/perf-hooks-gc-observer.js index cf98bf2692a..353f10fc0ee 100644 --- a/test/fixtures/snapshot/perf-hooks-gc-observer.js +++ b/test/fixtures/snapshot/perf-hooks-gc-observer.js @@ -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'); + }); + } }); diff --git a/test/parallel/test-snapshot-perf-hooks-gc-observer.js b/test/parallel/test-snapshot-perf-hooks-gc-observer.js index 13222665fe7..ce43592a958 100644 --- a/test/parallel/test-snapshot-perf-hooks-gc-observer.js +++ b/test/parallel/test-snapshot-perf-hooks-gc-observer.js @@ -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, + }); +}