0

I am trying to find the proper / canonical way to implement the code below that provides a synchronous wrapper around async asio methods in order to have a timeout. The code appears to work, but none of the examples I have looked at use the boolean in the lambda to terminate the do/while loop running i/o service, so I'm not sure if this is the proper form or if it will have unintended consequences down the road. Some do things like while(IOService.run_one); but that never terminates.

Edit: I'm trying to follow this example: http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/timeouts/blocking_tcp_client.cpp

But in this code they avoid needing the number of bytes read by using a \n terminator. I need the number of bytes read, hence the callback.

I have seen many other solutions that use boost async futures as well as other methods, but they do not seem to compile with the versions of gcc / boost standard for Ubuntu 16.04 and I would like to stay with those versions.

ByteArray SessionInfo::Read(const boost::posix_time::time_duration &timeout)
{

  Deadline.expires_from_now(timeout);
  auto bytes_received = 0lu;
  auto got_callback = false;

  SessionSocket->async_receive(boost::asio::buffer(receive_buffer_,
                               1024),
                               [&bytes_received, &got_callback](const boost::system::error_code &error, std::size_t bytes_transferred) {
                             bytes_received = bytes_transferred;
                             got_callback = true;
                           });
  do
  {
    IOService.run_one();
  }while (!got_callback);

  auto bytes = ByteArray(receive_buffer_, receive_buffer_ + bytes_received);
  return bytes;
}
Olympionex
  • 13
  • 1
  • 5
  • If you want it synchronous with a timeout why are you using asynchronous I/O at all? – user207421 Jun 16 '17 at 09:46
  • From my understanding, that is the preferred / only way to use boost asio with a timeout. In theory you can get the native socket and setsockopt, but its apparently neither recommended, reliable, or portable from what I read. It seems like the preferred mechanism is to use boost::use_future, but that is what doesn't compile on gcc 5.4.0 / boost 1.58 – Olympionex Jun 16 '17 at 10:20
  • That's not how I use timeouts. Set the deadline when you call async_receive, and reset it when you receive a packet. – Michaël Roy Jun 16 '17 at 13:33
  • Michaël Roy - I'm not sure exactly what you mean. As far as I know, I'm not having issues with the timeout mechanics - I took it directly from the boost example. – Olympionex Jun 16 '17 at 21:19

2 Answers2

0

This is how I'd do it. The first event that fires causes io_service::run() to return.

ByteArray SessionInfo::Read(const boost::posix_time::time_duration &timeout)
{
  Deadline.expires_from_now(timeout);  // I assume this is a member of SessionInfo
  auto got_callback{false};
  auto result = ByteArray();

  SessionSocket->async_receive(  // idem for SessionSocket
    boost::asio::buffer(receive_buffer_, 1024),
    [&](const boost::system::error_code error, 
        std::size_t bytes_received) 
    {
      if (!ec)
      {
        result = ByteArray(receive_buffer_, bytes_received);
        got_callback = true;
      }
      Deadline.cancel();
    });

  Deadline.async_wait([&](const boost::system::error_code ec) 
  {
     if (!ec)
     {
       SessionSocket->cancel();
     }
  });        

  IOService.run();

  return result;
}
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
  • Michaël Roy - Thanks - I'll give that a go. It looks better than what I stumbled upon. I trust that it works, but I'll mark this as the answer as soon as I confirm tomorrow. – Olympionex Jun 18 '17 at 10:03
  • @olympionex thought about it, there is also this alternative: setting the timeout option of the socket drectly. https://stackoverflow.com/questions/292997/can-you-set-so-rcvtimeo-and-so-sndtimeo-socket-options-in-boost-asio – Michaël Roy Jun 18 '17 at 10:19
  • Michaël Roy - I read about that initially, but this detailed post seems to strongly advise against it: https://stackoverflow.com/a/30428941/3708683 – Olympionex Jun 18 '17 at 19:45
  • Michaël Roy - When I run the code you posted, it is currently just hanging in io_service::run(). According to the documentation: he run() function blocks until all work has finished and there are no more handlers to be dispatched, or until the io_service has been stopped. I don't know of any other outstanding work being done, but I'll have to do some more debugging to see what is going on. I had tried just run() before with the same result which is why I went to using the boolean to terminate the loop calling run_once. – Olympionex Jun 18 '17 at 23:53
  • I also tried calling io_service::reset() before the async_receive call as the documentation says: This function must be called prior to any second or later set of invocations of the run(), run_one(), poll() or poll_one() functions when a previous invocation of these functions returned due to the io_service being stopped or running out of work. After a call to reset(), the io_service object's stopped() function will return false. This function must not be called while there are any unfinished calls to the run(), run_one(), poll() or poll_one() functions. – Olympionex Jun 19 '17 at 00:04
  • May be you have outstanding work on IOService from another async entity, which would indicate other issues with your architecture. Are you sending as well? Do you have other connections using this particular io_service? You should only call run(), or run_once() in 1 spot. Having simultaneous instances of run*() may lead to undefined behavior. I personally call run() from within its own thread and have all the work done asynchronously. You function is synchronous, you may need to move the async_receive and timeout handlers to class scope and handle read results asynchronously. – Michaël Roy Jun 19 '17 at 11:00
0

Reading the conversation below M. Roy's answer, your goal is to make sure that IOService.run(); returns. All points are valid, the instance of boost::asio::io_service should only be run once (meaning not simultaneously but it could be run multiple times in series) per thread of execution so it is imperative to know how it is used. That said, to make the IOService stop I would amend M. Roy's solution like so:

ByteArray SessionInfo::Read(const boost::posix_time::time_duration &timeout) {
  Deadline.expires_from_now(timeout);
  auto got_callback{false};
  auto result = ByteArray();

  SessionSocket->async_receive(
      boost::asio::buffer(receive_buffer_, 1024),
      [&](const boost::system::error_code error, 
          std::size_t bytes_received)  {
        if (!ec) {
          result = ByteArray(receive_buffer_, bytes_received);
          got_callback = true;
        }
        Deadline.cancel();
      });

  Deadline.async_wait(
      [&](const boost::system::error_code ec) {
        if (!ec) {
          SessionSocket->cancel();
          IOService.stop();
        }
      });        

  IOService.run();
  return result;
}
Tom Trebicky
  • 638
  • 1
  • 5
  • 11