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
lib: rewrite AsyncLocalStorage without async_hooks #48528
base: main
Are you sure you want to change the base?
Conversation
|
Review requested:
|
|
Also worth considering that as this no longer depends on |
b35d35c
to
8b30200
Compare
|
Some additional notes:
|
|
Woo! Thanks for getting this started. The need for the flag is unfortunate but definitely required given how chromium is using the API and the conflict that causes with electron. But otherwise this should be a major improvement over the current state. Will do a thorough review this weekend. |
|
Another note: This currently uses a model of building a linked-list of AsyncContextFrames which each contain only the key and value they were constructed with. The alternative which is what was originally proposed was to use a map which is cloned from the parent frame at each frame construction and then the single additional entry for that frame is added but it is considered otherwise immutable. I went with the current model mostly because it was easier and made debugging simpler but I plan to spend some time benchmarking the two approaches to identify the trade-offs and figure out which approach would be generally better. The one definite downside to the current model is it holds past values for the same store from further up the call graph alive longer which may be undesirable but may only really apply to top-level scopes that will need to be kept alive anyway. Further analysis should provide some better insight into the behaviour. |
|
This is awesome!
That'd be a welcome change. We've been trying to eliminate async_hooks usage in our apps (as we've detected performance issues when async_hooks are misused), and having to import AsyncLocalStorage from the |
|
Thenables propagation fix is here: https://chromium-review.googlesource.com/c/v8/v8/+/4674242 |
|
I propose tagging this as semver-major. |
|
@mcollina Could do that. Though the way in which we land it is still not entirely decided. Due to the Electron conflict it needs build-flagging anyway, so this could be landed as a minor if it's by default turned off. It could land off by default and then have a follow-up to turn it on by default as a major. What do you think? |
|
That works |
|
I've got all but
|
|
The This is essentially what |
e009382
to
6d26f70
Compare
6d26f70
to
5a7b995
Compare
|
I've now got the builtins working against active context rather than creation context. With that all existing tests are now passing. Final benchmark results are: I'll get started on a CR to add the builtins to V8 and will update this again once we can get the changes for that and thenables support backported. |
|
I've submitted a V8 CR to add the TurboFan builtins for the get/set functionality. I'm also still waiting for reviews on the thenables fix. Feel free to review/comment on either. Once those both land I'll get them backported to Node.js and then update this PR. 🎉 |
|
How would people feel about this being behind a runtime flag rather than a build flag? I'm trying to figure out how to get CI testing against both implementations and it seems like it would be a lot easier if it was a runtime flag instead. I feel like it should be fine as the conflict with Electron would only happen if we actually call the get/set functions so we could just escape before those would be run and have basically the same behaviour as we have now. 🤔 The actual changes to the original version are very minimal as the new version is basically entirely standalone. The only really notable change is just moving some things out to separate files and the mode switching on detection of the AsyncContextFrame class. As long as I configure it to never call the set/get functions in either C++ or JS, it should actually be safe to land as a minor behind a flag, at least in my opinion. That also gives us the potential to backport which would be highly desirable for us APM providers as we could at least advise customers on older versions to use the flag for better performance in the event they have issues with the performance of the existing code. |
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.
Amazing work! This has been on my notification list for so long, but I couldn't look at it earlier - sorry about that.
How would people feel about this being behind a runtime flag rather than a build flag?
What's the reasoning behind using it through a build flag? Looks like the parts added by NODE_USE_NATIVE_ALS via pre-processor could be easily handled by if (UNLIKELY(native_als_enabled)). Until we make the full transition to native als, that would be the best option to keep on track.
I'm +1 landing it as an experimental runtime flag. That said, it's missing some docs.
|
Would be worth it to trigger https://ci.nodejs.org/job/node-test-commit-v8-linux/ since it manually patches v8. |
Runtime flag would be preferable if possible. We don't test every build flag in the CI. For CI runs, the majority of the time is compilation/building rather than test execution. |
|
BTW, on vacation this week, so there won't be any movement on this until next week. Shouldn't take too long to figure out runtime flagging though. 🙂 |
It would not even happen unless someone turns on the runtime flag right? So I don't think conflicts with Electron would be a problem, there are probably plenty of flags out there that do not work quite well with Electron anyway... On the other hand I wonder why the V8 API cannot use e.g. an FixedArray of continuation data, instead of just allowing one entry. The builtins can just be extended to take an index to that array. It doesn't even have to grow, a fixed number of entries may already be enough (like how each isolate only have 4 fixed data slots). That's been how we handle the multi-tenancy issue with e.g. context embedder data too (which is growable though) |
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.
Tried my best to review this, it's not that easy.
I hope I understood it correct and haven't overseen vital areas which need to be touched.
c631cb4
to
4b24936
Compare
ec22f3b
to
193052a
Compare
193052a
to
161d72b
Compare
| const { AsyncContextFrame } = require('internal/async_context_frame'); | ||
|
|
||
| let hasAsyncContextFrame_; | ||
| function hasAsyncContextFrame() { |
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.
Is there a significant performance improvement in copying this into several files?
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.
Probably not. Could be moved into internal/async_context_frame. It was just a direct conversion of the previous code which checked by existence of the AsyncContextFrame export of the native binding.
|
I think there is a difference regarding behavior after I doubt that this difference is critical because |
I'm working on rewriting AsyncLocalStorage to use v8::SetContinuationPreservedEmbedderData(...) in a similar model to Cloudflare and in anticipation of the upcoming AsyncContext proposal.
This is not done yet but is mostly working as-is. I have not got to benchmarking or writing tests for it yet, but it seems to be passing most of the existing AsyncLocalStorage. 😄
cc @nodejs/diagnostics @nodejs/performance