I'm writing a connection manager class that should establish connection to the server and send several HTTP requests using Boost.Beast.
It has a connect
method that should return true
if connection was successful and false
otherwise.
I am using tcp_stream
and its async_connect
method with boost::asio::use_future
to wait_for
a certain type and check if connection failed to establish in time.
My connect
method has the following code:
// ...
const auto resolve = this->resolver.resolve(this->host, this->port);
auto connect_future = this->stream.async_connect(resolve, boost::asio::use_future);
switch (connect_future.wait_for(boost::asio::chrono::seconds{5})) {
// Do the work
}
// ...
The event loop for io_context
associated with both reolver
and stream
has its run
method called constantly in event loop thread.
It establishes connection and works, but always waits full 5 seconds until proceeding. What am I doing wrong?
edit: While trying to make a minimal reproducible example, I've stumble upon the following: code exits cleanly when compiled with -DWORKING
and prints Timeout
without it!
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/asio/strand.hpp>
#include <atomic>
#include <iostream>
#include <string>
// Performs an HTTP GET and prints the response
int main(int argc, char** argv)
{
namespace net = boost::asio;
namespace beast = boost::beast;
net::io_context ioc;
std::atomic_bool running = true;
std::thread loop([&ioc, &running] {
while (running) {
ioc.run();
}
});
net::ip::tcp::resolver resolver(net::make_strand(ioc));
beast::tcp_stream stream(net::make_strand(ioc));
const auto resolve = resolver.resolve("127.0.0.1", "8080");
auto fut = stream.async_connect(resolve, net::use_future);
switch (fut.wait_for(net::chrono::seconds(5))) {
case std::future_status::timeout:
std::cerr << "Timelimit" << std::endl;
break;
default: break;
}
running = false;
loop.join();
stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
ioc.stop();
return 0;
}
edit2: Something more resembling my actual code. This is not affected by the issue, however. Am I running into an UB?
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/asio/strand.hpp>
#include <atomic>
#include <iostream>
#include <string>
// Performs an HTTP GET and prints the response
int main(int argc, char** argv)
{
namespace net = boost::asio;
namespace beast = boost::beast;
net::io_context ioc;
std::atomic_bool running = true;
std::thread loop([&ioc, &running] {
while (running) {
ioc.run();
}
});
net::ip::tcp::resolver resolver(net::make_strand(ioc));
beast::tcp_stream stream(net::make_strand(ioc));
const auto resolve = resolver.resolve("127.0.0.1", "8080");
auto fut = stream.async_connect(resolve, net::use_future);
switch (fut.wait_for(net::chrono::seconds(5))) {
case std::future_status::timeout:
std::cerr << "Timelimit" << std::endl;
break;
default: break;
}
running = false;
loop.join();
stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
ioc.stop();
return 0;
}
Futures only make sense if the service is running on some different thread(s).
Your thread is not guaranteed to work:
while(running)
{
ioc.run();
}
The documentation explains that if run()
runs out of work, restart()
would be required to restart it. This means that the thread will just run a tight loop without effect (except for heating up your CPU).
Instead use a work_guard
or indeed thread_pool
:
net::io_context ioc;
auto work = make_work_guard(ioc);
std::thread loop([&ioc, &running] { ioc.run(); });
Or
net::thread_pool ioc(1); // later ioc.join()
This has the benefit that you don't need to fix your hand-written thread to deal with exceptions (Should the exception thrown by boost::asio::io_service::run() be caught?)