I've been experimenting with ASIO strands and C++20 coroutines, and noticed weird behaviour. When I spawn a coroutine on one strand, and inside it spawn another coroutine on a second strand, and await on it, when the control is returned to the outer coroutine, both strands are blocked. Is this intended behaviour? If so, how do I work around this?
Following code demonstrates the issue, tested with boost.asio, boost version 1.74.0, gcc version 10.2.0 on linux.
#include <thread>
#include <chrono>
#include <iostream>
#include <boost/asio.hpp>
namespace asio = boost::asio;
using namespace std::chrono_literals;
int main() {
auto ioc = asio::io_context{};
// Create two strands
auto s1 = asio::make_strand(ioc);
auto s2 = asio::make_strand(ioc);
// Prevent the io context from stopping
auto wg = asio::make_work_guard(ioc);
// Over-provision threads just in case
auto w1 = std::jthread{[&]() { ioc.run(); }};
auto w2 = std::jthread{[&]() { ioc.run(); }};
auto w3 = std::jthread{[&]() { ioc.run(); }};
auto w4 = std::jthread{[&]() { ioc.run(); }};
auto w5 = std::jthread{[&]() { ioc.run(); }};
asio::co_spawn(s1, [&]() -> asio::awaitable<void> {
std::cout << "a" << std::endl;
co_await asio::co_spawn(s2, []() -> asio::awaitable<void> {
// Works as intended
// s2 is blocked, s1 is free (b will print)
std::this_thread::sleep_for(2s);
co_return;
}, asio::use_awaitable);
// Expected: s1 is blocked, s2 is free (d will print, e will wait 5s)
// Reality: both s1 and s2 are blocked (after 5s, d and e print at the same time).
// Why?
std::cout << "c" << std::endl;
std::this_thread::sleep_for(5s);
}, asio::detached);
std::this_thread::sleep_for(1s);
asio::co_spawn(s1, []() -> asio::awaitable<void> {
std::cout << "b" << std::endl;
co_return;
}, asio::detached);
std::this_thread::sleep_for(2s);
asio::co_spawn(s2, []() -> asio::awaitable<void> {
std::cout << "d" << std::endl;
co_return;
}, asio::detached);
asio::co_spawn(s1, []() -> asio::awaitable<void> {
std::cout << "e" << std::endl;
co_return;
}, asio::detached);
std::this_thread::sleep_for(10s);
ioc.stop();
}
Expected behaviour:
t__0_1_2_3_4_5_6_7
s1 a_b_c_________e
s2 ______d________
Actual behaviour:
t__0_1_2_3_4_5_6_7
s1 a_b_c_________e
s2 ______________d
Figured out a solution: instead of spawning the coroutine on the strand, spawn it on the io context, and post to a strand when access through that strand is required.
asio::co_spawn(ioc, [&]() -> asio::awaitable<void> {
co_await asio::post(s1, asio::use_awaitable);
std::cout << "a" << std::endl;
co_await asio::post(s2, asio::use_awaitable);
std::this_thread::sleep_for(2s);
co_await asio::post(s1, asio::use_awaitable);
std::cout << "c" << std::endl;
std::this_thread::sleep_for(5s);
}, asio::detached);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With