-
Notifications
You must be signed in to change notification settings - Fork 78
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
Proposal for reworking promise integration into async_hooks #389
Comments
The Also, I think this is actually slightly overthinking and/or looking past where the real issue is. We don't actually need promise hooks at all. What we need is microtask hooks. When it comes down to it, promises are actually conceptually similar to event emitters in that they are only async because of external forces making them so. In this case, the microtask queue. The function resolve(value) {
for (const realResolve of resolversForChainedPromises) {
queueMicrotask(() => {
realResolve(value)
})
}
} If instead of wrapping promises we wrapped microtasks at the point they are created and the points before and after they are executed, it would solve the same problem and would actually be a much simpler solution. |
I think that somebody should try to implement a |
I think @Qard makes good points. Hooking the queue seems more effective to me, too, and gets around the problem that there are many Promise implementations, but only one microtask queue. Its more robust to hook the locations of true asyncness then all the code above that might use them. |
@targos Yep. That's the plan! |
Looks promising and for sure worth a more detailed look/experiment! I'm curious what else we may uncoer at the microtask level...
Interesting analogy. This raises the question which context is the "correct" to propagate for an ALS system, that one where |
I don't think there's really a "correct" path. As Microsoft folks brought up in the previous async context formalization discussions, there's really two relevant paths: user-intent and technical causality. The user intent is generally what is reflected by capturing |
I tried the MicrotaskQueue subclass idea. Doesn't work out quite how I hoped. The specific I'm refocusing my effort on seeing if I can just produce a new API which roughly parallels the PromiseHook API in functionality, but focuses specifically on microtask creation and lifecycle rather than promises. |
Here is a small note on the performance of the current PromiseHook integration: nodejs/node#34493 (comment) It seems to me that what we have now is quite expensive and low overhead should be one of the main requirements for the new implementation. |
@puzpuzpuz Well, that is really one of the primary benefits of this approach. |
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made. |
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made. |
Background
The current implementation of promise integration into
async_hooks
is.[[Promise]]
object, call theinit
hook with aPromiseWrap
referencing that Promise.[[resolve]]
or[[reject]]
call theresolve
hook.[[then]]
callback, call thebefore
hook before calling the callback.[[then]]
callback, call theafter
hook after calling the callback.I think this implementation is fundamentally wrong, as it intertwines the promise lifecycle with
async_hooks
. This causes a number of issues that are currently blocking us from makingasync_hooks
stable.destroy
hook is not called ifasync_hook
is enabled after Promise creation..then()
calls are used on the same promise object, is not possible.Proposal
My proposal is to rework the promise integration into
async_hooks
such that the async barrier is around the[[then]]
call, not creating a newPromise
object.[[then]]
on apromise
orthenable
create a resource object (or use the promise/thenable object created by[[then]]
) then call theinit
hook.[[then]]
callback, thebefore
hook is called.[[then]]
callback, call theafter
hook, immediately after call thedestroy
hook.How it solves the above-mentioned issues
Performance issues caused by listening for the garbage collection event.
Because the
before
andafter
hooks are only called once per resource, thedestroy
hook can be called immediately after theafter
hook. Thus completely eliminating the need to track promise objects in the garbage collector.Thenables are not tracked when used by a native function that creates a microtrask.
This can now be solved because we don't need to know when the object was created or destroyed. The only knowledge that is required is when the
[[then]]
method of the thenable is called by the native JS APIs which invokes the microtask queue. That is actually doable, as we could hook into those APIs. Only manual calls to[[then]]
on a thenable will not be tracked. But I don't see that as a concern, because that is not an async action.destroy
hook is not called ifasync_hook
is enabled after Promise creation.Again, because the
destroy
hook is called with theafter
hook, calling thedestroy
hook becomes trivial.Tracking the async boundary when multiple
.then()
calls are used on the same promise object, is not possible.This directly confronts this issue, by making the async boundary the
[[then]]
call.Tracking the lifetime of promises
This proposal removes features from
async_hooks
that provide insight intopromises
. That information is still valuable.To keep providing that information, I propose making a dedicated
promise_hooks
module. A user can then connect the promise lifecycle withasync_hooks
via apromiseId
that is exposed both inpromise_hook
and via the resource inasync_hooks
.Compatability
This does change the default async chain, as the start point of the async-barrier is now the
.then()
call and not thenew Promise
call. However, I think this is actually a preferred default. For example, lazyloading a resource withnew Promise
, would currently not be tracked correctly, but with the proposed change it will.Even though this changes the async graph, the proposed async graph will still be valid and useful in most cases. And the existing async graph can be restored by integrating the proposed
promise_hooks
module.implementing this proposal should be part of a major release.
Open questions
async/await
. As I don't have a good grasp of how the internal[[then]]
calls are used and wrapped. It has been proposed that we supply our own customMicrotaskQueue
(async_hooks performance/stability improvement plans #376 (comment)) to solve this. I think this could also be a good approach to hook into the native promise APIs, which will be necessary to track[[then]]
calls on thenables.The text was updated successfully, but these errors were encountered: