boost.asio: how to cancel awaitable without causing termination?

111 Views Asked by At

Consider the following code:

#include <boost/asio/awaitable.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_awaitable.hpp>

namespace ba = boost::asio;

ba::cancellation_signal cancel_sub;
void subscribe(ba::io_context &context)
{
  ba::co_spawn(
    context,
    []() -> ba::awaitable<void> { co_return; },
    ba::bind_cancellation_slot(cancel_sub.slot(), ba::detached));
  cancel_sub.emit(ba::cancellation_type::all);
}

int main()
{
  ba::io_context ctx;
  subscribe(ctx);
  ctx.run();
  return 0;
}

It makes my program exit with an exception thrown from the innards of the co_await implementation in boost:

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  co_await: Operation canceled [system:125]

How can I avoid this ?

I tried:

  • Disabling exceptions on cancellation from within the coroutine:
co_await ba::this_coro::throw_if_cancelled(false);
  • Adding this code in the beginning of my coroutine to enable all kinds of cancellation:
co_await ba::this_coro::reset_cancellation_state(ba::enable_total_cancellation());

Nothing changes, I still get my app exiting.

Precisely, in boost's code: (marking my comments with // !!! )


template <typename Handler, typename Executor, typename Function>
awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
    awaitable<void, Executor>*, co_spawn_state<Handler, Executor, Function> s)
{
  (void) co_await co_spawn_dispatch{};

  (co_await awaitable_thread_has_context_switched{}) = false;
  std::exception_ptr e = nullptr;
  try
  {
    // !!! here an exception is thrown as the cancellation is observered.
    co_await s.function();
  }
  catch (...)
  {
    // !!! caught here, all is fine
    e = std::current_exception();
  }

  bool switched = (co_await awaitable_thread_has_context_switched{});
  if (!switched)
    (void) co_await co_spawn_post(); // !!! exception thrown again here as the cancellation state is checked again in await_transform! But now there is nothing to catch it...

  (dispatch)(s.handler_work.get_executor(),
      [handler = std::move(s.handler), e]() mutable
      {
        std::move(handler)(e);
      });
}
0

There are 0 best solutions below