From 77936c3d2482ded2cb868f20f177433081b6dca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinicius=20Louren=C3=A7o?= <12551007+H4ad@users.noreply.github.com> Date: Thu, 11 Jul 2024 14:57:20 -0300 Subject: [PATCH] process: port on-exit-leak-free to core PR-URL: https://github.com/nodejs/node/pull/53239 Reviewed-By: Matteo Collina Reviewed-By: Chemi Atlow Reviewed-By: Paolo Insogna Reviewed-By: Yagiz Nizipli --- LICENSE | 25 ++ doc/api/process.md | 213 ++++++++++++++++++ lib/internal/bootstrap/node.js | 20 ++ lib/internal/process/finalization.js | 150 ++++++++++++ lib/internal/process/per_thread.js | 1 - test/fixtures/process/before-exit.mjs | 31 +++ test/fixtures/process/close.mjs | 18 ++ .../process/different-registry-per-thread.mjs | 15 ++ test/fixtures/process/gc-not-close.mjs | 21 ++ test/fixtures/process/unregister.mjs | 21 ++ test/parallel/test-process-finalization.mjs | 52 +++++ tools/license-builder.sh | 3 + 12 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 lib/internal/process/finalization.js create mode 100644 test/fixtures/process/before-exit.mjs create mode 100644 test/fixtures/process/close.mjs create mode 100644 test/fixtures/process/different-registry-per-thread.mjs create mode 100644 test/fixtures/process/gc-not-close.mjs create mode 100644 test/fixtures/process/unregister.mjs create mode 100644 test/parallel/test-process-finalization.mjs diff --git a/LICENSE b/LICENSE index aceb0140e6dd9c..64b2e7f734b3f2 100644 --- a/LICENSE +++ b/LICENSE @@ -2378,3 +2378,28 @@ The externally maintained libraries used by Node.js are: OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + +- on-exit-leak-free, located at lib/internal/process/finalization, is licensed as follows: + """ + MIT License + + Copyright (c) 2021 Matteo Collina + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + """ diff --git a/doc/api/process.md b/doc/api/process.md index 5eb4eef0745c66..54d36f6dc11ffb 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1889,6 +1889,219 @@ a code. Specifying a code to [`process.exit(code)`][`process.exit()`] will override any previous setting of `process.exitCode`. +## `process.finalization.register(ref, callback)` + + + +> Stability: 1.1 - Active Development + +* `ref` {Object | Function} The reference to the resource that is being tracked. +* `callback` {Function} The callback function to be called when the resource + 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'. + +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 +before the `exit` event is emitted, the callback will be removed from the finalization registry, +and it will not be called on process exit. + +Inside the callback you can release the resources allocated by the `ref` object. +Be aware that all limitations applied to the `beforeExit` event are also applied to the `callback` function, +this means that there is a possibility that the callback will not be called under special circumstances. + +The idea of ​​this function is to help you free up resources when the starts process exiting, +but also let the object be garbage collected if it is no longer being used. + +Eg: you can register an object that contains a buffer, you want to make sure that buffer is released +when the process exit, but if the object is garbage collected before the process exit, we no longer +need to release the buffer, so in this case we just remove the callback from the finalization registry. + +```cjs +const { finalization } = require('node:process'); + +// Please make sure that the function passed to finalization.register() +// does not create a closure around unnecessary objects. +function onFinalize(obj, event) { + // You can do whatever you want with the object + obj.dispose(); +} + +function setup() { + // This object can be safely garbage collected, + // and the resulting shutdown function will not be called. + // There are no leaks. + const myDisposableObject = { + dispose() { + // Free your resources synchronously + }, + }; + + finalization.register(myDisposableObject, onFinalize); +} + +setup(); +``` + +```mjs +import { finalization } from 'node:process'; + +// Please make sure that the function passed to finalization.register() +// does not create a closure around unnecessary objects. +function onFinalize(obj, event) { + // You can do whatever you want with the object + obj.dispose(); +} + +function setup() { + // This object can be safely garbage collected, + // and the resulting shutdown function will not be called. + // There are no leaks. + const myDisposableObject = { + dispose() { + // Free your resources synchronously + }, + }; + + finalization.register(myDisposableObject, onFinalize); +} + +setup(); +``` + +The code above relies on the following assumptions: + +* arrow functions are avoided +* regular functions are recommended to be within the global context (root) + +Regular functions _could_ reference the context where the `obj` lives, making the `obj` not garbage collectible. + +Arrow functions will hold the previous context. Consider, for example: + +```js +class Test { + constructor() { + finalization.register(this, (ref) => ref.dispose()); + + // even something like this is highly discouraged + // finalization.register(this, () => this.dispose()); + } + dispose() {} +} +``` + +It is very unlikely (not impossible) that this object will be garbage collected, +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. + +## `process.finalization.registerBeforeExit(ref, callback)` + + + +> Stability: 1.1 - Active Development + +* `ref` {Object | Function} The reference + to the resource that is being tracked. +* `callback` {Function} The callback function to be called when the resource + 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'. + +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. + +Be aware that all limitations applied to the `beforeExit` event are also applied to the `callback` function, +this means that there is a possibility that the callback will not be called under special circumstances. + +## `process.finalization.unregister(ref)` + + + +> Stability: 1.1 - Active Development + +* `ref` {Object | Function} The reference + to the resource that was registered previously. + +This function remove the register of the object from the finalization +registry, so the callback will not be called anymore. + +```cjs +const { finalization } = require('node:process'); + +// Please make sure that the function passed to finalization.register() +// does not create a closure around unnecessary objects. +function onFinalize(obj, event) { + // You can do whatever you want with the object + obj.dispose(); +} + +function setup() { + // This object can be safely garbage collected, + // and the resulting shutdown function will not be called. + // There are no leaks. + const myDisposableObject = { + dispose() { + // Free your resources synchronously + }, + }; + + finalization.register(myDisposableObject, onFinalize); + + // Do something + + myDisposableObject.dispose(); + finalization.unregister(myDisposableObject); +} + +setup(); +``` + +```mjs +import { finalization } from 'node:process'; + +// Please make sure that the function passed to finalization.register() +// does not create a closure around unnecessary objects. +function onFinalize(obj, event) { + // You can do whatever you want with the object + obj.dispose(); +} + +function setup() { + // This object can be safely garbage collected, + // and the resulting shutdown function will not be called. + // There are no leaks. + const myDisposableObject = { + dispose() { + // Free your resources synchronously + }, + }; + + // Please make sure that the function passed to finalization.register() + // does not create a closure around unnecessary objects. + function onFinalize(obj, event) { + // You can do whatever you want with the object + obj.dispose(); + } + + finalization.register(myDisposableObject, onFinalize); + + // Do something + + myDisposableObject.dispose(); + finalization.unregister(myDisposableObject); +} + +setup(); +``` + ## `process.getActiveResourcesInfo()`