I\'m trying to write a wrapper synchronous method around async_read
to allow non blocking reads on a socket. Following several examples around internet I have d
The main difference between io_service::run_one() and io_service::poll_one() is that run_one()
will block until a handler is ready to run, whereas poll_one()
will not wait for any outstanding handlers to become ready.
Assuming the only outstanding handlers on _io_service
are handle_timeout()
and handle_read()
, then run_one()
does not require a loop because it will only return once either handle_timeout()
or handle_read()
have ran. On the other hand, poll_one()
requires a loop because poll_one()
will return immediately, as neither handle_timeout()
nor handle_read()
are ready to run, causing the function to eventually return.
The main issue with the original code, as well as the fix proposal #1, is that there are still outstanding handlers in the io_service when async_read_helper()
returns. Upon the next call to async_read_helper()
, the next handler to be invoked will be a handler from the previous call. The io_service::reset() method only allows the io_service to resume running from a stopped state, it does not remove any handlers already queued into the io_service. To account for this behavior, try using a loop to consume all of the handlers from the io_service. Once all handlers have been consumed, exit the loop and reset the io_service:
// Consume all handlers.
while (_io_service->run_one())
{
if (_message_received)
{
// Message received, so cancel the timer. This will force the completion of
// handle_timer, with boost::asio::error::operation_aborted as the error.
timer.cancel();
}
else if (_timeout_triggered)
{
// Timeout occured, so cancel the socket. This will force the completion of
// handle_read, with boost::asio::error::operation_aborted as the error.
_socket->cancel();
}
}
// Reset service, guaranteeing it is in a good state for subsequent runs.
_io_service->reset();
From the caller's perspective, this form of timeout is synchronous as run_one()
blocks. However, work is still being made within the I/O service. An alternative is to use Boost.Asio's support for C++ futures to wait on a future and perform a timeout. This code can be easier to read, but it requires at least one other thread to be processing the I/O service, as the thread waiting on the timeout is no longer processing the I/O service:
// Use an asynchronous operation so that it can be cancelled on timeout.
std::future<std::size_t> read_result = boost::asio::async_read(
socket, buffer, boost::asio::use_future);
// If timeout occurs, then cancel the operation.
if (read_result.wait_for(std::chrono::seconds(1)) ==
std::future_status::timeout)
{
socket.cancel();
}
// Otherwise, the operation completed (with success or error).
else
{
// If the operation failed, then on_read.get() will throw a
// boost::system::system_error.
auto bytes_transferred = read_result.get();
// process buffer
}