-
Notifications
You must be signed in to change notification settings - Fork 28.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ECMAScript Explicit Resource Management proposal integration with NodeJS APIs #45658
Comments
I'm not sure I see the real use case for something like this (generally speaking). As far as node goes, most things are async, which would not benefit from this kind of language change as far as I can tell. The proposed async disposal would be more useful I think. |
There are numerous things in the Node APIs that could be synchronously disposable, such as Asynchronous disposables will also be fully supported in the future as well, with syntactic support lagging behind the rest of the proposal due to a potential dependency on |
I'm still not following. Those would all be asynchronous tasks, requiring that the object still exist (e.g. when |
And aside from // node:util
class RefHolder {
#value
constructor(value) {
this.#value = value;
this.#value.ref();
}
[Symbol.dispose]() {
const value = this.#value;
this.#value = null;
value?.unref();
}
}
exports.RefHolder = RefHolder;
// main.js
import { RefHolder } from "node:util";
async function f() {
const timer = setInterval(100, someCallback);
using holder = new RefHolder(timer);
...
// 'timer' keeps the node process alive until `f` exits, at which point `timer.unref()` is called.
} Though, I admit that could potentially be handled by async function f() {
using stack = new DisposableStack();
const timer = stack.adopt(setInterval(100, someCallback), timer => timer.unref());
...
// 'timer' keeps the node process alive until `f` exits, at which point `timer.unref()` is called.
} |
Also, I apologize if my initial explanation was unclear. A synchronously disposed resource is one whose disposal completes synchronously (inasmuch as the user is involved). Synchronously disposed resources can still be used in async functions, since it does not matter if the intervening code between the |
Let's take the example of a child process. const proc = spawn('foo');
proc.stdout.on('data', (data) => {
if (data.includes(0xFF))
proc.kill();
}); If we try to adapt child process spawning with this new function foo() {
using proc = spawn('foo');
proc.stdout.on('data', (data) => {
if (data.includes(0xFF))
proc.kill();
});
} then realistically what could we possibly dispose if we're still needing to process child process output? The synchronous child process APIs already have implicit cleanup since they block the thread and any The same goes for all the other examples where the object use is typically asynchronous (e.g. streams and dns). About the only use case I can really see for something like |
Yes, the example you presented would not be correctly leveraging the A async function* foo() {
using proc = spawn('foo');
for await (const data of proc.stdout) {
if (data.includes(0xff)) break;
// do something with data
// if an exception is thrown (and not caught) anywhere in the function, 'proc' is killed
}
} // proc is killed if its still alive However, there is more to the resource management than just class Client {
#proc;
#logger;
#disposables;
constructor(logFile) {
// The lifetime of 'stack' is scoped to the constructor body
using stack = new DisposableStack();
// The lifetimes of '#proc' and '#logger' are scoped to the lifetime of 'stack'
this.#proc = stack.use(spawn('background_process')); // tracks '#proc' for disposal in 'stack'
this.#logger = stack.use(new FileLogger(logFile)); // tracks '#logger' for disposal in 'stack'
// If 'FileLogger' is unable to create 'logFile', it may throw an Error. When the exception
// causes the constructor to exit prematurely, 'stack' is disposed, which in turn would dispose
// '#proc' (killing the background process)
// If everything completes successfully, we move everything out of the 'stack' that is marked
// for disposal and into a new 'DisposableStack', which we store in '#disposables'. Now when
// 'stack' is disposed, '#proc' and '#logger' will remain active.
// The lifetimes of '#proc' and '#logger' are now scoped to the lifetime of '#disposables'.
// The lifetime of '#disposables' is scoped to the lifetime of the `Client` instance.
this.#disposables = stack.move();
}
[Symbol.dispose]() {
// When a 'Client' instance is disposed, its respective '#proc' and '#logger' values will
// also be disposed, killing the process and closing the file if either remain open.
this.#disposables.dispose();
}
} |
Ok, so basically the non- |
There are many use cases, and not all would necessitate async/await. For this case ( |
It seems like the discussion here ran its course? If not, I can convert it to a GH discussion. |
The ECMAScript Explicit Resource Management proposal is currently at Stage 2 and is nearing advancement Stage 3. I'd like to get a feel from NodeJS as to whether, and how, Node might integrate the capabilities of this proposal into existing APIs. I've created a rough list of potential integration points in the proposal explainer.
What follows is a brief summary of the proposal. You can read more about the proposal in the proposal repository and the proposal specification text. If you are unfamiliar with the TC39 staging process, please see the TC39 process document.
The Explicit Resource Management proposal provides the ability for JavaScript users to leverage RAII ("Resource Acquisition is Initialization")-style declarations via the new
using
declaration:A
using
declaration is constant, block-scoped declaration (likeconst
), and tracks its binding for disposal at the end of the containing block, regardless as to whether from a normal or abrupt completion. This allows for explicit control over the lifetime of a resource. A resource is an object with aSymbol.dispose
method that, when called, should perform synchronous cleanup activities.This kind of declaration is extremely useful when managing the scoping and lifetime of multiple resources, as it guarantees ordered cleanup of resources (excluding process/thread termination):
In addition, this proposal introduces a new
DisposableStack
constructor which allows a user to collect multiple disposable resources in a single disposable container, which is extremely helpful when composing disposable classes consisting of other resources:Not all resources can be disposed of in a synchronous manner, however. This proposal also introduces
Symbol.asyncDispose
and anAsyncDisposableStack
API for asynchronously disposed resources. These two APIs are part of the main proposal and are intended to advance to Stage 3. However, syntax for an asynchronoususing
declaration has been postponed to a follow-on proposal due to its possible dependency onasync do
expressions, though it will likely look something like the following:In summary, the following features are at Stage 2 and will be proposed for advancement to Stage 3 at the upcoming TC39 plenary:
using
declarations — block-scoped, constant, synchronous disposal.Symbol.dispose
— built-in symbol, indicates disposal method.Symbol.asyncDispose
— built-in symbol, indicates async disposal method.DisposableStack
— disposable container.AsyncDisposableStack
— async disposable container.The following features are at Stage 2 and have been postponed temporarily due to dependency on other proposals:
async using
declarations — block-scoped, constant, asynchronous disposal.Apologies for using neither the bug nor feature request templates. Neither seemed to suit the needs of this specific request.
The text was updated successfully, but these errors were encountered: