'c++20 coroutines: boost asio co_spawn blocks from another coroutine

I do not know if this is the expected behavior of boost::asio::co_spawn (I did check the docs 1.78.0...), but if i call (e.g. co_spawn(ctx, ..., detached)) from a function, this call is async, meaning that this call does not block waiting for the completion, but returns immediately. However, if i do the same call from within another coroutine, co_spawn will block until whatever that was spawned completes. Below is the a test compiled with g++ 11.2 with boost asio 1.78.


#include <iostream>
#include <thread>
#include <chrono>
#include <coroutine>

#include <boost/asio.hpp>
#include <boost/asio/experimental/as_tuple.hpp>

using namespace boost;
using namespace boost::asio;

awaitable<void> TestCoro2(io_context& ctx) {
    std::cout << "test coro1 thread id = " << std::this_thread::get_id() << std::endl;
    co_return;
}

awaitable<void> TestCoro1(io_context& ctx) {
    std::cout << "test coro1 thread id = " << std::this_thread::get_id() << std::endl;
    std::cout << "333" << std::endl;
    //co_await TestCoro2(ctx);
    co_spawn(ctx, TestCoro2(ctx), detached);
    std::cout << "444" << std::endl;
    co_return;
}

awaitable<void> TestCoro(io_context& ctx) {
    std::cout << "test coro thread id = " << std::this_thread::get_id() << std::endl;
    std::cout << "111" << std::endl;
    co_spawn(ctx.get_executor(), TestCoro1(ctx), detached);
    std::cout << "222" << std::endl;
    co_return;
}

void Test1() {
    io_context ctx;
    auto work = require(ctx.get_executor(), execution::outstanding_work.tracked);

    std::cout << "before" << std::endl;
    co_spawn(ctx.get_executor(), TestCoro(ctx), detached);
    std::cout << "after" << std::endl;

    ctx.run();
}

int main() {
    Test1();

    return 0;
}

In the example above i had not yet called ctx.run() when spawking a coro... still semantics, I would expect, to be similar...

My understanding was that first it will schedule and return, and a currently running coroutine will proceed, however i guess i was wrong. I do understand that i can also just wrap this co_spawn into a post... but i'm a bit confused on the difference in behavior...

Is this the expected behavior?

thanks! VK



Solution 1:[1]

The problem is that you use the same context for all of your coroutines. co_spawn internally uses dispatch() to ensure the coroutine starts in the desired context. dispatch() calls the token synchronously if the target context is the same as the current one. So the coroutine is executed synchronously in such case, at least until the first suspension point (some co_await).

You can insert this line at the beginning of your coroutine to ensure it is always scheduled instead of being called synchronously, even when called from the same context:

co_await asio::post(ctx, asio::use_awaitable);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 vagran