-
Notifications
You must be signed in to change notification settings - Fork 30k
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: use new v8::Context PromiseHook API #36394
Conversation
fb63787
to
6567930
Compare
This is amazing work. I still think nodejs/diagnostics#389 is the best long-term solution, but regardless there will always be a huge value in being able to fully track promises. |
@Qard looks very promising. |
@AndreasMadsen That can actually be fully achieved on the consuming end of this PromiseHook API, so we can always try that later. Because it's a major breaking change though, I don't want to mix that in here. For the time being, my goal is to just make what already works faster. 😸 |
trackPromise(promise, parent); | ||
const asyncId = promise[async_id_symbol]; | ||
const triggerAsyncId = promise[trigger_async_id_symbol]; | ||
emitInitScript(asyncId, 'PROMISE', triggerAsyncId, 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.
It has previously been discussed to not expose the actual promise object via async_hooks
(and eventually PromiseHooks
), in the hope that one day we could enable escape analysis for promise objects. See #23443 and nodejs/diagnostics#188 (comment)
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.
That only applies when using PromiseWrap though, which is quite expensive and therefore I've been working towards removing it. Also, AsyncLocalStorage requires the promise, or a distinct resource with the same lifetime as the promise itself, to work properly so there's not currently a way to eliminate that. I'm happy to hear any other suggestions you can think of to handle that.
I've been doing this work, and my prior async_hooks work this year, based on that PromiseHook proposal doc and the later discussions at the summit in Munich. I went for a slightly different compromise path which achieves mostly all the performance wins available in those ideas while avoiding significant breaking changes. This will (hopefully) allow the changes to be backported. We can then consider the remaining breaking parts later. The one part I do still intend to look into from that doc in the short-term is if I can replace PromiseWrap
gc-tracking with WeakRef
finalizers instead.
Local<Value> after_hook = args[2]; | ||
Local<Value> resolve_hook = args[3]; | ||
ctx->SetPromiseHooks(init_hook, before_hook, after_hook, resolve_hook); | ||
} | ||
|
||
static void DisablePromiseHook(const FunctionCallbackInfo<Value>& args) { |
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.
EnablePromiseHook()
/DisablePromiseHook()
still suffer from the problem mentioned in the design doc that they perform per-Isolate actions, when what we want are per-Context actions… we could make that go away with the new hook functions, though, right? We’d just need to make the init_hook
function here do a call into C++ to set up the GC tracking if there any destroy
hooks, correct?
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.
Yes, the destroy-hook fallback remains unchanged currently, so it still suffers from the isolate issues. I'm hoping to eliminate that eventually, but it may have to wait until later. My plan is to eliminate the switch to native hooks for destroy hooks and instead just apply gc-tracking from the JS side. Might make the gc-tracking a bit more expensive, but I suspect the benefit of the rest of the hooks moving to JS should more than offset that.
I'll be getting back to this starting tomorrow. Just been taking some time off from code. 😅 |
bbd31df
to
607e836
Compare
I've updated with the latest V8 changes. As far as I'm aware the V8 stuff should be ready to land now, just waiting on approval. Once that gets in I can make a proper backport commit for the V8 change and then this should be safe to land on top of that. :) |
The V8 changes have moved to https://chromium-review.googlesource.com/c/v8/v8/+/2759188. Hopefully I can get that landed soon. 😅 |
🤦 Might help if I remember to disable the _other_ promise hook implementation when switching between them... Fixes nodejs#38814 Fixes nodejs#38815 Refs nodejs#36394 PR-URL: nodejs#38912 Backport-PR-URL: nodejs#38577 Reviewed-By: Vladimir de Turckheim <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: Bryan English <[email protected]>
I've been working on an alternative to the existing PromiseHook API in V8 using split JSFunctions per event type rather than the old way which routes everything through a single C++ function even though it's just going right back to JS in the end. The old way has many, many costly boundary jumps and deopts in many cases, so we really need a better way to track promise lifecycle events. 😅
The corresponding Chromium Review is here:
https://chromium-review.googlesource.com/c/v8/v8/+/2466783https://chromium-review.googlesource.com/c/v8/v8/+/2759188As the change has not yet landed in V8, I'm opening this as a draft PR just to show my progress and give people something to look at. I've got some nice benchmarks to share too!
Before:
After:
It's getting about 3-4x better performance. Not quite on-par with disabled yet, but getting close. There is still much that can be done to improve those numbers though.
The current changes do not allow the optimizer to do its magic in certain cases so I'm hoping to dig a bit more into getting those cases to optimize too. For reference though, the existing PromiseHook API bails out too, so this is quite a bit faster already. I'm also investigating moving the
PromiseWrap
used when there's a destroy hook to using this model and wrapping from JS instead, which I think should still be an overall performance improvement despite the hop back to native for the initial wrap.cc @nodejs/diagnostics @nodejs/v8
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes