Boost.Asio deadline_timer not working as expected - c++

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.

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.Asio: Why the timer is executed only once?

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.

Reading a Serial Port Asynchronously Boost

I am trying to read several bytes asynchronously from a serial port. Currently the code I am using (source is How do I perform a nonblocking read using asio?), only allows me to read 1 byte and then it exits. How do I get it so that the code keeps reading until there is no more incoming data in the serial port? Thanks :)
void foo()
{
boost::asio::io_service io_svc;
boost::asio::serial_port ser_port(io_svc, "/dev/ttyS0");
boost::asio::deadline_timer timeout(io_svc);
unsigned char my_buffer[2];
bool data_available = false;
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
timeout.expires_from_now(boost::posix_time::seconds(30));
timeout.async_wait(boost::bind(&wait_callback, boost::ref(ser_port),boost::asio::placeholders::error()));
io_svc.run();
io_svc.
if(!data_available)
{
ser_port.close();
cout << "ser_port was closed";
}
}
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error || !bytes_transferred)
{
// No data was read!
data_available = false;
return;
}
timeout.cancel();
data_available = true;
}
void wait_callback(boost::asio::serial_port& ser_port, const boost::system::error_code& error)
{
if (error)
{
// Data was read and this timeout was cancelled
return;
}
ser_port.cancel();
}
In your read_callback you cancel your timer and do not start a new read.
So what do you expect ASIO to do. You just canceled all handlers and like the documentation states, the run method will return when no handlers are left.
So if you want to have more data than just the one byte you receive you can do two things:
First just issue another call to asio to read more from the port.
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error || !bytes_transferred)
{
// No data was read!
data_available = false;
return;
}
// do something with the data you just read
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
//restart your timer
data_available = true;
}
or you can add use transfer_at_least to get at least the amount of data you need.

Boost asio async_write callback doesn't get called

I'm trying to write a simple server to send messages asynchronously, but my callback for async_write doesn't get called. By the way, data is succesfully transmitted to the client.
Firs, my server initialisation code:
void Server::start()
{
ioService = new boost::asio::io_service();
acceptor = new tcp::acceptor(*ioService, tcp::endpoint(tcp::v4(), port));
serverRunning = true;
serverThread = new boost::thread(&Server::run, this);
startAccept();
}
My start accept and accept handler methods:
void Server::startAccept()
{
serverSocket = new tcp::socket(acceptor->get_io_service());
acceptor->async_accept(*serverSocket,
boost::bind(&Server::handleAccept, shared_from_this(),
boost::asio::placeholders::error));
}
void Server::handleAccept( const boost::system::error_code& error )
{
changeState(Connected);
}
The handleAccept method get called when i connect, the changeState method does nothing for now. After the client connected, i put some data to the toSend vector, and the server's thread sends them:
void Server::run(){
while( serverRunning ){
ioService->poll();
if (toSend.size()>0 ){
mtx.lock();
for (int i=0; i<toSend.size(); i++){
cout << "async write" << endl;
boost::asio::async_write(*serverSocket, boost::asio::buffer(toSend.at(i)),
boost::bind(&Server::handleSend, shared_from_this(),
toSend.at(i),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
toSend.clear();
mtx.unlock();
}
usleep(1000);
}
}
I can see "async write" messages coming on std out, and the client recieves the data as well. My handleSend method is just some cout now, but it never get called. Why?
If you really want to poll the io_service manually, do this after it gets some work, and call reset between the iterations.
Besides, do not call asio::async_write in a loop - the data won't arrive in the correct order. Instead, either prepare a single sequence of buffers and send it at once, or chain async_write - completion handler - async_write, as shown in the examples.