-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
v8: patch for thenables in PromiseHook API #33560
Conversation
Thenables produce an additional promise which is not currently emitting before/after hooks from the PromiseHook API in V8. This change makes that additional promise also emit the proper events.
I can submit this upstream in V8 if you would like. |
Thank you for doing this, this change looks a lot better to me than #33189 in terms of the performance impact |
@devsnek That'd be much appreciated! I'll get to work on figuring out a V8 test for it too. :) |
@@ -198,11 +198,20 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( | |||
const TNode<Object> thenable = LoadObjectField( | |||
microtask, PromiseResolveThenableJobTask::kThenableOffset); | |||
|
|||
// Run the promise before/debug hook if enabled. | |||
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, |
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 looking through the history of promise hooks (v8:4643 and such) trying to understand why this would've been left out and I haven't found anything, but it does seem suspicious...
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.
It looks like it was just missed. I think "thenables" just weren't considered. I don't see anything in v8:4643 suggesting otherwise.
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.
Agreed. They have it for the other branches in there, but not thenables. Makes me think there could be a specific reason for that. As far as I can tell though it's just been overlooked. In testing it locally though, it fixes the problem perfectly for our needs.
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 think there was a discussion at diagnostics summit in munich if v8 should call promise hooks for non user visible promises or not. The agreement was it is not needed as it improves performance and assumption was that no hooks are needed for "invisible" promises. Promisehooks are quite expensive for v8 - in special since they did more optimizations of promises/await that time.
To me this looks like some corner case regarding visibility of the promise created here. The Promise itself is not visible in the sense that user can access it as it is only used within the await
statemachine. On the other hand the side effects are visible.
Thanks, @devsnek! |
This seems the path to follow, good work! Have you run the async_hooks benchmarks to see if this impacts at all? |
Good finding! I expect the impact havily depends on the sample. Currently we have no sample using thenables or non native promises. |
I haven't run it through the benchmarks, but just structurally, it's a branch within another branch so it should have zero cost in any non-thenable case, and only a single load and branch in the thenable case, so basically zero. 🤷 |
@quad Could you explain how this integrates with the async_hooks init hook? I see it emits before and after, but what about init? |
… On 30 May 2020, at 23:48, Andreas Madsen ***@***.***> wrote:
@quad <https://github.com/quad> Could you explain how this integrates with the async_hooks init hook? I see it emits before and after, but what about init?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#33560 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAF3WMEFSLCC7ELYXTDK6TRUEFCLANCNFSM4NJ2C42A>.
|
So what we have now with a thenable is a wrapper promise with all lifecycle events and the thenable itself which only lacks before/after events. It was actually already producing init and promiseResolve just fine, it just lacked the microtask queue codegen stuff to emit before/after events for that specific type of promise job. If you run this on master: 'use strict';
const { AsyncLocalStorage, createHook } = require('async_hooks');
function hookFn(name) {
return (asyncId, type, triggerId) => {
let message = `${name} ${asyncId}`;
if (name === 'init') {
message += ` for ${type} from ${triggerId}`;
}
process._rawDebug(message);
};
}
createHook({
init: hookFn('init'),
before: hookFn('before'),
after: hookFn('after'),
promiseResolve: hookFn('promiseResolve')
}).enable();
const store = new AsyncLocalStorage();
function thenable() {
return {
then(cb) {
console.log('store in then is', store.getStore());
process._rawDebug('before then');
setImmediate(cb);
process._rawDebug('after then');
}
};
}
store.run({}, async () => {
process._rawDebug('before thenable()');
await thenable();
process._rawDebug('after thenable()');
console.log('store after run is', store.getStore());
}); You'll get this:
Note the lack of before/after events for the promise with async id 4. Now if you run this again after the fix, you get this:
Here you can see the promise with async id 4 does run the before and after hooks, and the store check within the |
Thanks, this is great 🙂 |
8ae28ff
to
2935f72
Compare
@Qard can you run some benchmarks with this enabled and disabled? |
@devsnek Yep. I'll put something together on Monday. 👍 |
This is a run of async function run(n) {
for (let i = 0; i < n; i++) {
await { then(cb) { cb(); } };
}
}
|
The fix has landed in V8. How do we proceed from here? Float a patch until we update to the version of V8 with the fix? Can we get that backported too? |
@Qard cherry-picking it into our v8 should be fine. |
@Qard would you like to change this PR into a cherry-pick from upstream? |
Yep, sounds good to me. I’ll see about doing that on Monday. 🙂 |
Just tried it locally and it seems to be stuck spinning at "apply patch" forever. I left it and came back an hour later to find it still spinning. 🤔 |
Ah, it appears to be due to nodejs/node-core-utils#421 which looks like it's been fixed but the fix has not yet been released. 🤔 |
PR to cherry-pick the v8 fix: #33778 |
Thenables produce an additional promise which is not currently emitting before/after hooks from the PromiseHook API in V8. This change makes that additional promise also emit the proper events.
I spent some time digging into V8 trying to figure out a fix for the thenables issue which I previously attempted a higher-level fix for in #33189 and got some resistance. With this change,
async_hooks
and thereforeAsyncLocalStorage
will now be able to correctly propagate context into thethen(...)
method call of a thenable.I'm submitting this as a PR here to seek help in getting this change into V8 and for us to have the option to land it as a patch, if we want. I haven't written tests for it, though I've tested it manually, as I'm not yet sure how we want to approach landing this.
cc @nodejs/v8 @nodejs/diagnostics @nodejs/async_hooks