nodejs/node · #66482

process: return unregister fn from finalization

mcollina · merged Oct 18, 20264 files · 67 + / 5 −
doc/api/process.md19 + / 0 −
@@ -2063,6 +2063,9 @@ Since it's not possible to build Node.js without libuv, this value is always `tr <!-- YAML added: v22.5.0 changes:+  - version: REPLACEME+    pr-url: https://github.com/nodejs/node/pull/66482+    description: Returns a function that removes the registration.   - version: v26.12.0     pr-url: https://github.com/nodejs/node/pull/66436     description: Marked as release candidate.@@ -2075,6 +2078,7 @@ changes:   is finalized.   * `ref` {Object | Function} The reference to the resource that is being tracked.   * `event` {string} The event that triggered the finalization. Defaults to 'exit'.+* Returns: {Function} A function that removes this registration when called.  This function registers a callback to be called when the process emits the `exit` event if the `ref` object was not garbage collected. If the object `ref` was garbage collected@@ -2171,11 +2175,25 @@ but if it is not, `dispose` will be called when `process.exit` is called. Be careful and avoid relying on this feature for the disposal of critical resources, as it is not guaranteed that the callback will be called under all circumstances. +The returned function removes only this registration, leaving other+registrations for the same `ref` in place. Calling it more than once has no+effect. It does not hold a strong reference to `ref`.++```js+const unregister = finalization.register(myDisposableObject, onFinalize);++// Later, once the resource has been released manually:+unregister();+```+ ## `process.finalization.registerBeforeExit(ref, callback)`  <!-- YAML added: v22.5.0 changes:+  - version: REPLACEME+    pr-url: https://github.com/nodejs/node/pull/66482+    description: Returns a function that removes the registration.   - version: v26.12.0     pr-url: https://github.com/nodejs/node/pull/66436     description: Marked as release candidate.@@ -2189,6 +2207,7 @@ changes:   is finalized.   * `ref` {Object | Function} The reference to the resource that is being tracked.   * `event` {string} The event that triggered the finalization. Defaults to 'beforeExit'.+* Returns: {Function} A function that removes this registration when called.  This function behaves exactly like the `register`, except that the callback will be called when the process emits the `beforeExit` event if `ref` object was not garbage collected.
lib/internal/process/finalization.js18 + / 5 −
@@ -84,33 +84,45 @@ function createFinalization() {     ref.fn = fn;      registry ||= new SafeFinalizationRegistry(clear);-    registry.register(obj, ref);+    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); -    _register('exit', obj, fn);+    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); -    _register('beforeExit', obj, fn);+    return _register('beforeExit', obj, fn);   }    /**@@ -121,16 +133,17 @@ function createFinalization() {     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);+          registry.unregister(ref);         }       }-      uninstall(event);     }+    uninstall('exit');+    uninstall('beforeExit');   }    return {
test/fixtures/process/unregister-function.mjsadded29 + / 0 −
@@ -0,0 +1,29 @@+import { strictEqual } from 'assert'++const calls = []++function onExit(obj, event) {+  calls.push(`${obj.name}:${event}`)+}++const a = { name: 'a' }+const b = { name: 'b' }++const unregisterA = process.finalization.register(a, onExit)+const unregisterABeforeExit = process.finalization.registerBeforeExit(a, onExit)+process.finalization.register(b, onExit)+const unregisterBAgain = process.finalization.register(b, onExit)++strictEqual(typeof unregisterA, 'function')+strictEqual(typeof unregisterABeforeExit, 'function')++unregisterA()+unregisterA() // twice, this should not throw+unregisterABeforeExit()++// Removing one registration keeps the others for the same object.+unregisterBAgain()++process.on('exit', function () {+  strictEqual(calls.join(','), 'b:exit')+})
test/parallel/test-process-finalization.mjs1 + / 0 −
@@ -12,6 +12,7 @@ const files = [   'finalization-cleanup.mjs',   'gc-not-close.mjs',   'unregister.mjs',+  'unregister-function.mjs',   'different-registry-per-thread.mjs', ];