Use SafeSet collections for finalization refs so insertion, removal, and emptiness checks match the identity-based tracking model. This also fixes cleanup removal for collected refs. Previously cleanup used the ref index as the splice delete count, which could remove later live refs when the collected ref was not first. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 PR-URL: https://github.com/nodejs/node/pull/64087 Fixes: https://github.com/nodejs/node/issues/64086 Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
149 lines
3.2 KiB
JavaScript
149 lines
3.2 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');
|
|
const { emitExperimentalWarning } = require('internal/util');
|
|
|
|
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);
|
|
|
|
refs[event].add(ref);
|
|
}
|
|
|
|
/**
|
|
* Execute the given function when the process exits,
|
|
* and clean things up when the object is gc.
|
|
* @param {any} obj
|
|
* @param {Function} fn
|
|
*/
|
|
function register(obj, fn) {
|
|
emitExperimentalWarning('process.finalization.register');
|
|
validateObject(obj, 'obj', kValidateObjectAllowFunction);
|
|
|
|
_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
|
|
*/
|
|
function registerBeforeExit(obj, fn) {
|
|
emitExperimentalWarning('process.finalization.registerBeforeExit');
|
|
validateObject(obj, 'obj', kValidateObjectAllowFunction);
|
|
|
|
_register('beforeExit', obj, fn);
|
|
}
|
|
|
|
/**
|
|
* Unregister the given object from the onExit or onBeforeExit event.
|
|
* @param {object} obj
|
|
*/
|
|
function unregister(obj) {
|
|
emitExperimentalWarning('process.finalization.unregister');
|
|
if (!registry) {
|
|
return;
|
|
}
|
|
registry.unregister(obj);
|
|
for (const event of ['exit', 'beforeExit']) {
|
|
for (const ref of refs[event]) {
|
|
const _obj = ref.deref();
|
|
if (!_obj || _obj === obj) {
|
|
refs[event].delete(ref);
|
|
}
|
|
}
|
|
uninstall(event);
|
|
}
|
|
}
|
|
|
|
return {
|
|
register,
|
|
registerBeforeExit,
|
|
unregister,
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
createFinalization,
|
|
};
|