c++exceptionc++20boost-asioc++-coroutine

boost asio (with coroutines) not throwing exceptions correctly?


This is intentionally a near-duplicate of: boost cobalt+asio not throwing exceptions correctly?

The difference is that the other ticket uses boost cobalt (a library that sits on top of asio that adds a more "co-routiney" interface).

This ticket applies to only asio, without cobalt, and the code has been modified accordingly. You can use the same CMakeLists.txt as the cobalt ticket.

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

#include <iostream>
#include <string>
#include <vector>

namespace net = boost::asio;
using executor_type =  net::any_io_executor;

net::awaitable<std::vector<net::ip::tcp::endpoint>> resolve(
    executor_type executor,
    std::string const& hostname,
    std::string const& service
)
{
    auto endpoints = std::vector<net::ip::tcp::endpoint>{};
    auto resolver = net::ip::tcp::resolver{executor};
    try
    {
        auto results = co_await resolver.async_resolve(hostname, service, net::use_awaitable);

        for (const auto& result : results)
            endpoints.push_back(result.endpoint());

        co_return endpoints;
    }
    catch (...)
    {
        std::cerr << "Resolve exception\n";
        throw;
    }
}

net::awaitable<void> main2()
{
    auto executor = co_await net::this_coro::executor;

    auto endpoints = co_await resolve(executor, "www.googleqqq.com", "https"); // Intentionally wrong; use www.google.com to make it work

    // Print the resolved endpoints
    for (const auto& endpoint : endpoints)
        std::cerr << endpoint << std::endl;

    co_return;
}

int main()
{
    try
    {
        net::io_context ctx;
        net::co_spawn(
            ctx,
            main2(),
            [&](std::exception_ptr eptr)
            {
                try
                {
                    if (eptr)
                        std::rethrow_exception(eptr);
                }
                catch (const std::exception& e)
                {
                    std::cerr << "Exception: " << e.what() << std::endl;
                }
            });
        ctx.run();
    }
    catch (...)
    {
        std::cerr << "Uncaught exception\n";
    }
    return 0;
}

The solution to this ticket is I suspect the solution to both tickets.

Just to reiterate the problem: The code above works fine when passed a reasonable address (e.g., "www.google.com") but fails to throw an exception out of async_resolve() when passed an unreasonable address (e.g., "www.googleqqq.com").

What happens instead is that this prints out:

libc++abi: terminating due to uncaught exception of type boost::system::system_error: Host not found (authoritative) [asio.netdb:1]

and SIGABRT occurs (which probably means std::terminate was called). So none of the exception catching stuff in the code above was hit.


Solution

  • The answer is "it's a bug" (credit to @sehe for mentioning dynamic linking).

    Relevant info: https://github.com/llvm/llvm-project/issues/92121

    Solution (in CMakeLists.txt, assuming MacOS):

    execute_process(
        COMMAND brew --prefix llvm
        OUTPUT_VARIABLE LLVM_PREFIX
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    target_link_libraries(${PROJECT_NAME}
        PRIVATE
            ${LLVM_PREFIX}/lib/libunwind.dylib
    )