Skip to content

Commit cbf147f

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. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 8bf7793 commit cbf147f

4 files changed

Lines changed: 69 additions & 5 deletions

File tree

‎doc/api/process.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,6 +2062,10 @@ Since it's not possible to build Node.js without libuv, this value is always `tr
20622062
20632063
<!-- YAML
20642064
added: v22.5.0
2065+
changes:
2066+
- version: REPLACEME
2067+
pr-url: https://github.com/nodejs/node/pull/XXXXX
2068+
description: Returns a function that removes the registration.
20652069
-->
20662070
20672071
> Stability: 1.1 - Active Development
@@ -2071,6 +2075,7 @@ added: v22.5.0
20712075
is finalized.
20722076
* `ref` {Object | Function} The reference to the resource that is being tracked.
20732077
* `event` {string} The event that triggered the finalization. Defaults to 'exit'.
2078+
* Returns: {Function} A function that removes this registration when called.
20742079
20752080
This function registers a callback to be called when the process emits the `exit`
20762081
event if the `ref` object was not garbage collected. If the object `ref` was garbage collected
@@ -2167,10 +2172,25 @@ but if it is not, `dispose` will be called when `process.exit` is called.
21672172
Be careful and avoid relying on this feature for the disposal of critical resources,
21682173
as it is not guaranteed that the callback will be called under all circumstances.
21692174
2175+
The returned function removes only this registration, leaving other
2176+
registrations for the same `ref` in place. Calling it more than once has no
2177+
effect. It does not hold a strong reference to `ref`.
2178+
2179+
```js
2180+
const unregister = finalization.register(myDisposableObject, onFinalize);
2181+
2182+
// Later, once the resource has been released manually:
2183+
unregister();
2184+
```
2185+
21702186
## `process.finalization.registerBeforeExit(ref, callback)`
21712187
21722188
<!-- YAML
21732189
added: v22.5.0
2190+
changes:
2191+
- version: REPLACEME
2192+
pr-url: https://github.com/nodejs/node/pull/XXXXX
2193+
description: Returns a function that removes the registration.
21742194
-->
21752195
21762196
> Stability: 1.1 - Active Development
@@ -2181,6 +2201,7 @@ added: v22.5.0
21812201
is finalized.
21822202
* `ref` {Object | Function} The reference to the resource that is being tracked.
21832203
* `event` {string} The event that triggered the finalization. Defaults to 'beforeExit'.
2204+
* Returns: {Function} A function that removes this registration when called.
21842205
21852206
This function behaves exactly like the `register`, except that the callback will be called
21862207
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
@@ -85,35 +85,47 @@ function createFinalization() {
8585
ref.fn = fn;
8686

8787
registry ||= new SafeFinalizationRegistry(clear);
88-
registry.register(obj, ref);
88+
registry.register(obj, ref, ref);
8989

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

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

103-
_register('exit', obj, fn);
114+
return _register('exit', obj, fn);
104115
}
105116

106117
/**
107118
* Execute the given function before the process exits,
108119
* and clean things up when the object is gc.
109120
* @param {any} obj
110121
* @param {Function} fn
122+
* @returns {Function} A function that removes this registration.
111123
*/
112124
function registerBeforeExit(obj, fn) {
113125
emitExperimentalWarning('process.finalization.registerBeforeExit');
114126
validateObject(obj, 'obj', kValidateObjectAllowFunction);
115127

116-
_register('beforeExit', obj, fn);
128+
return _register('beforeExit', obj, fn);
117129
}
118130

119131
/**
@@ -125,16 +137,17 @@ function createFinalization() {
125137
if (!registry) {
126138
return;
127139
}
128-
registry.unregister(obj);
129140
for (const event of ['exit', 'beforeExit']) {
130141
for (const ref of refs[event]) {
131142
const _obj = ref.deref();
132143
if (!_obj || _obj === obj) {
133144
refs[event].delete(ref);
145+
registry.unregister(ref);
134146
}
135147
}
136-
uninstall(event);
137148
}
149+
uninstall('exit');
150+
uninstall('beforeExit');
138151
}
139152

140153
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)