Version
main
Platform
Subsystem
process
What steps will reproduce the bug?
import process from 'node:process';
import { setImmediate } from 'node:timers/promises';
const kept = [];
function onFinalize(ref) {
console.log(`finalized: ${ref.name}`);
}
function setup() {
const first = { name: 'first' };
let collected = { name: 'collected' };
const third = { name: 'third' };
kept.push(first, third);
process.finalization.register(first, onFinalize);
process.finalization.register(collected, onFinalize);
process.finalization.register(third, onFinalize);
collected = null;
}
setup();
// Give V8 a few chances to collect `collected` and run the
// FinalizationRegistry cleanup before process exit.
for (let i = 0; i < 10; i++) {
gc();
await setImmediate();
}
console.log('expected: finalized: first');
console.log('expected: finalized: third');
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
expected: finalized: first
expected: finalized: third
finalized: first
finalized: third
Both first and third are strongly reachable through kept, so both should remain registered and run their finalization callbacks during process exit.
What do you see instead?
expected: finalized: first
expected: finalized: third
finalized: first
After collected is garbage-collected, cleanup removes its ref and also removes third because splice(1, 2) deletes two entries. As a result, third is still alive but its exit callback is skipped.
Additional information
Noticed while working on #64085
The docs for process.finalization.register(ref, callback) say that it "registers a callback to be called when the process emits the exit event if the ref object was not garbage collected."
Version
main
Platform
Subsystem
process
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
Both
firstandthirdare strongly reachable throughkept, so both should remain registered and run their finalization callbacks during process exit.What do you see instead?
After
collectedis garbage-collected, cleanup removes its ref and also removesthirdbecausesplice(1, 2)deletes two entries. As a result,thirdis still alive but its exit callback is skipped.Additional information
Noticed while working on #64085
The docs for
process.finalization.register(ref, callback)say that it "registers a callback to be called when the process emits theexitevent if therefobject was not garbage collected."