I inherited a codebase that was working on boost 1.75. This code was working without issues on boost 1.75:
using CompletionTokenType = boost::asio::yield_context;
using FunctionType = void(boost::system::error_code);
using AsyncResultType = boost::asio::async_result<CompletionTokenType, FunctionType>;
using HandlerType = typename AsyncResultType::completion_handler_type;
[[maybe_unused]] ResultOrErrorType
read(CompletionTokenType token, StatementType const& statement)
{
auto handler = HandlerType{token};
auto result = AsyncResultType{handler};
auto const future = handle_.get().asyncExecute(statement, [handler](auto const&) mutable {
boost::asio::post(boost::asio::get_associated_executor(handler), [handler]() mutable {
handler(boost::system::error_code{});
});
});
result.get(); // suspends coroutine until handler called
if (auto res = future.get(); res) {
return res;
} else {
// handle error
}
}
Then we were forced to migrate to boost 1.82 and with that came the necessity to use async_compose
/async_initiate
(as far as I understand).
The code had to be changed to the following mess.
- We now have to make a
shared_ptr
out ofself
because the callback given toasyncExecute
is astd::function
that copies the callback. Sadly,self
is non-copyable, unlikehandler
was before. - "Callback hell" instead of slightly more sequential code often associated with coroutines
[[maybe_unused]] ResultOrErrorType
read(CompletionTokenType token, StatementType const& statement)
{
auto future = std::optional<FutureWithCallbackType>{};
auto init = [this, &statement, &future]<typename Self>(Self& self) {
future.emplace(handle_.get().asyncExecute(statement, [sself = std::make_shared<Self>(std::move(self))](auto&& data) mutable {
auto executor = asio::get_associated_executor(*sself);
asio::post(executor, [data = std::move(data), sself = std::move(sself)]() mutable {
sself->complete(std::move(data));
});
}));
};
auto res = asio::async_compose<CompletionTokenType, void(ResultOrErrorType)>(init, token, boost::asio::get_associated_executor(token));
if (res) {
return res;
} else {
// handle error
}
}
This new code works correctly most of the times but eventually gets stuck after entering async_compose
. I don't see any logs from the handle error
path and in general the DB code is assumed to work correctly (from multiple other tests). There is always exactly one callback firing from the asyncExecute
call.
The code above is slightly simplified. For example, on MacOS the above code works as is but on Linux(gcc) we need to add an explicit work object to the inner post
, otherwise the application crashes. Also on MacOS it's ok to call sself->complete
without wrapping it with a post
but on Linux once again that makes the application crash.
All this suggests that something is not quite right with this code. Please help me to understand what we are doing wrong. Any ideas/pointers will be highly appreciated.
If there is a way to get the old code working again it's also a viable solution. However, i was not able to do that as completion_handler_type
appears to be gone even tho the docs for boost 1.82 claim to still expose it.