Boost asio: `co_spawn` and `spawn` as async initiating functions cause segfault

117 Views Asked by At

I was using both stackful coroutines (as depicted here: https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio/overview/composition/spawn.html) and C++20 coroutines (https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio/overview/composition/cpp20_coroutines.html) in my project. Here is an example:

#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>

using namespace boost::asio;

io_context ioctx;
void stackful_coroutine(yield_context yield);

awaitable<void> cxx_coroutine() {
  std::cout << "In cxx_coroutine" << std::endl;
  co_await spawn(ioctx, stackful_coroutine, use_awaitable);
  co_return;
}

void stackful_coroutine(yield_context yield) {
  std::cout << "In stackful_coroutine" << std::endl;
}

int main() {
  co_spawn(ioctx, cxx_coroutine(), detached);
  ioctx.run();
}

This works with no errors and spawn is used as just another asynchronous operation.

However, when I switch the order:


#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>

using namespace boost::asio;

io_context ioctx;
void stackful_coroutine(yield_context yield);

awaitable<void> cxx_coroutine() {
  std::cout << "In cxx_coroutine" << std::endl;
  co_return;
}

void stackful_coroutine(yield_context yield) {
  std::cout << "In stackful_coroutine" << std::endl;
  co_spawn(ioctx, cxx_coroutine(), yield);
}

int main() {
  spawn(ioctx, stackful_coroutine, detached);
  ioctx.run();
}

I get a segmentation fault!

Moreover, I also get a segmentation fault even when I only use stackful coroutines as follows:

#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>

using namespace boost::asio;

io_context ioctx;

void stackful_coroutine(yield_context yield) {
  std::cout << "In stackful_coroutine" << std::endl;
  spawn(
      ioctx, [](auto yc) { std::cout << "Spawn inside spawn" << std::endl; },
      yield);
}

int main() {
  spawn(ioctx.get_executor(), stackful_coroutine, detached);
  ioctx.run();
}

I am not sure if this is a bug, or whether I am missing something... I am using gcc (Debian 12.2.0-14) 12.2.0 with Boost 1.83.0.

1

There are 1 best solutions below

1
sehe On

I think you're on to a bug, introduced in Asio 1.24/Boost 1.80.0.

I've noticed a behaviorial change with commit 5bbdc9b Added new spawn() overloads that conform to the requirements for asynchronous operations before¹ and checked before and after.

I can't give a satisfactory suggestion. I can show you what I've tried (based on your last, simplest, example).

None of the alternatives (e.g. inheriting the yield context for the inner coro, which is deprecated but still exists) work. Note also, the old style doesn't actually achieve the same result anyways, because the inner spawn is not "awaited", regardless of whether the yield context is inherited. ¯\(ツ)/¯ I guess this makes it "not a breaking change" because the functionality didn't exist before Asio 1.24.

Note I also experimented with adding work-guards to the inner handlers but it made no difference.

Live On Coliru

#define BOOST_ASIO_ENABLE_HANLDER_TRACKING 1
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
#if BOOST_ASIO_VERSION >=                                                                                    \
    10'24'00 // Asio 1.24.0,  Boost 1.80.0
             // https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio/history.html#boost_asio.history.asio_1_24_0___boost_1_80
    #define NEWSTYLE 1
#else
    #define NEWSTYLE 0
#endif

namespace asio = boost::asio;
asio::io_context ioctx;

void coro(asio::yield_context yield) try {
    std::cout << "Start coro" << std::endl;

#if NEWSTYLE
    auto ex = yield.get_executor();
#else
    auto ex = asio::get_associated_executor(yield);
#endif

    asio::spawn(
#if NEWSTYLE
        ioctx, // or: yield, // or: ex
#else
        yield,
#endif
        [](asio::yield_context yc) {
            std::cout << "Spawn inside spawn" << std::endl;
            post(yc); // just a suspend so we can observe scheduling orders
            std::cout << "End inside spawn" << std::endl;
        }
#if NEWSTYLE
        , yield
#endif
    );
    std::cout << "End coro" << std::endl;
} catch(...) {
    std::cout << "Here be dragons" << std::endl;
}

int main() {
    std::cout << "Boost:" << BOOST_VERSION << " Asio:" << BOOST_ASIO_VERSION << std::endl;
    asio::spawn(ioctx.get_executor(), coro
#if NEWSTYLE
        , [](std::exception_ptr) { std::cout << "Exceptional" << std::endl; }
#endif
    );
    ioctx.run();
}

Printing e.g.

Boost:107900 Asio:102202
Start coro
Spawn inside spawn
End coro
End inside spawn

Vs.

Boost:108300 Asio:102802
Start coro
Spawn inside spawn
End inside spawn
Segmentation fault (core dumped)

Lastly I made sure to isolate it to Asio-only changes by switching just the ASIO version, instead of all of boost:

enter image description here

I would suggest raising this question at https://github.com/boostorg/asio/issues or (possibly better?) https://github.com/chriskohlhoff/asio/issues