The connection attempt below creates (on my network configuration) a delay of 2 minutes because the target does not exist and the address is on a different subnet to my machine. So I added timeout logic to limit the attempt to 5 seconds:
#define BOOST_ASIO_HAS_CO_AWAIT
#define BOOST_ASIO_HAS_STD_COROUTINE
#include <iostream>
#include <chrono>
#include <thread>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/redirect_error.hpp>
namespace this_coro = boost::asio::this_coro;
using namespace std::chrono_literals;
boost::asio::awaitable<void> fail_to_connect()
{
auto executor = co_await this_coro::executor;
auto socket = boost::asio::ip::tcp::socket{executor};
auto ep = boost::asio::ip::tcp::endpoint{
boost::asio::ip::make_address_v4("192.168.1.52"),
80};
auto timer = boost::asio::steady_timer{executor};
timer.expires_after(5s);
boost::asio::co_spawn(
executor,
[&]() -> boost::asio::awaitable<void> {
auto ec = boost::system::error_code{};
co_await timer.async_wait(boost::asio::redirect_error(boost::asio::use_awaitable, ec));
std::cout << "Thread ID: " << std::this_thread::get_id()
<< " Timer: " << ec.message() << std::endl;
if (!ec) {
socket.close();
}
},
boost::asio::detached
);
std::cout << "Thread ID: " << std::this_thread::get_id()
<< " Starting connection" << std::endl;
co_await boost::asio::async_connect(socket,
std::array{std::move(ep)},
boost::asio::use_awaitable);
timer.cancel();
}
int main()
{
auto ctx = boost::asio::io_context{};
auto guard = boost::asio::make_work_guard(ctx.get_executor());
auto exception_handler = [&](auto e_ptr) {
if (e_ptr) {
std::rethrow_exception(e_ptr);
}
};
boost::asio::co_spawn(ctx, fail_to_connect, std::move(exception_handler));
ctx.run();
}
This works as expected. As you can see from the thread ID's, using the same execution context between the two coroutines means that I'm not accessing the socket concurrently too.
14:58:41: Starting /home/cmannett85/workspace/build-scratch-Desktop-Debug/scratch ...
Thread ID: 140171855615808 Starting connection
Thread ID: 140171855615808 Timer: Success
terminate called after throwing an instance of 'boost::system::system_error'
what(): Operation canceled
14:58:46: The program has unexpectedly finished.
However this feels clunky, especially compared to the non-coroutine callback-style. Is there a better approach for timeouts using coroutines? I'm struggling to find examples.