Skip to content

awaitable_operators::operator&& doesn't foward thrown exception as expected #1715

@peper0

Description

@peper0

Consider the code below:

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/multiple_exceptions.hpp>
#include <boost/asio/steady_timer.hpp>

#include <chrono>
#include <exception>
#include <future>
#include <iostream>
#include <stdexcept>
#include <string>

namespace asio = boost::asio;
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::steady_timer;
using asio::use_awaitable;

using namespace std::chrono_literals;
using namespace asio::experimental::awaitable_operators;

struct MyError : std::exception {
    const char* what() const noexcept override { return "MyError: this is the one I expected to catch"; }
};

static awaitable<void> async_sleep(std::chrono::steady_clock::duration d)
{
    steady_timer t(co_await asio::this_coro::executor, d);
    co_await t.async_wait(use_awaitable);
}

static awaitable<void> throw_my_error_after(std::chrono::steady_clock::duration d)
{
    co_await async_sleep(d);
    throw MyError{};
}

static awaitable<void> demo()
{
    try {
        co_await (async_sleep(5s) && throw_my_error_after(50ms));
    }
    catch (const MyError&) {
        std::cout << "I'd like to be here\n";
    }
    catch (const asio::multiple_exceptions& me) {
        std::cout << "I'm here, since the other coroutine is cancelled which results in operation_aborted error from it.\n";
        // And even me.first_exception() is not MyError since it gets the operation_aborted error
    }

    co_return;
}

int main()
{
    asio::io_context io;
    co_spawn(io, demo(), asio::detached);
    io.run();
    return 0;
}

In the example above, one of the coroutines passed to && throws MyError and I would expect this one to be thrown out of the && expression. However, when the exception is thrown, the operator cancels the other coroutine (which is quite expected) which results in system_error with operation_aborted from it (which is also expected). Now, we have two exceptions so we won't catch MyError but rather asio::multiple_exceptions. Moreover, the real exception is not even accessible from multiple_exceptions.

Also, note that handling exceptions via asio::multiple_exceptions is inconvenient. In practice, we would need to write sophiticated try/catch ladders for that. It would be much easier if this operator propagated just the first catched exception and ignored the following ones.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions