Repository navigation
uncaughtException not called with http.get() #5555
Description
Activity
Wrapped that one up as a test that uses common / assert.
Here's a fun bit... everything hangs on v5, but passes on v4. BUT if we introduce a timeout (as we would need for the test) everything passes.
I am going to bisect and find out what introduced this weirdness
Git bisect is showing that the regression was created by #5419
/cc @chrisdickinson @indutny @trevnorris
edit: here is the test I used --> https://gist.github.com/TheAlphaNerd/6615a27684deb682dfe7
- added a commit that references this issue
on Mar 3, 2016 - addedhttpIssues and PRs related to the http subsystem.Issues and PRs related to the http subsystem.
on Mar 4, 2016 @trevnorris In #4507, we had talked about the fact that not having an external (
TryCatch) handler on the stack innode::AsyncWrap::MakeCallbackand innode::MakeCallbackwould still generate a message that would be handled bynode::FatalException, but after double-checking it seems clear that this cannot happen, and an external (TryCatch) handler is needed on the stack to generate such a message and eventually emituncaughtExceptionon theprocessobject. My apologies for suggesting otherwise.Putting back an external verbose handler on the stack in
node::AsyncWrap::MakeCallbackandnode::MakeCallbackfixes the bug described in this issue, but it also has the problems we talked about previously in #4507: when a callback called byMakeCallbackthrows, the execution of the script continues even if there's no try/catch handler.I need to improve my knowledge about V8's exception handling before being able to help figuring out a way to solve that problem without introducing this regression.
BUT if we introduce a timeout (as we would need for the test) everything passes.
I'm assuming you were referring to the following code from https://gist.github.com/TheAlphaNerd/6615a27684deb682dfe7:
setTimeout(function() { console.log('test'); common.fail('the process should throw and not timeout'); server.close(); }, common.platformTimeout(1000))The reason why that ends up emitting an
uncaughtExceptionevent is thatcommonis not defined, and so V8 throws an exception when running the script itself, andMakeCallbackis not involved. At that time, a verbose external handler is on the stack and so the message corresponding to the error is handled as expected bynode::FatalException.Replacing the first line of that gist with
var common = require('../common')hangs in the same way.@misterdjules I say we add the
TryCatchinMakeCallbackand then figure out how to deal with script execution continuing. Usage ofMakeCallbackthis way isn't documented yet, and as far as I'm concerned could be the "intended" behavior. Either way it'll fix this issue, which is more pressing.@trevnorris Sounds good to me.
Almost have it working. Unfortunately is breaking the following from
test-http-parser.js:parser[kOnHeadersComplete] = function(info) { throw new Error('hello world'); }; parser.reinitialize(HTTPParser.REQUEST); assert.throws(function() { parser.execute(request, 0, request.length); }, Error, 'hello world');
The error is able to bubble all the way and not be caught by
assert.throws(). Not sure why that is. @misterdjules have any ideas?So it looks like
SetVerbose(true)is causing the exception to immediately bubble up toFatalExceptionand ignore the fact that it's wrapped in atry/catchin JS. Though I can't remember the consequence for not usingSetVerbose(true).Almost have it working. Unfortunately is breaking the following from test-http-parser.js:
parser[kOnHeadersComplete] = function(info) {
throw new Error('hello world');
};parser.reinitialize(HTTPParser.REQUEST);
assert.throws(function() {
parser.execute(request, 0, request.length);
}, Error, 'hello world');The error is able to bubble all the way and not be caught by assert.throws(). Not sure why that is. @misterdjules have any ideas?
If you put back external exception handlers (
TryCatchinstances) inMakeCallbackand still useMakeCallbackinnode::Parser, it means that theMakeCallback's external exception handler is above the test's JavaScript exception handler in the stack.Therefore, that JavaScript exception handler is not the one found when unwinding the stack to find the appropriate exception handler, and instead the external exception handler is found. Because that exception handler is verbose, a message is emitted though, which is handled by
FatalExceptionand ultimately makes the process exit, giving the impression that the error bubbled up all the way to the top of the stack.Using plain
Function::Callcalls instead ofMakeCallbackcalls innode::Parserlets exception thrown in node's HTTP parser's JS code bubble up to any JavaScript exception handler.So it looks like SetVerbose(true) is causing the exception to immediately bubble up to FatalException and ignore the fact that it's wrapped in a try/catch in JS. Though I can't remember the consequence for not using SetVerbose(true).
See my previous comment above. Basically what's happening with the current 5.7.1 version is:
- Node sets an external exception handler on the stack and runs the top-level script.
- The top level script is done running, and node enters the libuv event loop, but the external exception handler is gone from the stack.
- When there's something to read on a socket,
StreamBase::EmitDatacallsMakeCallback, which doesn't set an external exception handler on the stack. - The callback called by
MakeCallbackcalls the node's HTTP parser (node::Parser) which also callsMakeCallback, and doesn't set an external exception handler. - An exception is thrown and not caught, but there's no external exception to propagate it too, and thus V8 doesn't report the message corresponding to the exception, and
FatalExceptionis not called.
Now what if we add an exception handler back in
MakeCallbackand keep usingMakeCallbackinnode::Parser? We get the following:- Node sets an external exception handler on the stack and runs the top-level script.
- The top level script is done running, and node enters the libuv event loop, but the external exception handler is gone from the stack.
- When there's something to read on a socket,
StreamBase::EmitDatacallsMakeCallback, which does set an external exception handler on the stack. - The callback called by
MakeCallbackcalls the node's HTTP parser (node::Parser) which also callsMakeCallback, and does set a new external exception handler. - An exception is thrown and, even if it's caught by some user's JavaScript code, the external exception handler set in 4) is above that JavaScript handler on the stack, so the user's JavaScript exception handler won't run.
- V8 does report the message corresponding to the exception, because the external handler set in 4) is verbose.
And then finally, the following happens when we put back external exception handlers in
MakeCallbackand we useFunction::Callinnode::Parser:- Node sets an external exception handler on the stack and runs the top-level script.
- The top level script is done running, and node enters the libuv event loop, but the external exception handler is gone from the stack.
- When there's something to read on a socket,
StreamBase::EmitDatacallsMakeCallback, which does set an external exception handler on the stack. - The callback called by
MakeCallbackcalls the node's HTTP parser (node::Parser) which doesn't callMakeCallbackbut instead callsFunction::Call, and thus does not set a new external exception handler. - An exception is thrown and not caught, and there's an external exception to propagate it too, but it's below the user's JavaScript exception handler on the stack in
test-http-parser.js, so the user's JavaScript exception handler runs. - However, in the case of the repro code of this issue, V8 does report the message corresponding to the exception, because there is a verbose external exception handler (set at step 3) on the stack and there's no user JavaScript exception handler on top of it, therefore
FatalExceptionis called.
This last behavior is what fixes this issue, and
test-http-parser.js.@misterdjules I think the following is pretty much what you explained: trevnorris@8d7b346
While that patch does fix all tests, problem is it now looses calls to the pre/post callbacks of async wrap.
7 remaining items
- added 2 commits that reference this issue
on Mar 8, 2016 - added a commit that references this issue
on Jul 12, 2016

With the following example, I would expect uncaughtException to be triggered. It works on v4.x but not on latest v5.