Boost.Asio: Why the timer is executed only once? - c++

I have a function called read_packet. This function remains blocked while there is no connection request or the timer is signaled.
The code is the following:
std::size_t read_packet(const std::chrono::milliseconds& timeout,
boost::system::error_code& error)
{
// m_timer_ --> boost::asio::high_resolution_timer
if(!m_is_first_time_) {
m_is_first_time = true;
// Set an expiry time relative to now.
m_timer_.expires_from_now( timeout );
} else {
m_timer_.expires_at( m_timer_.expires_at() + timeout );
}
// Start an asynchronous wait.
m_timer_.async_wait(
[ this ](const boost::system::error_code& error){
if(!error) m_is_timeout_signaled_ = true;
}
);
auto result = m_io_service_.run_one();
if( !m_is_timeout_signaled_ ) {
m_timer_.cancel();
}
m_io_service_.reset();
return result;
}
The function works correctly while not receiving a connection request. All acceptances of requests are asynchronous.
After accepting a connection, the run_one() function does not remains blocked the time set by the timer. The function always returns 1 (one handle has been processed). This handle corresponds to the timer.
I do not understand why this situation occurs.
Why the function is not blocked the time required for the timer?
Cheers.
NOTE: This function is used in a loop.
UPDATE:
I have my own io_service::run() function. This function performs other actions and tasks. I want to listen and process the network level for a period of time:
If something comes on the network level, io_service::run_one() returns and read_packet() returns the control to my run() function.
Otherwise, the timer is fired and read_packet() returns the control to my run() function.
Everything that comes from the network level is stored in a data structure. Then my run() function operates on that data structure.
It also runs other options.
void run(duration timeout, boost::system::error_code& error)
{
time_point start = clock_type::now();
time_point deadline = start + timeout;
while( !stop() ) {
read_packet(timeout, error);
if(error) return;
if(is_timeout_expired( start, deadline, timeout )) return;
// processing network level
// other actions
}
}
In my case, the sockets are always active until a client requests the closing of the connection.
During a time slot, you manage the network level and for another slot you do other things.

After reading the question more closely I got the idea that you are actually trying to use Asio to get synchronous IO, but with a timeout on each read operation.
That's not what Asio was intended for (hence, the name "Asynchronous IO Library").
But sure, you can do it if you insist. Like I said, I feel you're overcomplicating things.
In the completion handler of your timer, just cancel the socket operation if the timer had expired. (Note that if it didn't, you'll get operation_aborted, so check the error code).
Small selfcontained example (which is what you should always do when trying to get help, by the way):
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
struct Program {
Program() { sock_.connect({ boost::asio::ip::address_v4{}, 6771 }); }
std::size_t read_packet(const std::chrono::milliseconds &timeout, boost::system::error_code &error) {
m_io_service_.reset();
boost::asio::high_resolution_timer timer { m_io_service_, timeout };
timer.async_wait([&](boost::system::error_code) {
sock_.cancel();
});
size_t transferred = 0;
boost::asio::async_read(sock_, boost::asio::buffer(buffer_), [&](boost::system::error_code ec, size_t tx) {
error = ec;
transferred = tx;
});
m_io_service_.run();
return transferred;
}
private:
boost::asio::io_service m_io_service_;
using tcp = boost::asio::ip::tcp;
tcp::socket sock_{ m_io_service_ };
std::array<char, 512> buffer_;
};
int main() {
Program client;
boost::system::error_code ec;
while (!ec) {
client.read_packet(std::chrono::milliseconds(100), ec);
}
std::cout << "Exited with '" << ec.message() << "'\n"; // operation canceled in case of timeout
}
If the socket operation succeeds you can see e.g.:
Exited with 'End of file'
Otherwise, if the operation didn't complete within 100 milliseconds, it will print:
Exited with 'Operation canceled'
See also await_operation in this previous answer, which generalizes this pattern a bit more:
boost::asio + std::future - Access violation after closing socket

