22

I need to know how to read (sync or async doesn't matters) with a timeout. I want to check if a device is connected with a serial port or not.

For that I use asio::write and then I wait for the response of the device.

If a device is connected asio::read(serial, boost::asio::buffer(&r,1)) works fine but if there is no device the program stops, which is is why I need the timeout

I know that I need a deadline_timer but I have no idea how to use it in the async_read function.

An example of how it works would be really helpful.

I know that there are many similar threads and I read lot of them but I can't find a solution that helps me solving my problem!

dsolimano
  • 8,870
  • 3
  • 48
  • 63
Chris K.
  • 221
  • 1
  • 2
  • 3

4 Answers4

14

The code posted by Igor R. did not compile for me. Here is my improved version of his code, which works perfectly. It uses lambdas to get rid of the set_result helper function.

template <typename SyncReadStream, typename MutableBufferSequence>
void readWithTimeout(SyncReadStream& s, const MutableBufferSequence& buffers, const boost::asio::deadline_timer::duration_type& expiry_time)
{
    boost::optional<boost::system::error_code> timer_result;
    boost::asio::deadline_timer timer(s.get_io_service());
    timer.expires_from_now(expiry_time);
    timer.async_wait([&timer_result] (const boost::system::error_code& error) { timer_result.reset(error); });

    boost::optional<boost::system::error_code> read_result;
    boost::asio::async_read(s, buffers, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });

    s.get_io_service().reset();
    while (s.get_io_service().run_one())
    { 
        if (read_result)
            timer.cancel();
        else if (timer_result)
            s.cancel();
    }

    if (*read_result)
        throw boost::system::system_error(*read_result);
}
Community
  • 1
  • 1
