-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Asyncwrap more #5756
Asyncwrap more #5756
Conversation
All green except for a single flaky test failure?! Must be an omen this is ready. |
I think @vkurchatkin had some code proposed using KickNextTick. So pinging. I'd also love to know where we stand on promises in AsyncWrap. |
What is the use case for the |
@AndreasMadsen Working through the logic of propagating the parent handle asynchronously I realized that the usual case is that var parentId = [];
function pre(id) {
parentId.push(id);
}
function post(id, didThrow) {
if (didThrow)
parentId.length = 0;
}
function final(id, didThrow) {
if (didThrow)
parentId.length = 0;
else
parentId.pop();
} The edge case is if a callback in the Though since this case is an exception the overhead from the additional logic shouldn't be a burden. Especially since the overhead for storing the parent on every new Reason I haven't included it here is because additional API is required to make it useful. I was considering something to the affect of @benjamingr Native Promise support (since that's all we could guarantee) depends on our willingness to either override some native methods in order to collect additional information, or the v8 API adding additional hooks so we can see into the MicrotaskQueue's black box. i.e. will be happy to include support as soon as we have the necessary APIs to do so. |
@trevnorris ... what shape do you expect asyncwrap to be in by the time we're looking to cut v6? |
Local<Value> final_v = | ||
fn_obj->Get(FIXED_ONE_BYTE_STRING(env->isolate(), "final")); | ||
Local<Value> destroy_v = | ||
fn_obj->Get(FIXED_ONE_BYTE_STRING(env->isolate(), "destroy")); |
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.
Can you use the Context-taking, MaybeLocal-returning overload of Get() here?
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.
Can do.
@trevnorris I'm still not sure I understand the purpose of the final hook. Is it for performance reasons or an alternative to monkey patching
Can you elaborate on what is not included?
That seams dangerous, because one can't distinguish between a defined parent relation (TCP) and a guessed/implicit parent relation (from |
It's mainly an alternative to patching The goal is simply to allow the parent context to propagate. Which can be achieved by grouping all
If no What's not included is the retaining of the parent id when a const async_wrap = process.binding('async_wrap');
const net = require('net');
const async_map = new Map();
function init(id, provider, parent_id) {
async_map.set(parent_id, this);
}
// setupHooks, enable, etc.
net.createServer(function(conn) {
process.nextTick(function throwMe() {
throw new Error('ah crap');
});
process.nextTick(function forTheFuture() {
// This callback will not run immediately after the above throwMe().
// Meaning the parent id of this AsyncWrap constructor will not be
// the same as the connection it's currently queued in. For this
// reason the parent id must be stored so it can correctly propagate.
net.connect({port: 8123}, () => {});
});
});
process.on('uncaughtException', function() { }); As far as processing Promise callback in the
The relationship is easy to track. I've already done this before in my initial async listener implementation. Except in the case that 1) the parent id is propagated manually in cases like a new TCP connection; 2) implementation details of timers creating multiple JS handles for a single It will be easiest for me to PR the implementation with tests as an explanation of how this works. Will do that this week. |
164bb15
to
5473c97
Compare
@@ -121,18 +122,35 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) { | |||
|
|||
if (env->async_hooks()->callbacks_enabled()) |
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 reason for requiring callers to explicitly disable callbacks, vs implicitly disabling them & restoring them?
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 missing context. Users have to explicitly run async_wrap.enable()
before callbacks will be called. This case is to handle:
const async_wrap = require('async_wrap');
async_wrap.setupHooks({ ...callbacks... });
async_wrap.enable();
async_wrap.setupHooks({ ...different callbacks... });
To prevent callbacks from being pulled out from underneath the user while operations may be in flight.
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.
Right, question is why is it the responsibility of the setupHooks()
caller to enable()/disable()
? i.e., why can't this be done implicitly by setupHooks()
?
Same question follows when the handlers are invoked. We know that if an init()
callback triggers an async operation, it risks infinite recursion, so why not implicitly disable async callbacks before invoking init()
?
Thanks, this makes sense. I'm a little worried about how the long–stack-traces are going to look if one uses the Also a test case for the final hook is missing. There is one for when the callbacks throws, but not for the common case (no-throw). |
Apologies if I'm being slow. Two questions:
Now, my question is, with the current set of changes, the nextTick operations of Sorry if these are long-winded. :) Let me know if anything is unclear and I'll do my best to clarify. Thanks, Mike |
Ah thanks. Missed that.
No worries. Head wrapping around complex asynchronous call chains and their associated edge cases is mind warping. I've learned this from hundreds of hours spent on the
net.createServer().listen().close() will fire init since the server is instantiated, but because no connection is ever received
Do you mean one of the async wrap callbacks, or the callback of the object handle passed to
Yes. As I mention above it's possible for execution to proceed even in the case of an exception. Which may execute callbacks in the Now, this does have a flaw that I'm still working out. That is, determining when the
Yes.
You can't. While it's cheap to store the parent on each At the moment I'm considering backing out the
To reiterate, it's prohibitively expensive to do this. Enabling async wrap with That explain everything? |
Sorry, I'm not following the 200k/sec. Do you mean the benchmark executes over 200k callbacks/second? Do you know how this impacts benchmark measurements? |
The sum of calls made to
I added the simple operation of tracking every handle in a |
@trevnorris Could we back out both the |
@AndreasMadsen Can do. I'll make a separate pr for the |
The second argument of the post callback is a boolean indicating whether the callback threw and was intercepted by uncaughtException or a domain. Currently node::MakeCallback has no way of retrieving a uid for the object. This is coming in a future patch. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Rather than abort if the init/pre/post/final/destroy callbacks throw, force the exception to propagate and not be made catchable. This way the application is still not allowed to proceed but also allowed the location of the failure to print before exiting. Though the stack itself may not be of much use since all callbacks except init are called from the bottom of the call stack. /tmp/async-test.js:14 throw new Error('pre'); ^ Error: pre at InternalFieldObject.pre (/tmp/async-test.js:14:9) Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Now that HTTPParser uses MakeCallback it is unnecessary to manually process the nextTickQueue. The KickNextTick function is now no longer needed so code has moved back to node::MakeCallback to simplify implementation. Include minor cleanup moving Environment::tick_info() call below the early return to save an operation. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Make comment clear that Undefined() is returned for legacy compatibility. This will change in the future as a semver-major change, but to be able to port this to previous releases it needs to stay as is. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
The second argument of the post callback is a boolean indicating whether the callback threw and was intercepted by uncaughtException or a domain. Currently node::MakeCallback has no way of retrieving a uid for the object. This is coming in a future patch. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Rather than abort if the init/pre/post/final/destroy callbacks throw, force the exception to propagate and not be made catchable. This way the application is still not allowed to proceed but also allowed the location of the failure to print before exiting. Though the stack itself may not be of much use since all callbacks except init are called from the bottom of the call stack. /tmp/async-test.js:14 throw new Error('pre'); ^ Error: pre at InternalFieldObject.pre (/tmp/async-test.js:14:9) Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Now that HTTPParser uses MakeCallback it is unnecessary to manually process the nextTickQueue. The KickNextTick function is now no longer needed so code has moved back to node::MakeCallback to simplify implementation. Include minor cleanup moving Environment::tick_info() call below the early return to save an operation. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Make comment clear that Undefined() is returned for legacy compatibility. This will change in the future as a semver-major change, but to be able to port this to previous releases it needs to stay as is. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
The second argument of the post callback is a boolean indicating whether the callback threw and was intercepted by uncaughtException or a domain. Currently node::MakeCallback has no way of retrieving a uid for the object. This is coming in a future patch. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Rather than abort if the init/pre/post/final/destroy callbacks throw, force the exception to propagate and not be made catchable. This way the application is still not allowed to proceed but also allowed the location of the failure to print before exiting. Though the stack itself may not be of much use since all callbacks except init are called from the bottom of the call stack. /tmp/async-test.js:14 throw new Error('pre'); ^ Error: pre at InternalFieldObject.pre (/tmp/async-test.js:14:9) Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Now that HTTPParser uses MakeCallback it is unnecessary to manually process the nextTickQueue. The KickNextTick function is now no longer needed so code has moved back to node::MakeCallback to simplify implementation. Include minor cleanup moving Environment::tick_info() call below the early return to save an operation. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Make comment clear that Undefined() is returned for legacy compatibility. This will change in the future as a semver-major change, but to be able to port this to previous releases it needs to stay as is. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
The second argument of the post callback is a boolean indicating whether the callback threw and was intercepted by uncaughtException or a domain. Currently node::MakeCallback has no way of retrieving a uid for the object. This is coming in a future patch. Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Rather than abort if the init/pre/post/final/destroy callbacks throw, force the exception to propagate and not be made catchable. This way the application is still not allowed to proceed but also allowed the location of the failure to print before exiting. Though the stack itself may not be of much use since all callbacks except init are called from the bottom of the call stack. /tmp/async-test.js:14 throw new Error('pre'); ^ Error: pre at InternalFieldObject.pre (/tmp/async-test.js:14:9) Ref: #7048 PR-URL: #5756 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Pull Request check-list
make -j8 test
(UNIX) orvcbuild test nosign
(Windows) pass withthis change (including linting)?
test (or a benchmark) included?
Affected core subsystem(s)
async_wrap
Description of change
Series of commits that update the
AsyncWrap
API in the effort to make it ready for public API. Significant changes are:final
callback that runs after thenextTickQueue
andMicrotaskQueue
has been depleted, or if in the process an exception was thrown but was caught by a domain or'uncaughtException'
handler.post
andfinal
callbacks that notify user if an exception was thrown and was caught by a domain or'uncaughtException'
handler. If this is true forpost
then itsfinal
callback will not be called.setupHooks()
now accepts an object instead of a series of arguments.'uncaughtException'
handlers to forcefully allow the exception to bubble and exit the process.R=@AndreasMadsen
R=@bnoordhuis
CI: https://ci.nodejs.org/job/node-test-pull-request/1946/