Ok, The code is incorrect. When the timer is canceled, the timer handler is always executed. For this reason io_service::run_one() function is never blocked.
More information: basic_waitable_timer::cancel
Thanks for the help.

Related

Is there any way to know the number of bytes transfered in an async_read function if the read handler won't get invoked?

I have coded the following DoRead function which reads data from the opened serial port, and it works as expected except one thing:
When the timeout elapses before the read completes, then no read handler will be invoked and I can not get the number of bytes read at this point.
Here is my code:
std::size_t wxSerialPort::DoRead(std::string& str, const int timeout)
{
m_bytes_transferred_read = 0;
boost::asio::async_read(m_serialPort, boost::asio::buffer(str),
std::bind(&wxSerialPort::AsyncReadHandler, this,
std::placeholders::_1, std::placeholders::_2));
m_io_context.restart();
if (timeout == wxTIMEOUT_INFINITE)
{
m_io_context.run_until(std::chrono::steady_clock::time_point::max());
}
else
{
m_io_context.run_for(std::chrono::milliseconds(timeout));
}
return m_bytes_transferred_read; // At this point I always get 0 bytes read.
}
void wxSerialPort::AsyncReadHandler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
m_bytes_transferred_read = bytes_transferred;
}
Keep in mind that any variable preceded with m_ is a member variable.
But if I give a small buffer for example to the function, then the read handler will be invoked before the timeout, and I get the actual number of bytes read.
Thank you in advance.
It sounds like you need to call async_read_some instead of async_read.
The async_read function ensures that the requested amount of data is read before the asynchronous operation completes, i.e. it needs enough data to fill the buffer before it calls the read handler.
The basic_serial_port::async_read_some method calls the read handler whenever data has been received, regardless of whether the buffer is full or not.
So simply replace the call to async_read with:
m_serialPort.async_read_some(boost::asio::buffer(str),
std::bind(&wxSerialPort::AsyncReadHandler, this,
std::placeholders::_1, std::placeholders::_2));
it turns out that, boost-asio -by design-, won't call any IO handler for any of the io_context::run_for, io_context::run_one_for, io_context::run_until and io_context::run_one_until functions when the timeout elapses.
And the solution for this problem, would be to provide our own wait handler and cancel (basic_serial_port::cancel) all asynchronous operations associated with the serial port in that wait handler, that in turn will trigger our read handler with a boost::asio::error::operation_aborted error code.
And the resulting code will be as follows:
std::size_t wxSerialPort::DoRead(std::string& str, const int timeout)
{
m_bytes_transferred_read = 0;
if (timeout == wxTIMEOUT_INFINITE)
{
m_timer.expires_at(std::chrono::steady_clock::time_point::max());
}
else
{
m_timer.expires_from_now(std::chrono::milliseconds(timeout));
}
m_timer.async_wait(std::bind(&wxSerialPort::AsyncWaitHandler, this,
std::placeholders::_1));
boost::asio::async_read(m_serialPort, boost::asio::buffer(str),
std::bind(&wxSerialPort::AsyncReadHandler, this,
std::placeholders::_1, std::placeholders::_2));
m_io_context.restart();
m_io_context.run();
return m_bytes_transferred_read;
}
void wxSerialPort::AsyncReadHandler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error != boost::asio::error::operation_aborted)
{
m_timer.cancel();
}
m_bytes_transferred_read = bytes_transferred;
}
void wxSerialPort::AsyncWaitHandler(const boost::system::error_code& error)
{
if (error != boost::asio::error::operation_aborted)
{
m_serialPort.cancel();
}
}
Thank you.

asio async operations aren't processed

