Exceptions disappear due to boost::asio::co_spawn how to propagate via io_context::run

148 Views Asked by At

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
1

There are 1 best solutions below

1
sehe On BEST ANSWER

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:

Live On Compiler Explorer

#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!