Reputation: 1096
i have implemented a pretty standard emulation of blocking api with timeouts for boost::asio. this is my main cycle:
io_service io_svc;
tcp::endpoint endpoint(tcp::v4(), m_port);
tcp::acceptor acceptor(io_svc, endpoint);
accept_helper acc_hlpr(acceptor, 5000);
while (m_bStop == false)
{
tcp::socket socket(io_svc);
if (acc_hlpr.accept(socket))
{
// do stuff
socket.close();
}
}
this is the helper class
class accept_helper
{
public:
accept_helper (tcp::acceptor &acc, size_t msTO) : m_timer(acc.get_io_service()), m_acceptor(acc), m_msTO(msTO) { }
bool accept (tcp::socket &socket)
{
m_bTimeout = false;
m_bAccept = false;
m_timer.expires_from_now(boost::posix_time::milliseconds(m_msTO));
m_timer.async_wait(boost::bind(&accept_helper::handle_timeout, this, boost::asio::placeholders::error));
m_acceptor.async_accept(socket, boost::bind(&accept_helper::handle_accept, this));
m_timer.get_io_service().run_one();
m_timer.get_io_service().reset();
if (m_bAccept)
{
m_timer.cancel();
return true;
}
else if (m_bTimeout)
{
// BOOST_ASIO_ENABLE_CANCELIO is defined
boost::system::error_code ec;
m_acceptor.cancel(ec);
}
return false;
}
private:
void handle_accept (void)
{
boost::mutex::scoped_lock lock(m_mutex);
m_bAccept = true;
}
void handle_timeout(const boost::system::error_code & error)
{
if (!error)
{
boost::mutex::scoped_lock lock(m_mutex);
m_bTimeout = true;
}
}
private:
boost::asio::deadline_timer m_timer;
boost::asio::ip::tcp::acceptor &m_acceptor;
boost::mutex m_mutex;
size_t m_msTO;
bool m_bTimeout;
bool m_bAccept;
};
the problem is that timer waits only on the first iteration. on others run_one method just returns immediately and no flags are set. i have tried to make timer local, but that didn't help. how to make timer wait every time?
fixed version
bool accept (tcp::socket &socket)
{
m_bTimeout = false;
m_bAccept = false;
m_timer.expires_from_now(boost::posix_time::milliseconds(m_msTO));
m_timer.async_wait(boost::bind(&accept_helper::handle_timeout, this, boost::asio::placeholders::error));
m_acceptor.async_accept(socket, boost::bind(&accept_helper::handle_accept, this, boost::asio::placeholders::error));
m_timer.get_io_service().reset();
m_timer.get_io_service().run_one();
if (m_bAccept)
{
m_timer.cancel();
}
else if (m_bTimeout)
{
boost::system::error_code ec;
m_acceptor.cancel(ec);
}
while (m_timer.get_io_service().run_one());
return m_bAccept;
}
private:
void handle_accept (const boost::system::error_code & error)
{
if (!error)
{
boost::mutex::scoped_lock lock(m_mutex);
m_bAccept = true;
}
}
void handle_timeout(const boost::system::error_code & error)
{
if (!error)
{
boost::mutex::scoped_lock lock(m_mutex);
m_bTimeout = true;
}
}
Upvotes: 0
Views: 692
Reputation: 51931
The io_service::reset()
function only allows the io_service
to resume running from a stopped state; it does not remove any handlers already queued into the io_service
. In this case, two operations are initiated on the io_service
(async_wait
and async_accept
), but only one handler is executed, as the io_service
's event loop is being processed by io_service::run_one()
. Upon the next call to accept_helper::accept()
, a handler from the previous invocation will be executed.
To resolve this, consider running the io_service
until both handlers have been invoked. Some solutions will run the io_service
to completion, as shown in this answer and the Boost.Asio blocking tcp client timeout example.
Upvotes: 2