简体   繁体   中英

Why do boost asio awaitable operators swallow exceptions?

After watching this excellent talk Talking Async I am experimenting with boost asio awaitable operators for cancellation and timeouts with C++ 20 coroutines.

While this works, I do not understand the error handling.

The following program runs without an error message, whereas I would expect the output "for testing".

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>

namespace io = boost::asio;
using namespace boost::asio::experimental::awaitable_operators;
using namespace std::chrono_literals;

io::awaitable<void> Cancelled(io::steady_timer& cancelTimer)
{
    boost::system::error_code ec;
    co_await cancelTimer.async_wait(io::redirect_error(io::use_awaitable, ec));
}

io::awaitable<void> Throws(io::io_context& context)
{
    io::steady_timer timer{ context, 100ms };
    co_await timer.async_wait(io::use_awaitable);
    throw std::runtime_error("for testing");
}

io::awaitable<void> Run(io::io_context& context, io::steady_timer& cancelTimer)
{
    co_await (Throws(context) || Cancelled(cancelTimer));
}

void RethrowException(std::exception_ptr e)
{
    if (e)
    {
        std::rethrow_exception(e);
    }
}

int main()
{
    try
    {
        io::io_context context;
        io::steady_timer cancelTimer{ context, 200ms };

        io::co_spawn(context, Run(context, cancelTimer), RethrowException);

        context.run();
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

Modifying the code like

io::awaitable<void> Run(io::io_context& context, io::steady_timer& cancelTimer)
{
    co_await Throws(context);
}

behaves like expected. I could reproduce this on godbolt using the latest MSVC compiler (x64 msvc v19.latest) and boost library (vcpkg 2022.05.10).

So how am I going to do error handling with awaitable operators?

The behaviour of boost asio's operator||() for awaitables seems to be intended. Here is the code for one of the overloads of operator||() for awaitables from boost asio:

/// Wait for one operation to succeed.
/**
 * If one operations succeeds, the other is cancelled as the OR-condition is
 * already satisfied.
 */
template <typename T, typename U, typename Executor>
awaitable<std::variant<T, U>, Executor> operator||(
    awaitable<T, Executor> t, awaitable<U, Executor> u)
{
    auto ex = co_await this_coro::executor;

    auto [order, ex0, r0, ex1, r1] =
        co_await make_parallel_group(
        co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
        co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
        ).async_wait(
        wait_for_one_success(),
        use_awaitable_t<Executor>{}
    );

    // ... //
}

The parameter wait_for_one_success() of the call to make_parallel_group() defines the behaviour such that the operator only returns, if any of the awaitables succeeds. If one fails by throwing an exception, the operator does not return.

The following hack modifies this behaviour:

#include <boost\asio\experimental\cancellation_condition.hpp>
#define wait_for_one_success wait_for_one
#include <boost\asio\experimental\awaitable_operators.hpp>
#undef wait_for_on_success

Now an exception from one of the awaitables is propagated, which is the behaviour I would have expected in the first place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM