Skip to content

Commit 30dcffb

Browse files
committed
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: #66482 Signed-off-by: Matteo Collina <matteo.collina@gmail.com> Assisted-by: pi
1 parent cbac135 commit 30dcffb

4 files changed

Lines changed: 67 additions & 5 deletions

File tree

‎doc/api/process.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,9 @@ Since it's not possible to build Node.js without libuv, this value is always `tr
20642064
added: v22.5.0
20652065
changes:
20662066
- version: REPLACEME
2067+
pr-url: https://github.com/nodejs/node/pull/66482
2068+
description: Returns a function that removes the registration.
2069+
- version: v26.12.0
20672070
pr-url: https://github.com/nodejs/node/pull/66436
20682071
description: Marked as release candidate.
20692072
-->
@@ -2075,6 +2078,7 @@ changes:
20752078
is finalized.
20762079
* `ref` {Object | Function} The reference to the resource that is being tracked.
20772080
* `event` {string} The event that triggered the finalization. Defaults to 'exit'.
2081+
* Returns: {Function} A function that removes this registration when called.
20782082
20792083
This function registers a callback to be called when the process emits the `exit`
20802084
event if the `ref` object was not garbage collected. If the object `ref` was garbage collected
@@ -2171,12 +2175,26 @@ but if it is not, `dispose` will be called when `process.exit` is called.
21712175
Be careful and avoid relying on this feature for the disposal of critical resources,
21722176
as it is not guaranteed that the callback will be called under all circumstances.
21732177
2178+
The returned function removes only this registration, leaving other
2179+
registrations for the same `ref` in place. Calling it more than once has no
2180+
effect. It does not hold a strong reference to `ref`.
2181+
2182+
```js
2183+
const unregister = finalization.register(myDisposableObject, onFinalize);
2184+
2185+
// Later, once the resource has been released manually:
2186+
unregister();
2187+
```
2188+
21742189
## `process.finalization.registerBeforeExit(ref, callback)`
21752190
21762191
<!-- YAML
21772192
added: v22.5.0
21782193
changes:
21792194
- version: REPLACEME
2195+
pr-url: https://github.com/nodejs/node/pull/66482
2196+
description: Returns a function that removes the registration.
2197+
- version: v26.12.0
21802198
pr-url: https://github.com/nodejs/node/pull/66436
21812199
description: Marked as release candidate.
21822200
-->
@@ -2189,6 +2207,7 @@ changes:
21892207
is finalized.
21902208
* `ref` {Object | Function} The reference to the resource that is being tracked.
21912209
* `event` {string} The event that triggered the finalization. Defaults to 'beforeExit'.
2210+
* Returns: {Function} A function that removes this registration when called.
21922211
21932212
This function behaves exactly like the `register`, except that the callback will be called
21942213
when the process emits the `beforeExit` event if `ref` object was not garbage collected.

‎lib/internal/process/finalization.js‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,33 +84,45 @@ function createFinalization() {
8484
ref.fn = fn;
8585

8686
registry ||= new SafeFinalizationRegistry(clear);
87-
registry.register(obj, ref);
87+
registry.register(obj, ref, ref);
8888

8989
refs[event].add(ref);
90+
91+
// The returned function must not capture `obj`, otherwise it would
92+
// never be garbage collected while the caller holds the function.
93+
return function unregister() {
94+
if (!refs[event].delete(ref)) {
95+
return;
96+
}
97+
registry.unregister(ref);
98+
uninstall(event);
99+
};
90100
}
91101

92102
/**
93103
* Execute the given function when the process exits,
94104
* and clean things up when the object is gc.
95105
* @param {any} obj
96106
* @param {Function} fn
107+
* @returns {Function} A function that removes this registration.
97108
*/
98109
function register(obj, fn) {
99110
validateObject(obj, 'obj', kValidateObjectAllowFunction);
100111

101-
_register('exit', obj, fn);
112+
return _register('exit', obj, fn);
102113
}
103114

104115
/**
105116
* Execute the given function before the process exits,
106117
* and clean things up when the object is gc.
107118
* @param {any} obj
108119
* @param {Function} fn
120+
* @returns {Function} A function that removes this registration.
109121
*/
110122
function registerBeforeExit(obj, fn) {
111123
validateObject(obj, 'obj', kValidateObjectAllowFunction);
112124

113-
_register('beforeExit', obj, fn);
125+
return _register('beforeExit', obj, fn);
114126
}
115127

116128
/**
@@ -121,16 +133,17 @@ function createFinalization() {
121133
if (!registry) {
122134
return;
123135
}
124-
registry.unregister(obj);
125136
for (const event of ['exit', 'beforeExit']) {
126137
for (const ref of refs[event]) {
127138
const _obj = ref.deref();
128139
if (!_obj || _obj === obj) {
129140
refs[event].delete(ref);
141+
registry.unregister(ref);
130142
}
131143
}
132-
uninstall(event);
133144
}
145+
uninstall('exit');
146+
uninstall('beforeExit');
134147
}
135148

136149
return {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { strictEqual } from 'assert'
2+
3+
const calls = []
4+
5+
function onExit(obj, event) {
6+
calls.push(`${obj.name}:${event}`)
7+
}
8+
9+
const a = { name: 'a' }
10+
const b = { name: 'b' }
11+
12+
const unregisterA = process.finalization.register(a, onExit)
13+
const unregisterABeforeExit = process.finalization.registerBeforeExit(a, onExit)
14+
process.finalization.register(b, onExit)
15+
const unregisterBAgain = process.finalization.register(b, onExit)
16+
17+
strictEqual(typeof unregisterA, 'function')
18+
strictEqual(typeof unregisterABeforeExit, 'function')
19+
20+
unregisterA()
21+
unregisterA() // twice, this should not throw
22+
unregisterABeforeExit()
23+
24+
// Removing one registration keeps the others for the same object.
25+
unregisterBAgain()
26+
27+
process.on('exit', function () {
28+
strictEqual(calls.join(','), 'b:exit')
29+
})

‎test/parallel/test-process-finalization.mjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const files = [
1212
'finalization-cleanup.mjs',
1313
'gc-not-close.mjs',
1414
'unregister.mjs',
15+
'unregister-function.mjs',
1516
'different-registry-per-thread.mjs',
1617
];
1718

0 commit comments

Comments
 (0)