-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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: support promise resolve hook #15296
Conversation
doc/api/async_hooks.md
Outdated
|
||
// promiseResolve is called only for promise resources, when the | ||
// `resolve` function passed to the `Promise` constructor is invoked | ||
// (either directly or though by other means of resolving a promise). |
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.
though by -> through || by ?
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.
And maybe add promiseResolve
to summary line 39?
const asyncHook = async_hooks.createHook(
{ init, before, after, destroy, promiseResolve }
);
doc/api/async_hooks.md
Outdated
* `asyncId` {number} | ||
|
||
Called when the `resolve` function passed to the `Promise` constructor is | ||
invoked (either directly or though by other means of resolving a promise). |
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.
though by -> through || by ?
I have a strange itch about this, are promises really the only case where we need this hook? I spent a great deal of time thinking about it after #13437, I can't really come up with something good. Although, I suppose one could implement ones own promise in which case the Embedder API should be extended as well. Ref: #13437 |
@vsemozhetbyt Thanks, done!
Me too! But I think so – the difference between this and other hooks is that Promises have their own ways of scheduling asynchronous work. I’d say (Except maybe setting up a separate async resource for the microtask queue, and then using |
@addaleax , in this PR, the Just as we discussed in const p = new Promise((resolve, reject) => {}); // a promise will never resolve
const p1 = new Promise((resolve, reject) => {
resolve(p); // p1 is not resolved, but PromiseResolve hook will be called?
}); is my understanding correct? Thank you very much! |
@JiaLiPassion Yes, your understanding is correct. I don’t quite like it myself, but this is what the V8 API offers. (cc @gsathya ) |
@addaleax , got it, thank you! |
// promiseResolve is called only for promise resources, when the | ||
// `resolve` function passed to the `Promise` constructor is invoked | ||
// (either directly or through other means of resolving a promise). | ||
function promiseResolve(asyncId) { } |
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.
Would it be called before or after the resolve
function is invoked? The distinction may be important for measuring timing.
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.
@jasnell I’m pretty sure that’s actually irrelevant, since the resolve
method doesn’t actually do any synchronously visible work.
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.
This should also likely include some clarification about the relationship (if any) this has to the before
and after
emits. An example that shows what kind of ordering of these events to expect when doing a new Promise((resolve) => resolve(true)).then((a) => {})
would be worthwhile.
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’m pretty sure that’s actually irrelevant, since the resolve method doesn’t actually do any synchronously visible work.
Then I would be explicit about that in the docs.
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’ve added a note about that and added an example
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 don't like adding another hook, but after thinking about this for quite a while I can't think of a better way to expose the interface. The only alternative I could think of is:
const hook = async_hooks.createHook({ /* ... */ }).enable();
hook.setPromiseResolve(cb);
i'd be partial to this approach if we thought there'd be more misc events like this in the future, but i can't think of any. @addaleax you think more events like this could occur in the future?
So, LGTM if there's no additional conversation on how the API is publicly exposed.
I'm +1 on this approach also but we'll definitely need to do some testing on how this works with things like async iterators... just so we know what to expect. I don't anticipate any problems but it would be good to have a test for it. I'll do some testing on that shortly. Looking at the various pending TC39 proposals I do not anticipate anything new and exotic that cannot be covered by the existing callbacks but it's difficult to say for sure that there wouldn't be. The |
Quick test run with async iterators using the following: The code...'use strict';
const crypto = require('crypto');
const ah = require('async_hooks');
const hook = ah.createHook({
init(id, type, triggerAsyncId, resource) {
process._rawDebug(`${type} - ${id}, trigger: ${triggerAsyncId}`);
},
before(id) {
process._rawDebug(` before ${id}`);
},
after(id) {
process._rawDebug(` after ${id}`);
},
destroy(id) {
process._rawDebug(` destroy ${id}`);
},
promiseResolve(id) {
process._rawDebug(` promise resolve ${id}`);
}
});
hook.enable();
async function hash(alg, data, enc) {
const hash = crypto.createHash(alg);
for await (const chunk of data) {
hash.update(chunk);
}
return hash.digest(enc);
}
var n = 0;
async function* dataIterator() {
while (n++ < 10)
yield `testing${n}`;
}
const p = hash('sha256', dataIterator(), 'hex');
p.then(console.log).catch(console.error);
console.log('testing 123'); Here's the output
The only interesting bit that I can see here is that |
d6abb3c
to
d2d5d26
Compare
Add a `promiseResolve()` hook.
d2d5d26
to
15befe1
Compare
I think we’re good with that, we can just use the
That seems to be expected to me in case where resolving one promise also resolves another? |
Hmm, surely a promise can only be resolved once. In the example promise 21 is resolved twice ( |
Oh. Yes, that is weird. |
The destroy hook would most likely be ok but cancel operations may end up having their own handler callback that invokes before the promise is actually destroyed. And yeah, it's quite weird to have the promise reported as having been resolved twice. I don't think it's actually resolving twice. It's likely some weird side effect of the async iterator itself. I'll see if I can isolate what is causing it. |
Looks like |
That's what it's looking like from what I can see. It appears to have something to do with the way the async iterator's promise is resolved. |
That, btw, should not block this since async iterators aren't enabled by default yet. I've been unable to recreate the issue any other way. |
@gsathya May have thoughts on where these multiple resolutions are coming from. |
@addaleax @jasnell So there are potentially future promise events that wouldn't be covered by this and the existing 4 events? Brain dumping on this, but what about something like |
There potentially are but it's just as likely that there won't be, which is what makes it difficult. Really the only possibility I can reasonably imagine right now would be a To be honest tho, I'd rather go with the approach of passing additional hooks on the original object the way this PR does. |
Where do we stand here? To me it looks like this could land as is? And is this actually semver-minor? I guess not because async_hooks is still experimental? |
@BridgeAR Yeah, I think this is ready, I appreciate the ping. |
Fixed linter error & landed in b605b15 |
Add a `promiseResolve()` hook. PR-URL: #15296 Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Add a `promiseResolve()` hook. PR-URL: nodejs/node#15296 Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Add a `promiseResolve()` hook. PR-URL: #15296 Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Add a
promiseResolve()
hook./cc @nodejs/diagnostics @nodejs/async_hooks
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
async_hooks