Hey I'm trying to wrap class provided by third party library to use Boost coroutines. The library also uses Boost but for the purpose of async operations uses completition handlers. Below is a simplified example that one could try. I think I'm close but for some reason awaitable returned from async_connect is of type void, whereas I would like to boost::error_code being returned. What am I missing?
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <iostream>
using AsyncHandler = std::function<void(boost::system::error_code)>;
struct LibraryClient
{
LibraryClient(boost::asio::io_context& ioc)
: socket{ioc}
{}
boost::asio::ip::tcp::socket socket;
void async_connect(AsyncHandler handler = {})
{
boost::system::error_code ec;
boost::asio::ip::address ip_address = boost::asio::ip::address::from_string("127.0.0.1", ec);
boost::asio::ip::tcp::endpoint ep(ip_address, 9999);
socket.async_connect(ep, std::move(handler));
}
};
template<class CompletitionToken = boost::asio::use_awaitable_t<>>
auto do_async_connect(LibraryClient& client, CompletitionToken&& token = {})
{
auto initiate = [&client]<class H>(H&& self) mutable
{
client.async_connect([self = std::make_shared<H>(std::forward<H>(self))](auto&& r)
{
(*self)(r);
});
};
return boost::asio::async_initiate<
CompletitionToken, boost::system::error_code(boost::system::error_code)
>(initiate, token);
}
struct LibraryClientWrapper
{
LibraryClient client;
boost::asio::awaitable<boost::system::error_code> async_connect()
{
//auto ec = co_await do_something_with_client();
const auto ec = co_await do_async_connect(client);
}
};
int main()
{
auto ioc = boost::asio::io_context{};
auto client = LibraryClientWrapper{LibraryClient{ioc}};
ioc.run();
}
EDIT It seems that I've found something. I slightly modified code, removed all code not needed for the purpose of this example
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <iostream>
#include <cassert>
template<class CompletitionToken>
auto do_async_connect(LibraryClient& client, CompletitionToken&& token)
{
auto initiate = [](auto&& handler) {
handler(nullptr, 90);
};
return boost::asio::async_initiate<CompletitionToken, void(Eptr, int)>(
initiate, std::forward<CompletitionToken>(token)
);
}
struct LibraryClientWrapper
{
LibraryClient client;
boost::asio::awaitable<void> async_connect()
{
const auto ec = co_await do_async_connect(client, boost::asio::use_awaitable);
assert(ec == 90);
}
};
void rethrow_exception(std::exception_ptr eptr)
{
if (eptr)
{
std::rethrow_exception(eptr);
}
}
int main()
{
auto ioc = boost::asio::io_context{};
auto client = LibraryClientWrapper{LibraryClient{ioc}};
boost::asio::co_spawn(ioc, client.async_connect(), rethrow_exception);
ioc.run();
}
As you can see I changed signatute to take both std::exception_ptr and int, and this resulted in int being returned properly from coroutine. But I don't get why exactly this signature is required, in particular std::exception_ptr as first parameter.
Well I managed to let's say solve it. But I don't get why the signature of handler has to be in a form of void(std:::exception_ptr, error_code) in order to return error_code. Also I failed to find it in boost documentation. I would be grateful if anyone could provide some kind of explanation. I believe that this could be better and also more generic. So far it doesn't handle eg. handling multiple return values like we have in e.g. async_send where completition handler has to be able to handle two params like error_code and bytes_sent.
#include <boost/asio.hpp>
#include <iostream>
using AsyncHandler = std::function<void(boost::system::error_code)>;
void rethrow_exception(std::exception_ptr eptr)
{
if (eptr)
{
std::rethrow_exception(eptr);
}
}
using Eptr = std::exception_ptr;
struct LibraryClient
{
LibraryClient(LibraryClient&&) = default;
LibraryClient(boost::asio::io_context& ioc)
: socket{ioc}
{}
boost::asio::ip::tcp::socket socket;
void async_connect(AsyncHandler handler = {})
{
boost::system::error_code ec;
boost::asio::ip::address ip_address = boost::asio::ip::address::from_string("127.0.0.1", ec);
boost::asio::ip::tcp::endpoint ep(ip_address, 9999);
socket.async_connect(ep, std::move(handler));
}
void async_disconnect(AsyncHandler handler = {})
{
auto ec = boost::system::error_code{};
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
}
};
template<class R, class Func, class CompletitionHandler = boost::asio::use_awaitable_t<>>
auto awaitable_call(Func&& func, CompletitionHandler&& handler = {})
{
using Signature_t = void(Eptr, R);
auto initiate = [func = std::forward<Func>(func)]<class Handler>(Handler&& self) mutable
{
std::invoke(func, [self = std::make_shared<Handler>(std::forward<Handler>(self))](auto&&... args) {
(*self)(std::current_exception(), std::forward<decltype(args)>(args)...);
});
};
return boost::asio::async_initiate<CompletitionHandler, Signature_t>(initiate, handler);
}
template<class Func, class O, class...Args>
auto bind_awaitable_func(Func&& func, O&& o, Args&&...args)
{
return std::bind(std::forward<Func>(func),
std::forward<O>(o),
std::forward<Args>(args)...,
std::placeholders::_1
);
}
struct LibraryClientWrapper
{
LibraryClient client;
using Impl = LibraryClient;
boost::asio::awaitable<void> async_connect()
{
const auto r1 = co_await awaitable_call<boost::system::error_code>(
bind_awaitable_func(&LibraryClient::async_connect, std::ref(client))
);
}
boost::asio::awaitable<void> async_disconnect()
{
const auto r1 = co_await awaitable_call<boost::system::error_code>(
bind_awaitable_func(&LibraryClient::async_disconnect, std::ref(client))
);
}
};
int main()
{
auto ioc = boost::asio::io_context{};
auto client = LibraryClientWrapper{LibraryClient{ioc}};
boost::asio::co_spawn(ioc, client.async_disconnect(), rethrow_exception);
ioc.run();
}