-
Notifications
You must be signed in to change notification settings - Fork 29k
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
async_hooks: add SymbolDispose support to AsyncLocalStorage #52065
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
Use a shared object as the top level async resource in JS and C++ so that when there is no active async resource, the same top level async resource object is returned from `async_hooks.executionAsyncResource()`.
56fff75
to
1f997de
Compare
1f997de
to
5a98409
Compare
This introduces explicit resource management support to AsyncLocalStorage. In order to avoid unexpected leakage of an async local storage value to outer scope, `asyncLocalStorage.run` requires a callback to nest all sub-procedures in a new function scope. However, when using `AsyncLocalStorage` with different function types, like async generator, moving statements and expressions to a new function body could be evaluated in different ways. For example, given an async generator: ```js class C { async* foo() { await a(); yield b(); await c(); } } ``` Then, if we want to modify the async local storage value for `b()` and `c()`, simply moving statements would not work: ```js const storage = new AsyncLocalStorage(); class C { async* foo() { await a(); storage.run(value, () => { yield b(); // syntax error, arrow function is not a generator await this.c(); // syntax error, arrow function is not async }); } } ``` Making the arrow function to be an async generator as well still requires significant refactoring: ```js const storage = new AsyncLocalStorage(); class C { async* foo() { await a(); yield* storage.run(value, async function*() => { yield b(); // ok await this.c(); // reference error, this is undefined }); } } ``` This could be potentially more convenient with `using` declarations: ```js const storage = new AsyncLocalStorage(); class C { async* foo() { await a(); using _ = storage.disposableStore(value); yield b(); await this.c(); } } ``` However, creating a disposable without a `using` declaration could still be a problem leading to leakage of the async local storage value. To help identifying such mis-use, an `ERR_ASYNC_LOCAL_STORAGE_NOT_DISPOSED` error is thrown if an `AsyncLocalStorageDisposableStore` instance is not disposed at the end of the synchronous execution.
5a98409
to
9099911
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer if we avoid further entangling AsyncLocalStorage with async_hooks, which I'm presently trying to undo. Can we instead make each constructed AsyncLocalStorageDisposableStore
just queue a microtask to check if it is disposed yet by the time the microtask is run and skip the use of createHook
entirely?
This needs a rebase. |
This introduces explicit resource management support to AsyncLocalStorage. In order to avoid unexpected leakage of an async local storage value to outer scope,
asyncLocalStorage.run
requires a callback to nest all sub-procedures in a new function scope. However, when usingAsyncLocalStorage
with different function types, like async generator, moving statements and expression to a new function body could be evaluated in different ways.For example, given an async generator:
Then, if we want to modify the async local storage value for
b()
andc()
, simply moving statements would not work:Making the arrow function to be an async generator as well still requires significant refactoring:
This could be potentially more convenient with
using
declarations:However, creating a disposable without a
using
declaration could still be a problem leading to leakage of the async local storage value. To help identifying such mis-use, anERR_ASYNC_LOCAL_STORAGE_NOT_DISPOSED
error is thrown if anAsyncLocalStorageDisposableStore
instance is not disposed at the end of the current async resource scope.