You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
N-API request: Ability to report fatal exception resulting from callbacks that are thrown outside the regular JS call stack #15371
I am attempting to (re)write a node module to use N-API, but there is an issue that I cannot see a workaround for.
I want to use uv_poll_t/uv_poll_init_socket etc. to set up socket polling to handle socket events in the native addon. In the native callback there is a code path that executes a JS callback. Looks something like this:
// env is stored in data field of the uv_poll_t handle
Napi::HandleScope scope(env);
Value().Get("emit").As<Napi::Function>().Call(Value(), {
Napi::String::New(env, "message"),
msg.ToBuffer(env),
});
Unfortunately any exception thrown in the JS callback will crash the process. Instead I want to report a fatal exception to Node, so that it can be delegated to the uncaughtException event of process.
Essentially, I want to use this code, identical to what is used in the async callback:
But there seems to be no way to do this. Even if I include v8.h and node.h I cannot convert the exception from N-API back to a v8 exception so I can call node::FatalException.
Ideally I'd like either:
Ability to call node::FatalException() via a supported addition to N-API (so that I can cause an uncaughtException event to be emitted); or
Ability to use uv_poll_t/uv_poll_init_socket etc via a supported addition to N-API, similar to napi_create_async_work etc.
changed the title [-]N-API request: Ability to report fatal resulting from callbacks that are outside the regular JS callstack[/-][+]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS callstack[/+]on Sep 12, 2017
changed the title [-]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS callstack[/-][+]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS call stack[/+]on Sep 12, 2017
changed the title [-]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS call stack[/-][+]N-API request: Ability to report fatal exception resulting from callbacks that are thrown outside the regular JS call stack[/+]on Sep 12, 2017
There is a napi_fatal_error() API that was added recently. It hasn't been added to the C++ wrapper yet, but meanwhile you should be able to call the C function. Does that meet your needs?
Unfortunately it doesn't seem like it would fit my needs. napi_fatal_error() calls node::FatalError() which prints a message and aborts. I want the uncaughtException handler to be invoked, which seems to be done by node::FatalException().
Having a similar wrapper for node::FatalException(), for example napi_fatal_exception() would work (although honestly the two function names would be easily confused).
But looking at that node::FatalException is already called. Maybe I misunderstand how to properly call back to Javascript from uv_poll_init_socket/uv_poll_start?
@rolftimmermans I think that's probably the correct fix. @addaleax Can you comment on this? You're more familiar with the details of CallbackScope and MakeCallback() than I am.
Not really … MakeCallback is basically CallbackScope + a function call, so you probably don’t want to do MakeCallbacks inside a CallbackScope (or at least that’s redundant). But more importantly, if you do it this way, you won’t get to catch exceptions thrown by MakeCallback if you actually do want to handle them.
(The origin of the reported issue here is that NAPI_PREAMBLE always sets up a try/catch block; and I don’t think we can disable that without breaking more code.)
So, ugh. This is not trivial. The most natural solution might just be to parametrize NAPI_PREAMBLE so that it can also create a verbose TryCatch, but that comes with significant API churn.
After that, maybe we could expose something that leads to the same code paths that Node’s own native uncaught exception handler also calls. I am not sure, but just adding a function that throws some input parameter as an exception inside a verbose TryCatch might work.
(And while I’m writing this: I’m not understanding myself rn why napi_throw works? It does use NAPI_PREAMBLE, so it looks like all it’s doing is throwing an exception inside a TryCatch …?)
I’m not understanding myself rn why napi_throw works? It does use NAPI_PREAMBLE, so it looks like all it’s doing is throwing an exception inside a TryCatch …?
The exception thrown in napi_throw is caught by the TryCatch at that point, and saved in env->lastException. Then before the native callback returns, the env->lastException is rethrown so that it propagates out to the JavaScript caller.
I don't understand how a verbose TryCatch would help here: it would still allow an exception to result in a crash instead of being reported via the uncaught-exception handler, right?
It looks like we just need to add a napi_fatal_exception() API. We had intended to support custom async handling without using the napi_async_work APIs, which are just meant to be a convenience for the most common pattern. Without the ability to throw a fatal exception, it isn't possible to fully replicate the correct async behavior.
Ooh, I missed that TryCatch in node_api.cc isn’t v8::TryCatch. Sorry.
I don't understand how a verbose TryCatch would help here: it would still allow an exception to result in a crash instead of being reported via the uncaught-exception handler, right?
As far as I understand it, the difference is that a verbose TryCatch would report the exception from the engine as if it was an uncaught one. I think that would work, or at least I can’t remember anything that would stand in the way of it.
(Which can also be in the form of an napi_fatal_exception() API, of course.)
Activity
[-]N-API request: Ability to report fatal resulting from callbacks that are outside the regular JS callstack[/-][+]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS callstack[/+][-]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS callstack[/-][+]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS call stack[/+][-]N-API request: Ability to report fatal exception resulting from callbacks that are outside the regular JS call stack[/-][+]N-API request: Ability to report fatal exception resulting from callbacks that are thrown outside the regular JS call stack[/+]jasongin commentedon Sep 12, 2017
There is a
napi_fatal_error()API that was added recently. It hasn't been added to the C++ wrapper yet, but meanwhile you should be able to call the C function. Does that meet your needs?rolftimmermans commentedon Sep 13, 2017
Unfortunately it doesn't seem like it would fit my needs.
napi_fatal_error()callsnode::FatalError()which prints a message and aborts. I want theuncaughtExceptionhandler to be invoked, which seems to be done bynode::FatalException().Having a similar wrapper for
node::FatalException(), for examplenapi_fatal_exception()would work (although honestly the two function names would be easily confused).bnoordhuis commentedon Sep 13, 2017
What happens when you use
napi_make_callback()instead ofFunction::Call()?rolftimmermans commentedon Sep 13, 2017
It seems the stack trace and abort is the same:
But looking at that
node::FatalExceptionis already called. Maybe I misunderstand how to properly call back to Javascript fromuv_poll_init_socket/uv_poll_start?rolftimmermans commentedon Sep 14, 2017
Upon further investigation it seems it is broken fornapi_create_async_worktoo. I guess I might be waiting for #14697.rolftimmermans commentedon Sep 15, 2017
#14697 solves a lot of problems, but the process uncaught exception handler is still not called by
napi_make_callback.rolftimmermans commentedon Sep 15, 2017
I have found a solution/workaround/hack.
The following patch solves my problems
I really don't know whether or not this makes sense, but hopefully it is useful for someone more knowledgeable about the internals.
jasongin commentedon Sep 15, 2017
@rolftimmermans I think that's probably the correct fix.
@addaleax Can you comment on this? You're more familiar with the details of
CallbackScopeandMakeCallback()than I am.addaleax commentedon Sep 15, 2017
Not really …
MakeCallbackis basicallyCallbackScope+ a function call, so you probably don’t want to doMakeCallbacks inside aCallbackScope(or at least that’s redundant). But more importantly, if you do it this way, you won’t get to catch exceptions thrown byMakeCallbackif you actually do want to handle them.(The origin of the reported issue here is that
NAPI_PREAMBLEalways sets up a try/catch block; and I don’t think we can disable that without breaking more code.)So, ugh. This is not trivial. The most natural solution might just be to parametrize
NAPI_PREAMBLEso that it can also create a verboseTryCatch, but that comes with significant API churn.After that, maybe we could expose something that leads to the same code paths that Node’s own native uncaught exception handler also calls. I am not sure, but just adding a function that throws some input parameter as an exception inside a verbose
TryCatchmight work.(And while I’m writing this: I’m not understanding myself rn why
napi_throwworks? It does useNAPI_PREAMBLE, so it looks like all it’s doing is throwing an exception inside aTryCatch…?)jasongin commentedon Sep 16, 2017
The exception thrown in
napi_throwis caught by theTryCatchat that point, and saved inenv->lastException. Then before the native callback returns, theenv->lastExceptionis rethrown so that it propagates out to the JavaScript caller.I don't understand how a verbose
TryCatchwould help here: it would still allow an exception to result in a crash instead of being reported via the uncaught-exception handler, right?It looks like we just need to add a
napi_fatal_exception()API. We had intended to support custom async handling without using thenapi_async_workAPIs, which are just meant to be a convenience for the most common pattern. Without the ability to throw a fatal exception, it isn't possible to fully replicate the correct async behavior.addaleax commentedon Sep 16, 2017
Ooh, I missed that
TryCatchinnode_api.ccisn’tv8::TryCatch. Sorry.As far as I understand it, the difference is that a verbose
TryCatchwould report the exception from the engine as if it was an uncaught one. I think that would work, or at least I can’t remember anything that would stand in the way of it.(Which can also be in the form of an
napi_fatal_exception()API, of course.)mafintosh commentedon Feb 28, 2018
Just ran into this same issue, anyway I can help get this fixed?
mhdawson commentedon Mar 18, 2018
Working woth the test case in nodejs/node-addon-api#235, I'm thinking I'll add
mafintosh commentedon Mar 18, 2018
@mhdawson #19337
mhdawson commentedon Mar 29, 2018
Believe this was closed by #19337, please re-open if you believe that is not the case.