I'm trying to understand how exceptions are handled asynchronously -- I have a webserver that contains a lambda handler for processing requests (uWebsockets) and it keeps crashing. To simulate the scenario I used std::async
void call(function<void()> fn) {
std::async([&fn]{
fn();
});
}
int main() {
try {
call([](){
throw std::runtime_error("Oops");
});
} catch (std::runtime_error &e) {
cout<<"Caught the error"<<endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100000));
}
It seems that running the function inside std::async causes the runtime error to never execute ... the program just exits as if nothing happened... why is this the case?
CodePudding user response:
Let's take this step by step. Let's start with the very definition of what std::async
does. It:
runs the function f asynchronously (potentially in a separate thread
The word "potentially" is a distraction here, but the point is that a different execution thread is going to be involved here. For all practical reasons, the function getting std::async
-ed can, and on modern C implementations, it will run in a new execution thread.
The very reason for having different execution threads, in the first place, is that they are completely independent of their parent execution thread. Different execution threads must properly implement sequencing operations in order to mutually exchange information between themselves, in some form or fashion.
try {
} catch (std::runtime_error &e) {
cout<<"Caught the error"<<endl;
}
The concept of exceptions, and how try
and catch
relate to them, is introduced in your average C textbook by explaining how try
/catch
will catch exceptions that occur inside the try
/catch
block. Once execution leaves the try
/catch
block, exception handling is no longer in effect, and no exception will be caught (unless execution is inside another try
/catch
block).
It may or may not be immediately clear that combining the concepts of execution threads and this typical explanation of exception handling, you must reach the inevitable conclusion that you can only catch exceptions thrown from the same execution thread.
Additionally, in the shown code, there's absolutely nothing that even guarantees that the original execution thread will still be inside the try
/catch
block when the exception in the other execution thread gets thrown. In fact, it is more likely than not that the parent execution thread has left the original try
/catch
block and it's sleep_for
-ing, when the exception gets thrown. You cannot catch exceptions any more, after leaving the try
/catch
block.
But even if the original execution thread is still inside the try
/catch
block this won't make any difference, because thrown exceptions can only be caught by the same execution thread, but the function that gets executed by std::async
will be running in a completely different execution thread.
Edit:
std::async([&fn]{
This captures fn
by reference. fn
is a function parameter that goes out of scope and gets destroyed when the function returns, but there's no guarantee that the new execution thread will reference it before it does. This is immaterial, for the purposes of the mechanics of exception handling (and to how std::async
itself handles thrown exceptions), but this needs to be fixed nevertheless.
CodePudding user response:
The reason that you do not observe the exception in the main thread is that the value of the future
that async
returns is never inquired. If you extend your implementation of call
like this:
void call(function<void()> fn) {
std::async([&fn]{
fn();
}).get(); // .get() added here
}
then the exception is observed because the function waits for the result.
Even without this modification, call
has well-defined behavior despite @SamVarshavchik's analysis because the future
that async
returns is special in a particular way: its destructor waits for the function to complete (see the Notes section there). That is, call
does not return until the called thread has completed (thrown the exception).