chriskohlhoff / asio

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

Different experimental::coro executors cause undefined behavior #1536

Open terrakuh opened 1 month ago

terrakuh commented 1 month ago

The implementation of experimental::coro does not properly set the cancel on suspension when the suspended coro and the new one have different executors. The result is undefined behavior.

The problem I'm referring occurs in the else branch. Note the coro_.coro_->cancel is not set: https://github.com/chriskohlhoff/asio/blob/master/asio/include/asio/experimental/impl/coro.hpp#L983-L1032

The problem can be reproduced with:

#include <boost/asio.hpp>
#include <boost/asio/experimental/coro.hpp>
#include <boost/asio/experimental/use_coro.hpp>

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

int main()
{
    io_service service{};
    static_thread_pool pool{ 1 };

    const auto my_coro = [&](any_io_executor, auto dur) -> coro<int> {
        steady_timer timer{ service };
        timer.expires_after(dur);
        co_await timer.async_wait(use_coro);
    };

    co_spawn(
      service,
      [&] -> awaitable<void> {
          auto gen = my_coro(pool.get_executor(), 2s);
          co_await gen.async_resume(use_awaitable);
      },
      detached);

    cancellation_signal signal{};
    co_spawn(
      service,
      [&] -> awaitable<void> {
          auto gen = my_coro(pool.get_executor(), 5s);
          co_await gen.async_resume(use_awaitable);
      },
      bind_cancellation_slot(signal.slot(), detached));

    signal_set set{ service, SIGINT };
    set.async_wait([&](auto, int) {
        signal.emit(cancellation_type::all);
    });

    service.run();
}