chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.72k stars 1.19k forks source link

How to immediately cancel an awaitable without causing runloop exit due to exception #1452

Open jcelerier opened 3 months ago

jcelerier commented 3 months ago

Reposted from SO: https://stackoverflow.com/questions/78213176/boost-asio-how-to-cancel-awaitable-without-causing-termination

#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;
}

BEGIN digression outside of the need of having to manage the ownership of the cancellation_signal explicitely which I'd consider a bug (e.g. the following code crashes which led me to put it as global for an example - maybe same as this issue? https://github.com/chriskohlhoff/asio/issues/1332 )

void subscribe(ba::io_context &context)
{
  ba::cancellation_signal cancel_sub;
  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);
}

END digression


I'd like to know what I'm missing in this code to make sure I never get an exception thrown out of my io_context::run, e.g. I don't want cancellation to be treated as an exceptional error (because it's not, I have an API where things can be subscribed / unsubscribed as part of a normal operation state which in turn should cancel running awaitables).

Right now it fails immediately with

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

despite me calling

co_await ba::this_coro::throw_if_cancelled(false);

in the coroutine