Question
Is the lifetime of a local lambda as a completion handler for co_spawn i.e. a function with functor&& sufficent
Question
I am getting a little confused or paranoid, given the pattern:
void setup(boost::asio::io_context &context) {
const auto completion_handler = [](std::exception_ptr ptr) {
if (ptr) {
std::cout << "Rethrowing in completion handler" << std::endl;
std::rethrow_exception(ptr);
} else {
std::cout << "Completed without error" << std::endl;
}
};
boost::asio::co_spawn(context, coroutine_with_rethrow_completion_handler(), completion_handler);
}
Is the lifetime of completion_handler
, i.e. being local, ok?
Reasoning
AFAIK of course any local capture would be bad, as they would be out of scope when eventually boost::asio::io_context
will run that handler. But whats about the lifetime of that very handler i.e. functor i.e. lambda itself?
boost::asio::co_spawn
takes a &&
, which AFAIK should be a forwarding reference (there is a lot of macro stuff in the template list of this boost function), and is perfect forwarding that completion function into the guts of boost::asio, and the documentation of co_spawn
is not stating any lifetime regards about the completion token.
So my fear is that in the end, only a reference to that lambda is stored in boost::asio::io_context
i.e. context
and when we actually execute the lambda in io_context::run
in main
after, the lambda has gone out of scope in setup
, we have UB.
Complete MRE
#include <iostream>
#include <boost/asio.hpp>
boost::asio::awaitable<void> coroutine_with_rethrow_completion_handler() {
std::cout << "Coroutine executes with rethrow completion handler\n";
throw std::runtime_error("Test throw from coroutine!");
co_return;
}
void setup(boost::asio::io_context &context) {
const auto completion_handler = [](std::exception_ptr ptr) {
if (ptr) {
std::cout << "Rethrowing in completion handler" << std::endl;
std::rethrow_exception(ptr);
} else {
std::cout << "Completed without error" << std::endl;
}
};
boost::asio::co_spawn(context, coroutine_with_rethrow_completion_handler(), completion_handler);
}
int main() {
boost::asio::io_context context;
setup(context);
std::thread t([&context]() {
try {
while (true) {
context.run();
return;
}
} catch (std::exception &e) {
std::cerr << "Exception in context::run(): " << e.what() << "\n";
}
});
t.join();
}