Before coroutines, when an exception was thrown out of a callback, like via boost::asio::post
the exception would propagate out of boost::asio::io_context::run()
. However if one uses coroutines via boost::asio::co_spawn
, in a fire and forget mode like with boost::asio::detached
the exceptions are not thrown out of run()
. In similar question an anwser, https://stackoverflow.com/a/68042350/3537677, refered to using a completion handler with the signature of void (std::exception_ptr,...)
however I could not get the code executed in that handler at all.
So how do I get exceptions out of co_routines propagated to io_context::run
or, if that is not possible, to define a exception handling clause i.e. to co_spawn
? My MRE:
#include <iostream>
#include <boost/asio.hpp>
boost::asio::awaitable<void> coroutine() {
std::cout << "Coroutine executes...\n";
throw std::runtime_error("Test throw from coroutine!");
co_return;
}
int main(int argc, char* argv[]) {
boost::asio::io_context context;
boost::asio::co_spawn(context, coroutine(), boost::asio::detached);
boost::asio::co_spawn(context, coroutine(), [] (std::exception_ptr ptr) {
std::cout << "Rethrowing in completion handler\n"; //Doesn't get executed
throw ptr;
});
boost::asio::post(context, [] () {
throw std::runtime_error("Test throw from post!");
});
std::thread t([&context]() {
try {
while (true) {
context.run();
return;
}
} catch (std::exception &e) {
std::cerr << "Exception in context::run(): " << e.what() << "\n";
}
});
t.join();
}
Actual Output:
Coroutine executes...
Coroutine executes...
Exception in context::run(): Test throw from post!
Process finished with exit code 0
Desired Output:
...
Coroutine executes...
std::cout << "Rethrowing in completion handler\n";
Exception in context::run(): Test throw from coroutine!
Exception in context::run(): Test throw from post!
Process finished with exit code 0
throw ptr
doesn't do what you think it does.
Use
if (ptr) {
std::cout << "Rethrowing in completion handler" << std::endl;
std::rethrow_exception(ptr);
}
Next up, handling exceptions from io_context
is subtly different: you stop the io_context
prematurely.
Slightly simplified and improved output:
#include <iostream>
#include <boost/asio.hpp>
boost::asio::awaitable<void> coroutine() {
std::cout << "Coroutine executes..." << std::endl;
throw std::runtime_error("Test throw from coroutine!");
co_return;
}
int main() {
boost::asio::io_context context;
boost::asio::co_spawn(context, coroutine, boost::asio::detached);
boost::asio::co_spawn(context, coroutine, [](std::exception_ptr ptr) {
if (ptr) {
std::cout << "Rethrowing in completion handler" << std::endl;
std::rethrow_exception(ptr);
} else {
std::cout << "Completed without error" << std::endl;
}
});
boost::asio::post(context, [] { throw std::runtime_error("Test throw from post!"); });
while (true) {
try {
context.run();
break;
} catch (std::exception const& e) {
std::cerr << "Exception in context::run(): " << e.what() << std::endl;
}
}
}
Prints
Coroutine executes...
Coroutine executes...
Exception in context::run(): Test throw from post!
Rethrowing in completion handler
Exception in context::run(): Test throw from coroutine!