I am following ASIO's async_tcp_echo_server.cpp example to write a server.
My server logic looks like this (.cpp part):
1.Server startup:
bool Server::Start()
{
mServerThread = std::thread(&Server::ServerThreadFunc, this, std::ref(ios));
//ios is asio::io_service
}
2.Init acceptor and listen for incoming connection:
void Server::ServerThreadFunc(io_service& service)
{
tcp::endpoint endp{ address::from_string(LOCAL_HOST),MY_PORT };
mAcceptor = acceptor_ptr(new tcp::acceptor{ service,endp });
// Add a job to start accepting connections.
StartAccept(*mAcceptor);
// Process event loop.Hang here till service terminated
service.run();
std::cout << "Server thread exiting." << std::endl;
}
3.Accept a connection and start reading from the client:
void Server::StartAccept(tcp::acceptor& acceptor)
{
acceptor.async_accept([&](std::error_code err, tcp::socket socket)
{
if (!err)
{
std::make_shared<Connection>(std::move(socket))->StartRead(mCounter);
StartAccept(acceptor);
}
else
{
std::cerr << "Error:" << "Failed to accept new connection" << err.message() << std::endl;
return;
}
});
}
void Connection::StartRead(uint32_t frameIndex)
{
asio::async_read(mSocket, asio::buffer(&mHeader, sizeof(XHeader)), std::bind(&Connection::ReadHandler, shared_from_this(), std::placeholders::_1, std::placeholders::_2, frameIndex));
}
So the Connection instance finally triggers ReadHandler callback where I perform actual read and write:
void Connection::ReadHandler(const asio::error_code& error, size_t bytes_transfered, uint32_t frameIndex)
{
if (bytes_transfered == sizeof(XHeader))
{
uint32_t reply;
if (mHeader.code == 12345)
{
reply = (uint32_t)12121;
size_t len = asio::write(mSocket, asio::buffer(&reply, sizeof(uint32_t)));
}
else
{
reply = (uint32_t)0;
size_t len = asio::write(mSocket, asio::buffer(&reply, sizeof(uint32_t)));
this->mSocket.shutdown(tcp::socket::shutdown_both);
return;
}
}
while (mSocket.is_open())
{
XPacket packet;
packet.dataSize = rt->buff.size();
packet.data = rt->buff.data();
std::vector<asio::const_buffer> buffers;
buffers.push_back(asio::buffer(&packet.dataSize,sizeof(uint64_t)));
buffers.push_back(asio::buffer(packet.data, packet.dataSize));
auto self(shared_from_this());
asio::async_write(mSocket, buffers,
[this, self](const asio::error_code error, size_t bytes_transfered)
{
if (error)
{
ERROR(200, "Error sending packet");
ERROR(200, error.message().c_str());
}
}
);
}
}
Now, here is the problem. The server receives data from the client and sends ,using sync asio::write, fine. But when it comes to to asio::async_read or asio::async_write inside the while loop, the method's lambda callback never gets triggered, unless I put io_context().run_one(); immediately after that. I don't understand why I see this behaviour. I do call io_service.run() right after acceptor init, so it blocks there till the server exit. The only difference of my code from the asio example, as far as I can tell, is that I run my logic from a custom thread.
Your callback isn't returning, preventing the event loop from executing other handlers.
In general, if you want an asynchronous flow, you would chain callbacks e.g. callback checks is_open(), and if true calls async_write() with itself as the callback.
In either case, the callback returns.
This allows the event loop to run, calling your callback, and so on.
In short, you should make sure your asynchronous callbacks always return in a reasonable time frame.

boost async_wait return handler never gets called

