In the following code, the parameter of the session coroutine is passed by reference.
#include <boost/asio.hpp>
#include <iostream>
boost::asio::awaitable<void> session(const std::string& name)
{
std::cout << "Starting " << name << std::endl;
auto executor = co_await boost::asio::this_coro::executor;
}
int main()
{
boost::asio::io_context io_context;
co_spawn(io_context, session("ServerA"), boost::asio::detached);
co_spawn(io_context, session("ServerB"), boost::asio::detached);
io_context.run();
return 0;
}
For some reason that I don't understand, the above code results in printing Starting ServerB twice.
> g -std=c 20 ../test-coro.cpp -o test-coro && ./test-coro
Starting ServerB
Starting ServerB
But when I change the coroutine parameter to pass by value, it will correctly print both Starting ServerA and Starting ServerB
#include <boost/asio.hpp>
#include <iostream>
boost::asio::awaitable<void> session(std::string name)
{
std::cout << "Starting " << name << std::endl;
auto executor = co_await boost::asio::this_coro::executor;
}
int main()
{
boost::asio::io_context io_context;
co_spawn(io_context, session("ServerA"), boost::asio::detached);
co_spawn(io_context, session("ServerB"), boost::asio::detached);
io_context.run();
return 0;
}
> g -std=c 20 ../test-coro.cpp -o test-coro && ./test-coro
Starting ServerA
Starting ServerB
Is it expected or this is a compiler/library bug? If it is expected, then what's the reasoning for it?
Environment:
Arch Linux 5.18.16-arch1-1
gcc (GCC) 12.2.0
boost version 1.79
CodePudding user response:
You can think of the coroutine state as containing what would be on the function call stack (which is what makes the function resumable): cppreference
When a coroutine begins execution, it performs the following:
- allocates the coroutine state object using operator new (see below)
- copies all function parameters to the coroutine state: by-value parameters are moved or copied, by-reference parameters remain references (and so may become dangling if the coroutine is resumed after the lifetime of referred object ends)
The coroutine logically stores a reference to a temporary string. Oops.
I haven't checked, but I'd assume that Asio's awaitable implementation starts with an initial suspend_always
(this makes intuitive sense to me with respect to the executor model).
Yes, this means that co_spawn
with any reference argument means the lifetime of the object referenced must be guaranteed.
On my system, the output was merely
Starting
Starting
One fix is what you showed. To illustrate out the lifetime aspect:
{
std::string a="ServerA", b="ServerB";
co_spawn(io_context, session(a), boost::asio::detached);
co_spawn(io_context, session(b), boost::asio::detached);
io_context.run();
}
Is also a valid fix. For the trivial example I'd suggest a std::string_view
regardless:
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
boost::asio::awaitable<void> session(std::string_view name)
{
std::cout << "Starting " << std::quoted(name) << std::endl;
auto executor = co_await boost::asio::this_coro::executor;
}
int main()
{
boost::asio::io_context io_context;
std::string const a="ServerA";
co_spawn(io_context, session(a), boost::asio::detached);
co_spawn(io_context, session("ServerB"), boost::asio::detached);
io_context.run();
}
Printing
Starting "ServerA"
Starting "ServerB"