Robert Hegner
  • 9,014
  • 7
  • 62
  • 98
  • This works excellent to simulate a timeout for `boost::asio::async_read`. Really simple & solid approach to the problem. I like this simple technique & I want to adapt it for `boost::asio::async_write` case ? But it doesn't seem to work for the write case. Would you mind taking a look at [this question here](https://stackoverflow.com/questions/47378022/how-to-do-a-boostasioasync-write-with-timeout) ? – TheWaterProgrammer Nov 19 '17 at 14:38
  • [This example](http://www.ridgesolutions.ie/index.php/2012/12/13/boost-c-read-from-serial-port-with-timeout-example/) is similar but does not use lambdas and is not templated. – Gabriel Devillers Apr 17 '18 at 08:53
  • Still we don't have any solution from boost for read with timeout? – abhiarora Dec 10 '19 at 17:01
  • `s.get_io_service().reset()` and `s.get_io_service().run_one()` do not work as `io_service` has been replaced by `io_context`, but I still can't figure out how to adapt the code. Any ideas? – Petio Petrov Dec 01 '20 at 21:10
  • I've successfully implemented the above solution in my code but, as stated below by @Tom Quarendon [in @Igor R. answer](https://stackoverflow.com/a/13144834/487356), I needed to added a `break` after cancelling the timer (Boost version: 1.69.0) – mattdibi Dec 12 '20 at 09:38
  • show its usage... – basil Oct 19 '22 at 23:20
7

Once upon a time, the library author proposed the following way to read synchronously with timeout (this example involves tcp::socket, but you can use serial port instead):

  void set_result(optional<error_code>* a, error_code b) 
  { 
    a->reset(b); 
  } 


  template <typename MutableBufferSequence> 
  void read_with_timeout(tcp::socket& sock, 
      const MutableBufferSequence& buffers) 
  { 
    optional<error_code> timer_result; 
    deadline_timer timer(sock.io_service()); 
    timer.expires_from_now(seconds(1)); 
    timer.async_wait(boost::bind(set_result, &timer_result, _1)); 


    optional<error_code> read_result; 
    async_read(sock, buffers, 
        boost::bind(set_result, &read_result, _1)); 

    sock.io_service().reset(); 
    while (sock.io_service().run_one()) 
    { 
      if (read_result) 
        timer.cancel(); 
      else if (timer_result) 
        sock.cancel(); 
    } 


    if (*read_result) 
      throw system_error(*read_result); 
  } 
Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • 1
    I don't understand the technique. Maybe this works in a single threaded scenario when there's no other async operations that might be going on. What makes the while loop terminate? If there are other threads around, is calling io_service.reset() a safe thing to do? – Tom Quarendon Mar 07 '18 at 14:37
  • @Tom Quarendon I guess you're right, it won't work if multiple threads are running the io_service. Note however that one can use io_service-per-core model, where there's one thread per io_service. – Igor R. Mar 07 '18 at 22:12
  • 1
    @Iror R. Having tried this out with a test multithreaded server, it *appears* to work. I had to add a "break" in the "if" and "else if" in order to make the loop terminate though. What I don't know is whether this is "supposed" to work in this multithreaded environment – Tom Quarendon Mar 15 '18 at 14:17
  • Actually, scrub that. I can't get a version that works OK in all cases, single and multithreaded, with other traffic going on and without. – Tom Quarendon Mar 15 '18 at 16:09
  • these examples are honestly so frustrating because they show neither the includes or the namespaces – basil Oct 19 '22 at 23:55
  • @basil this is not a self-containing example, it requires some understanding of the context we're talking of. – Igor R. Oct 20 '22 at 11:40
6

You don't use deadline_timer in async_read. But you can start two async processes:

  1. An async_read process on serial port. boost::asio::serial_port has a cancel method that cancels all async operations on it.
  2. A deadline timer with required timeout. In completion handler for deadline_timer you can cancel the serial port. This should close the async_read operation and call its completion handler with an error.

Code:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/array.hpp>

class timed_connection
{
    public:
        timed_connection( int timeout ) :
            timer_( io_service_, boost::posix_time::seconds( timeout ) ),
            serial_port_( io_service_ )
        {
        }

        void start()
        {
              timer_.async_wait
                (
                 boost::bind
                 (
                  &timed_connection::stop, this
                 )
                );

            // Connect socket
            // Write to socket

            // async read from serial port
            boost::asio::async_read
                (
                 serial_port_, boost::asio::buffer( buffer_ ),
                 boost::bind
                 (
                  &timed_connection::handle_read, this,
                  boost::asio::placeholders::error
                 )
                );

            io_service_.run();
        }

    private:
        void stop()
        {  
            serial_port_.cancel();
        }

        void handle_read ( const boost::system::error_code& ec)
        {  
            if( ec )
            {  
                // handle error
            }
            else
            {  
                // do something
            }
        }

    private:
        boost::asio::io_service io_service_;
        boost::asio::deadline_timer timer_;
        boost::asio::serial_port serial_port_;
        boost::array< char, 8192 > buffer_;
};

int main()
{
    timed_connection conn( 5 );
    conn.start();

    return 0;
}
Vikas
  • 8,790
  • 4
  • 38
  • 48
  • 2
    The question **explicitly** states that he uses a serial device, not a TCP endpoint. UNIX sockets might be an option if he used *nix, but the OS isn't stated and they kind of break portability. – TC1 Oct 30 '12 at 17:03
  • 1
    @TC1, Sure, edited the code. Conceptually the way Asio abstracts it, nothing changes. – Vikas Oct 30 '12 at 17:12
0

There's no one or easy answer per se, since even if you do an async read, the callback never gets called and you now have a loose thread somewhere around.

You're right in assuming that deadline_timer is one of the possible solutions, but it takes some fiddling and shared state. There's the blocking TCP example, but that's for async_connect and there's a cool thing about it returning when it has nothing to do. read won't do that, worst case scenario -- it'll crash & burn 'cause of the invalid resource. So the deadline timer thing is one of your options, but there's actually an easier one, that goes somewhat like this:

boost::thread *newthread = new boost::thread(boost::bind(&::try_read));
if (!newthread->timed_join(boost::posix_time::seconds(5))) {
    newthread->interrupt();
}

Basically, do the read in another thread and kill it off if it times out. You should read up on Boost.Threads.

If you interrupt it, make sure the resources are all closed.

TC1
  • 1
  • 3
  • 20
  • 31