I am working with the boost::asio tcp, version 1.57, creating a custom server/client, roughly following this example: Async_Tcp_Client , but I'm running the io_service run() in it's own thread per server/client. Also, there can be multiple server/clients per application.
Following the example I put my await_output function to sleep when I DON'T want to send a Message, and waking it up when I do want to send one (via async_write). After a varying amount of send-operations (sometimes less then 10, sometimes several thousand) I run into strange behaviour of my await_output Deadline (a boost deadline timer).
At some point, the async_wait against the timer just "disappears" and doesn't return when I cancel the deadline to send a message.
The transmit function, that is called by the Application owning the Client/Server (only by the application though, I guess it is not very threadsafe);
The await_output function that is waiting on the mOutputQueueDeadline;
And the handle_write function:
void SocketTcp::transmit(std::string pMsg) {
if (mStopped)
{ return; }
mOutputQueue.push(pMsg); // a global queue
// Signal that the output queue contains messages. Modifying the expiry
// will wake the output actor, if it is waiting on the timer.
size_t quits = mOutputQueueDeadline.expires_at(boost::posix_time::neg_infin);
//this returns '0' when the error occurs
}
void SocketTcp::await_output(const boost::system::error_code& ec)
{
if (mStopped)
{ return; }
if (mOutputQueue.empty())
{
size_t quits = mOutputQueueDeadline.expires_at(boost::posix_time::pos_infin);
mOutputQueueDeadline.async_wait(boost::bind(&SocketTcp::await_output, this, _1));
//this async_wait starts a wait on the deadline, that sometimes never returns!
}
else
{
boost::asio::async_write(mSocket,
boost::asio::buffer(mOutputQueue.front()),
boost::bind(&SocketTcp::handle_write, this, _1));
}
}
void SocketTcp::handle_write(const boost::system::error_code& ec)
{
if (mStopped)
{ return; }
if(!ec)
{
mOutputQueue.pop(); //remove sent element from queue
boost::system::error_code errcode;
await_output(errcode); //start the waiting actor for outgoing messages
}
else
{
mConnected = false; //update the connection status
this->stop();
}
}
I tried implementing a workaround, restarting the await_output in transmit() when expire_at returns 0, but that leads to TWO actors beeing awakened the next time I send a message, and then running into a crash (String iterator not dereferencable - the design doesn't allow for parallel send OP, much less trying to send the same message...)
I tried debugging with the BOOST_ASIO_ENABLE_HANDLER_TRACKING option, and found the error here:
#asio|1468415460.456019|0|deadline_timer#000000000050AB88.cancel //transmit cancels the timer
#asio|1468415460.456019|>474|ec=system:995 //await_output is called
#asio|1468415460.456019|474*479|socket#000000000050A9D8.async_send //starts the async send
#asio|1468415460.457019|<474|
#asio|1468415460.457019|>479|ec=system:0,bytes_transferred=102 //async send returns to it's handler
#asio|1468415460.457019|479|deadline_timer#000000000050AB88.cancel
//this cancel op is the only difference to the 'normal' order,
//not sure where it originates though!!
#asio|1468415460.457019|479*480|deadline_timer#000000000050AB88.async_wait //the handler starts the new async wait
//handler 480 never gets called when the deadline is canceled the next time
#asio|1468415460.457019|<479|
I'm pretty new to c++ as well as the stackoverflow (even though it has already safed me multiple times!) so please tell me if I can improve my question somehow!

Matching boost::deadline_timer callbacks to corresponding wait_async

Consider this short code snippet where one boost::deadline_timer interrupts another:
#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/asio.hpp>
static boost::asio::io_service io;
boost::asio::deadline_timer timer1(io);
boost::asio::deadline_timer timer2(io);
static void timer1_handler1(const boost::system::error_code& error)
{
std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:Operation canceled." << std::endl;
}
static void timer1_handler2(const boost::system::error_code& error)
{
std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:success." << std::endl;
}
static void timer2_handler1(const boost::system::error_code& error)
{
std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:success." << std::endl;
std::cout << "cancel and restart timer1. Bind to timer1_handler2" << std::endl;
timer1.cancel();
timer1.expires_from_now(boost::posix_time::milliseconds(10000));
timer1.async_wait(boost::bind(timer1_handler2, boost::asio::placeholders::error));
}
int main()
{
std::cout << "Start timer1. Bind to timer1_handler1." << std::endl;
timer1.expires_from_now(boost::posix_time::milliseconds(2000));
timer1.async_wait(boost::bind(timer1_handler1, boost::asio::placeholders::error));
std::cout << "Start timer2. Bind to timer2_handler1. Will interrupt timer1." << std::endl;
timer2.expires_from_now(boost::posix_time::milliseconds(2000));
timer2.async_wait(boost::bind(timer2_handler1, boost::asio::placeholders::error));
std::cout << "Run the boost io service." << std::endl;
io.run();
return 0;
}
If the time for timer2 is varied around the 2 second mark, sometimes timer1_handler1 reports success, and sometimes operation cancelled. This is probably determinate in the trivial example because we know what time timer2 is set to.
./timer1
Start timer1. Bind to timer1_handler1.
Start timer2. Bind to timer2_handler1. Will interrupt timer1.
Run the boost io service.
void timer1_handler1(const boost::system::error_code&) time:1412680360 error:Success expect:Operation canceled.
void timer2_handler1(const boost::system::error_code&) time:1412680360 error:Success expect:success.
cancel and restart timer1. Bind to timer1_handler2
void timer1_handler2(const boost::system::error_code&) time:1412680370 error:Success expect:success.
This represents a more complex system where timer1 is implementing a timeout, and timer2 is really an asynchronous socket. Occasionally I've observed a scenario where timer1 is cancelled too late, and the first handler returns after the second async_wait() has been called, thus giving a spurious timeout.
Clearly I need to match up the handler callbacks with the corresponding async_wait() call. Is there a convenient way of doing this?
One convenient way of solving the posed problem, managing higher-level asynchronous operations composed of multiple non-chained asynchronous operations, is by using the approach used in the official Boost timeout example. Within it, handlers make decisions by examining current state, rather than coupling handler logic with an expected or provided state.
Before working on a solution, it is important to identify all possible cases of handler execution. When the io_service is ran, a single iteration of the event loop will execute all operations that are ready to run, and upon completion of the operation, the user's completion handler is queued with an error_code indicating the operation's status. The io_service will then invoke the queued completion handlers. Hence, in a single iteration, all ready to run operations are executed in an unspecified order before completion handlers, and the order in which completion handlers are invoked is unspecified. For instance, when composing an async_read_with_timeout() operation from async_read() and async_wait(), where either operation is only cancelled within the other operation's completion handler, the following case are possible:
async_read() runs and async_wait() is not ready to run, then async_read()'s completion handler is invoked and cancels async_wait(), causing async_wait()'s completion handler to run with an error of boost::asio::error::operation_aborted.
async_read() is not ready to run and async_wait() runs, then async_wait()'s completion handler is invoked and cancels async_read(), causing async_read()'s completion handler to run with an error of boost::asio::error::operation_aborted.
async_read() and async_wait() run, then async_read()'s completion handler is invoked first, but the async_wait() operation has already completed and cannot be cancelled, so async_wait()'s completion handler will run with no error.
async_read() and async_wait() run, then async_wait()'s completion handler is invoked first, but the async_read() operation has already completed and cannot be cancelled, so async_read()'s completion handler will run with no error.
The completion handler's error_code indicates the status of the operation and does not not reflect changes in state resulting from other completion handlers; therefore, when the error_code is successful, one may need examine the current state to perform conditional branching. However, before introducing additional state, it can be worth taking the effort to examine the goal of the higher-level operation and what state is already available. For this example, lets define that the goal of async_read_with_timeout() is to close a socket if data has not been received before a deadline has been reached. For state, the socket is either open or closed; the timer provides expiration time; and the system clock provides the current time. After examining the goal and available state information, one may propose that:
async_wait()'s handler should only close the socket if the timer's current expiration time is in the past.
async_read()'s handler should set the timer's expiration time into the future.
With that approach, if async_read()'s completion handler runs before async_wait(), then either async_wait() will be cancelled or async_wait()'s completion handler will not close the connection, as the current expiration time is in the future. On the other hand, if async_wait()'s completion handler runs before async_read(), then either async_read() will be cancelled or async_read()'s completion handler can detect that the socket is closed.
Here is a complete minimal example demonstrating this approach for various use cases:
#include <cassert>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
class client
{
public:
// This demo is only using status for asserting code paths. It is not
// necessary nor should it be used for conditional branching.
enum status_type
{
unknown,
timeout,
read_success,
read_failure
};
public:
client(boost::asio::ip::tcp::socket& socket)
: strand_(socket.get_io_service()),
timer_(socket.get_io_service()),
socket_(socket),
status_(unknown)
{}
status_type status() const { return status_; }
void async_read_with_timeout(boost::posix_time::seconds seconds)
{
strand_.post(boost::bind(
&client::do_async_read_with_timeout, this, seconds));
}
private:
void do_async_read_with_timeout(boost::posix_time::seconds seconds)
{
// Start a timeout for the read.
timer_.expires_from_now(seconds);
timer_.async_wait(strand_.wrap(boost::bind(
&client::handle_wait, this,
boost::asio::placeholders::error)));
// Start the read operation.
boost::asio::async_read(socket_,
boost::asio::buffer(buffer_),
strand_.wrap(boost::bind(
&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void handle_wait(const boost::system::error_code& error)
{
// On error, such as cancellation, return early.
if (error)
{
std::cout << "timeout cancelled" << std::endl;
return;
}
// The timer may have expired, but it is possible that handle_read()
// ran succesfully and updated the timer's expiration:
// - a new timeout has been started. For example, handle_read() ran and
// invoked do_async_read_with_timeout().
// - there are no pending timeout reads. For example, handle_read() ran
// but did not invoke do_async_read_with_timeout();
if (timer_.expires_at() > boost::asio::deadline_timer::traits_type::now())
{
std::cout << "timeout occured, but handle_read ran first" << std::endl;
return;
}
// Otherwise, a timeout has occured and handle_read() has not executed, so
// close the socket, cancelling the read operation.
std::cout << "timeout occured" << std::endl;
status_ = client::timeout;
boost::system::error_code ignored_ec;
socket_.close(ignored_ec);
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
// Update timeout state to indicate handle_read() has ran. This
// cancels any pending timeouts.
timer_.expires_at(boost::posix_time::pos_infin);
// On error, return early.
if (error)
{
std::cout << "read failed: " << error.message() << std::endl;
// Only set status if it is unknown.
if (client::unknown == status_) status_ = client::read_failure;
return;
}
// The read was succesful, but if a timeout occured and handle_wait()
// ran first, then the socket is closed, so return early.
if (!socket_.is_open())
{
std::cout << "read was succesful but timeout occured" << std::endl;
return;
}
std::cout << "read was succesful" << std::endl;
status_ = client::read_success;
}
private:
boost::asio::io_service::strand strand_;
boost::asio::deadline_timer timer_;
boost::asio::ip::tcp::socket& socket_;
char buffer_[1];
status_type status_;
};
// This example is not interested in the connect handlers, so provide a noop
// function that will be passed to bind to meet the handler concept
// requirements.
void noop() {}
/// #brief Create a connection between the server and client socket.
void connect_sockets(
boost::asio::ip::tcp::acceptor& acceptor,
boost::asio::ip::tcp::socket& server_socket,
boost::asio::ip::tcp::socket& client_socket)
{
boost::asio::io_service& io_service = acceptor.get_io_service();
acceptor.async_accept(server_socket, boost::bind(&noop));
client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
io_service.reset();
io_service.run();
io_service.reset();
}
int main()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
// Scenario 1: timeout
// The server writes no data, causing a client timeout to occur.
{
std::cout << "[Scenario 1: timeout]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(0));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Run timeout and read operations.
io_service.run();
assert(client.status() == client::timeout);
}
// Scenario 2: no timeout, succesful read
// The server writes data and the io_service is ran before the timer
// expires. In this case, the async_read operation will complete and
// cancel the async_wait.
{
std::cout << "[Scenario 2: no timeout, succesful read]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(10));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Write to client.
boost::asio::write(server_socket, boost::asio::buffer("test"));
// Run timeout and read operations.
io_service.run();
assert(client.status() == client::read_success);
}
// Scenario 3: no timeout, failed read
// The server closes the connection before the timeout, causing the
// async_read operation to fail and cancel the async_wait operation.
{
std::cout << "[Scenario 3: no timeout, failed read]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(10));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Close the socket.
server_socket.close();
// Run timeout and read operations.
io_service.run();
assert(client.status() == client::read_failure);
}
// Scenario 4: timeout and read success
// The server writes data, but the io_service is not ran until the
// timer has had time to expire. In this case, both the await_wait and
// asnyc_read operations complete, but the order in which the
// handlers run is indeterminiate.
{
std::cout << "[Scenario 4: timeout and read success]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(0));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Allow the timeout to expire, the write to the client, causing both
// operations to complete with success.
boost::this_thread::sleep_for(boost::chrono::seconds(1));
boost::asio::write(server_socket, boost::asio::buffer("test"));
// Run timeout and read operations.
io_service.run();
assert( (client.status() == client::timeout)
|| (client.status() == client::read_success));
}
}
And its output:
[Scenario 1: timeout]
timeout occured
read failed: Operation canceled
[Scenario 2: no timeout, succesful read]
read was succesful
timeout cancelled
[Scenario 3: no timeout, failed read]
read failed: End of file
timeout cancelled
[Scenario 4: timeout and read success]
read was succesful
timeout occured, but handle_read ran first
You can boost::bind additional parameters to the completion handler which can be used to identify the source.

Boost.Asio deadline_timer not working as expected

I'm trying to implement a timeout for a Boost.Asio read on a TCP socket.
I am trying to use a async_read_some with a deadline_timer. My function below is a member of a class that holds a smart pointer to the TCP socket and io_service. What I would expect to happen when called on an active socket that doesn't return any data is wait 2 seconds and return false.
What happens is: If the socket never returns any data it works as expected. How ever if the server returns the data the proceeding calls to the method below return immediately because to timers callback is called without waiting the two seconds.
I tried commenting out the async_read_some call and the function always works as expected. Why would async_read_some change how the timer works?
client::client() {
// Init socket and timer
pSock = boost::shared_ptr<tcp::socket > (new tcp::socket(io_service));
}
bool client::getData() {
// Reset io_service
io_service.reset();
// Init read timer
boost::asio::deadline_timer timer(pSock->io_service());
timer.expires_from_now(boost::posix_time::seconds(2));
timer.async_wait(boost::bind(&client::read_timeout, this, boost::system::error_code(), true));
// // Async read the data
pSock->async_read_some(boost::asio::buffer(buffer_),
boost::bind(&client::read_complete,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
));
// While io_service runs check read result
while (pSock->io_service().run_one()) {
if (m_read_result > 0) {
// Read success
return m_read_result;
}else if(m_read_result < 0){
return false;
}
}
}
}
void client::read_complete(const boost::system::error_code& error, size_t bytes_transferred) {
if (!error) {
m_read_result = bytes_transferred;
}else{
m_read_result = -1;
}
}
void client::read_timeout(const boost::system::error_code& error, bool timeout) {
if(!error){
m_read_result = -1;
}
}
Simple problem when setting up the timer boost::system::error_code() should be changed to _1 or a error::placeholder
timer.async_wait(boost::bind(&client::read_timeout, this, _1, true));
You have negated condition when you check for connection errors.
It should be:
if(error){
std::cout << "read_timeout Error - " << error.message() << std::endl;
}
Now you will see, that the callback is invoked with error code boost::asio::error::operation_aborted.
This is because, when you receive any data, you return from function getData and deadline_timer's destructor calls the callback with the error code set.