Files
Matteo Collina 9a9f7e87b8 process: return unregister fn from finalization
process.finalization.register() and registerBeforeExit() now return
a function that removes only that registration, leaving other
registrations for the same object in place. The function does not
hold a strong reference to the object.

Each registration now uses its WeakRef as the unregister token, which
also makes process.finalization.unregister() remove the entries from
the FinalizationRegistry; previously it passed the object as the
token, which was never registered, so the call had no effect.

Refs: https://github.com/nodejs/node/pull/66482

Signed-off-by: Matteo Collina <matteo.collina@gmail.com>
Assisted-by: pi
PR-URL: https://github.com/nodejs/node/pull/66482
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
2026-10-18 06:36:43 +00:00

159 lines
3.4 KiB
JavaScript

'use strict';
// This file is a modified version of the on-exit-leak-free module on npm.
const {
SafeFinalizationRegistry,
SafeSet,
SafeWeakRef,
} = primordials;
const { validateObject, kValidateObjectAllowFunction } = require('internal/validators');
function createFinalization() {
/**
* @type {SafeFinalizationRegistry}
*/
let registry = null;
const refs = {
__proto__: null,
exit: new SafeSet(),
beforeExit: new SafeSet(),
};
const functions = {
__proto__: null,
exit: onExit,
beforeExit: onBeforeExit,
};
function install(event) {
if (refs[event].size > 0) {
return;
}
process.on(event, functions[event]);
}
function uninstall(event) {
if (refs[event].size > 0) {
return;
}
process.removeListener(event, functions[event]);
if (refs.exit.size === 0 && refs.beforeExit.size === 0) {
registry = null;
}
}
function onExit() {
callRefsToFree('exit');
}
function onBeforeExit() {
callRefsToFree('beforeExit');
}
function callRefsToFree(event) {
for (const ref of refs[event]) {
const obj = ref.deref();
const fn = ref.fn;
// This should always happen, however GC is
// indeterministic so it might not happen.
/* istanbul ignore else */
if (obj !== undefined) {
fn(obj, event);
}
}
refs[event].clear();
}
function clear(ref) {
for (const event of ['exit', 'beforeExit']) {
if (refs[event].delete(ref)) {
uninstall(event);
}
}
}
function _register(event, obj, fn) {
install(event);
const ref = new SafeWeakRef(obj);
ref.fn = fn;
registry ||= new SafeFinalizationRegistry(clear);
registry.register(obj, ref, ref);
refs[event].add(ref);
// The returned function must not capture `obj`, otherwise it would
// never be garbage collected while the caller holds the function.
return function unregister() {
if (!refs[event].delete(ref)) {
return;
}
registry.unregister(ref);
uninstall(event);
};
}
/**
* Execute the given function when the process exits,
* and clean things up when the object is gc.
* @param {any} obj
* @param {Function} fn
* @returns {Function} A function that removes this registration.
*/
function register(obj, fn) {
validateObject(obj, 'obj', kValidateObjectAllowFunction);
return _register('exit', obj, fn);
}
/**
* Execute the given function before the process exits,
* and clean things up when the object is gc.
* @param {any} obj
* @param {Function} fn
* @returns {Function} A function that removes this registration.
*/
function registerBeforeExit(obj, fn) {
validateObject(obj, 'obj', kValidateObjectAllowFunction);
return _register('beforeExit', obj, fn);
}
/**
* Unregister the given object from the onExit or onBeforeExit event.
* @param {object} obj
*/
function unregister(obj) {
if (!registry) {
return;
}
for (const event of ['exit', 'beforeExit']) {
for (const ref of refs[event]) {
const _obj = ref.deref();
if (!_obj || _obj === obj) {
refs[event].delete(ref);
registry.unregister(ref);
}
}
}
uninstall('exit');
uninstall('beforeExit');
}
return {
register,
registerBeforeExit,
unregister,
};
}
module.exports = {
createFinalization,
};