How to set a timeout on blocking sockets in boost asio?
Asked Answered
L

10

51

Is there a way to cancel a pending operation (without disconnect) or set a timeout for the boost library functions?

I.e. I want to set a timeout on blocking socket in boost asio?

socket.read_some(boost::asio::buffer(pData, maxSize), error_);

Example: I want to read some from the socket, but I want to throw an error if 10 seconds have passed.

Lally answered 15/11, 2008 at 0:31 Comment(0)
P
9

Under Linux/BSD the timeout on I/O operations on sockets is directly supported by the operating system. The option can be enabled via setsocktopt(). I don't know if boost::asio provides a method for setting it or exposes the socket scriptor to allow you to directly set it -- the latter case is not really portable.

For a sake of completeness here's the description from the man page:

SO_RCVTIMEO and SO_SNDTIMEO

          Specify the receiving or sending  timeouts  until  reporting  an
          error.  The argument is a struct timeval.  If an input or output
          function blocks for this period of time, and data has been  sent
          or  received,  the  return  value  of  that function will be the
          amount of data transferred; if no data has been transferred  and
          the  timeout has been reached then -1 is returned with errno set
          to EAGAIN or EWOULDBLOCK just as if the socket was specified  to
          be  non-blocking.   If  the timeout is set to zero (the default)
          then the operation  will  never  timeout.   Timeouts  only  have
          effect  for system calls that perform socket I/O (e.g., read(2),
          recvmsg(2), send(2), sendmsg(2)); timeouts have  no  effect  for
          select(2), poll(2), epoll_wait(2), etc.
Pentahedron answered 15/11, 2008 at 11:34 Comment(4)
This would be a great solution, but they don't have such socket options. See socket options here: boost.org/doc/libs/1_37_0/doc/html/boost_asio/reference.htmlLally
But from what I can tell, asio's read_some() will still internally continue to loop forever if it reads nothing, thus canceling the effect of SO_RCVTIMEO you may have set on the native socket. Looks like using select() with socket.native() will still be the most obvious way to have a timeout.Butchery
@Stéphane Is this the only possible solution and if yes do you have an example?Staunch
@Staunch That was 5 years ago! My use of ASIO is from an old version of Boost. I'd have hoped that by now ASIO would have been fixed to allow timeouts -- have they not fixed this yet? As for examples...calling select() on a ASIO socket is simple. int rc = select(s.native() + 1, &fds, NULL, NULL, &timeout);Butchery
G
36

TL;DR

socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 200 });

FULL ANSWER This question keep being asked over and over again for many years. Answers I saw so far are quite poor. I'll add this info right here in one of the first occurrences of this question.

Everybody trying to use ASIO to simplify their networking code would be perfectly happy if the author would just add an optional parameter timeout to all sync and async io functions. Unfortunately, this is unlikely to happen (in my humble opinion, just for ideological reasons, after all, AS in ASIO is for a reason).

So these are the ways to skin this poor cat available so far, none of them especially appetizing. Let's say we need 200ms timeout.

1) Good (bad) old socket API:

const int timeout = 200;
::setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof timeout);//SO_SNDTIMEO for send ops

Please note those peculiarities: - const int for timeout - on Windows the required type is actually DWORD, but the current set of compilers luckily has it the same, so const int will work both in Win and Posix world. - (const char*) for value. On Windows const char* is required, Posix requires const void*, in C++ const char* will convert to const void* silently while the opposite is not true.

Advantages: works and probably will always work as the socket API is old and stable. Simple enough. Fast. Disadvantages: technically might require appropriate header files (different on Win and even different UNIX flavors) for setsockopt and the macros, but current implementation of ASIO pollutes global namespace with them anyway. Requires a variable for timeout. Not type-safe. On Windows, requires that the socket is in overlapped mode to work (which current ASIO implementation luckily uses, but it is still an implementation detail). UGLY!

2) Custom ASIO socket option:

typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; //somewhere in your headers to be used everywhere you need it
//...
socket.set_option(rcv_timeout_option{ 200 });

Advantages: Simple enough. Fast. Beautiful (with typedef). Disadvantages: Depends on ASIO implementation detail, which might change (but OTOH everything will change eventually, and such detail is less likely to change then public APIs subject to standardization). But in case this happens, you'll have to either write a class according to https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/SettableSocketOption.html (which is of course a major PITA thanks to obvious overengineering of this part of ASIO) or better yet revert to 1.

3) Use C++ async/future facilities.

#include <future>
#include <chrono>
//...
auto status = std::async(std::launch::async, [&] (){ /*your stream ops*/ })
    .wait_for(std::chrono::milliseconds{ 200 });
switch (status)
    {
    case std::future_status::deferred:
    //... should never happen with std::launch::async
        break;
    case std::future_status::ready:
    //...
        break;
    case std::future_status::timeout:
    //...
        break;
    }

Advantages: standard. Disadvantages: always starts a new thread (in practice), which is relatively slow (might be good enough for clients, but will lead to DoS vulnerability for servers as threads and sockets are "expensive" resources). Don't try to use std::launch::deferred instead of std::launch::async to avoid new thread launch as wait_for will always return future_status::deferred without trying to run the code.

4) The method prescribed by ASIO - use async operations only (which is not really the answer to the question).

Advantages: good enough for servers too if huge scalability for short transactions is not required. Disadvantages: quite wordy (so I will not even include examples - see ASIO examples). Requires very careful lifetime management of all your objects used both by async operations and their completion handlers, which in practice requires all classes containing and using such data in async operations be derived from enable_shared_from_this, which requires all such classes allocated on heap, which means (at least for short operations) that scalability will start taper down after about 16 threads as every heap alloc/dealloc will use a memory barrier.

Goby answered 14/8, 2018 at 21:29 Comment(8)
I'm getting an invalid argument (boost 1.55 with c++98): int recvTimeoutInMS = 1000; const boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> option = boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>(recvTimeoutInMS); socket_.set_option(option);Reyreyes
I think you need a correction in 3rd point-- Sure, we definitely can use std::future as a work-around, but it doesn't always require an async (specifically in boost-asio), you can get std::future by initiating asynchronous operation with use_future model: no threads involved. @Pavel VerevkinDania
@Explorer_N reading the answer once again doesn't help me to convert the code to c++98Reyreyes
Has nothing to do with version of C++.Dania
in solution 3), the destructor of std::future will block the thread in case of timeout.Plush
SO_RCVTIMEO and SO_SNDTIMEO on POSIX systems require struct timeval argument, not int. In fact, int will work correctly only on Win32.Partisan
In my testing, setting rcv timeouts won't work, at least in linux. asio async reads won't return even if the low level socket times out (see aside in https://mcmap.net/q/354840/-can-i-read-from-a-socket-synchronously-using-boost-asio-with-a-timeout-on-a-multithreaded-i-o-service ). Additionally, there is now also a method using futures outlined here: stackoverflow.com/a/22967714.Levis
Doesn't work now. libc++abi.dylib: terminating with uncaught exception of type boost::wrapexcept<boost::system::system_error>: set_option: Bad file descriptorSuperfluous
D
26

When this question was asked, I guess ASIO did not have any example on how to accomplish what the OP needed, that is to timeout a blocking operation such as a blocking socket operation. Now there exists examples to show you exactly how to do this. the example seems long, but that is because it is WELL commented. It shows how to use the ioservice in a 'one shot' kind of mode.

I think the example is a great solution. The other solutions here break portability and don't take advantage of ioservice. if portability is not important and the ioservice seems like to much overhead --THEN-- you should not be using ASIO. No matter what, you will have an ioservice created (almost all ASIO functionality depends on it, even sync sockets) so, take advantage of it.

Timeout a blocking asio tcp operation

Timeout a blocking asio udp operation

The ASIO documentation has been updated, so check it out for new examples on how to overcome some of the 'gotchas' ASIO use to have.

Doralia answered 28/12, 2012 at 2:57 Comment(3)
On Windows, io_service.run_one() never blocks on async_read (at least in Boost 1.59.0), resulting in 100% cpu usage.Partisan
The examples are long because the documentation is 3x redundant. I don't consider that "WELL commented".Kerstin
Links are dead now...Diet
E
10

You could do an async_read and also set a timer for your desired time out. Then if the timer fires, call cancel on your socket object. Otherwise if your read happens, you can cancel your timer. This requires you to use an io_service object of course.

edit: Found a code snippet for you that does this

http://lists.boost.org/Archives/boost/2007/04/120339.php

Extinctive answered 26/11, 2008 at 2:41 Comment(2)
This snippet contains a call to io_service::reset(). But the documentation for it says This function must not be called while there are any unfinished calls to the run(), run_one(), poll() or poll_one() functions.Sprawl
If this code is to run in an asio async callback event, that is, inside something called by io_service::run, I suspect you get undefined behaviour.Sprawl
P
10

I had the same question, and after some research, the simplest, cleanest solution I could come up with was to get the underlying native socket, and do a select until there was data to read. Select will take a timeout parameter. Of course, working with the native socket starts to go against the point of using asio in the first place, but again, this seems to be the cleanest way. As far as I could tell, asio doesn't provide a way to do this for synchronous usage easily. Code:

        // socket here is:  boost::shared_ptr<boost::asio::ip::tcp::socket> a_socket_ptr

        // Set up a timed select call, so we can handle timeout cases.

        fd_set fileDescriptorSet;
        struct timeval timeStruct;

        // set the timeout to 30 seconds
        timeStruct.tv_sec = 30;
        timeStruct.tv_usec = 0;
        FD_ZERO(&fileDescriptorSet);

        // We'll need to get the underlying native socket for this select call, in order
        // to add a simple timeout on the read:

        int nativeSocket = a_socket_ptr->native();

        FD_SET(nativeSocket,&fileDescriptorSet);

        select(nativeSocket+1,&fileDescriptorSet,NULL,NULL,&timeStruct);

        if(!FD_ISSET(nativeSocket,&fileDescriptorSet)){ // timeout

                std::string sMsg("TIMEOUT on read client data. Client IP: ");

                sMsg.append(a_socket_ptr->remote_endpoint().address().to_string());

                throw MyException(sMsg);
        }

        // now we know there's something to read, so read
        boost::system::error_code error;
        size_t iBytesRead = a_socket_ptr->read_some(boost::asio::buffer(myVector), error);

        ...

Perhaps this will be useful for your situation.

Paluas answered 20/4, 2011 at 18:43 Comment(2)
boost.org/doc/libs/1_80_0/doc/html/boost_asio/reference/…Damnedest
this is the only answer what works for me. @Damnedest what is this link for?Rejuvenate
P
9

Under Linux/BSD the timeout on I/O operations on sockets is directly supported by the operating system. The option can be enabled via setsocktopt(). I don't know if boost::asio provides a method for setting it or exposes the socket scriptor to allow you to directly set it -- the latter case is not really portable.

For a sake of completeness here's the description from the man page:

SO_RCVTIMEO and SO_SNDTIMEO

          Specify the receiving or sending  timeouts  until  reporting  an
          error.  The argument is a struct timeval.  If an input or output
          function blocks for this period of time, and data has been  sent
          or  received,  the  return  value  of  that function will be the
          amount of data transferred; if no data has been transferred  and
          the  timeout has been reached then -1 is returned with errno set
          to EAGAIN or EWOULDBLOCK just as if the socket was specified  to
          be  non-blocking.   If  the timeout is set to zero (the default)
          then the operation  will  never  timeout.   Timeouts  only  have
          effect  for system calls that perform socket I/O (e.g., read(2),
          recvmsg(2), send(2), sendmsg(2)); timeouts have  no  effect  for
          select(2), poll(2), epoll_wait(2), etc.
Pentahedron answered 15/11, 2008 at 11:34 Comment(4)
This would be a great solution, but they don't have such socket options. See socket options here: boost.org/doc/libs/1_37_0/doc/html/boost_asio/reference.htmlLally
But from what I can tell, asio's read_some() will still internally continue to loop forever if it reads nothing, thus canceling the effect of SO_RCVTIMEO you may have set on the native socket. Looks like using select() with socket.native() will still be the most obvious way to have a timeout.Butchery
@Stéphane Is this the only possible solution and if yes do you have an example?Staunch
@Staunch That was 5 years ago! My use of ASIO is from an old version of Boost. I'd have hoped that by now ASIO would have been fixed to allow timeouts -- have they not fixed this yet? As for examples...calling select() on a ASIO socket is simple. int rc = select(s.native() + 1, &fds, NULL, NULL, &timeout);Butchery
B
4

Following on to what grepsedawk has mentioned. There are a few examples showing how to cancel long running asynchronous operations after a period of time, under the Timeouts section within asio doco. Boost Asio Examples . Async TCP client helped me the most.

Happy Asyncing :)

Biagi answered 8/1, 2012 at 23:56 Comment(0)
U
3

Even years after the original question, there is still not a satisfying answer.

Manually using select is not a good option

  1. file descriptor number must be less than 1024
  2. FD may be spuriously reported as ready due to wrong checksum.

Call io_service.run_one() is also a bad idea, because there may be other async options that needs an io_service to always run(). And boost's document about blocking tcp client is hard to comprehend.

So here is my solution. The key idea is the following:

{
    Semaphore r_sem;
    boost::system::error_code r_ec;
    boost::asio::async_read(s,buffer,
                            [this, &r_ec, &r_sem](const boost::system::error_code& ec_, size_t) {
                                r_ec=ec_;
                                r_sem.notify();
                            });
    if(!r_sem.wait_for(std::chrono::seconds(3))) // wait for 3 seconds
    {
        s.cancel();
        r_sem.wait();
        throw boost::system::system_error(boost::asio::error::try_again);
    }
    else if(r_ec)
        throw boost::system::system_error(r_ec);
}

Here Semaphore is just a mutex and a condition_variable.
wait_for is implemented by http://en.cppreference.com/w/cpp/thread/condition_variable/wait_for

Full code is at https://github.com/scinart/cpplib/blob/master/include/asio.hpp
Example: https://github.com/scinart/cpplib/blob/6e9a1690bf68971b809be34dfe432949d9a9f727/standalone_example/boost_block_tcp_client_server.cpp

-- update -- Example link updated.

Undervest answered 11/9, 2017 at 8:47 Comment(4)
std::future<size_t> f = asio::async_read(s,buffer, asio::use_future); f.wait_for(3s);Damnedest
Isn't the future solution missing the error code though ?Hypothermal
@GabrielRavier It's raised as a system_error on get(), just as like the blocking API or when using yield_context or use_awaitable as completion tokens.Damnedest
@GabrielRavier alternatively, use a token adaptor like as_tuple or redirect_error: coliru.stacked-crooked.com/a/a868eeef535b407bDamnedest
C
3

SO_RCVTIMEO and SO_SNDTIMEO take in a timeval struct from "sys/time.h" instead of an int. So @Pavel Verevkin 's option 1. would need to take in a timeval instead of an int and option 2. would require implementing a class since boost::asio::detail::socket_option::integer only stores a single integer value.

Catarrhine answered 22/2, 2020 at 22:10 Comment(0)
D
0

Caution: Using SO_RCVTIMEO may not always help with timeout on blocking calls. I ran into a problem on *nix systems with infinite blocking call in the pool (for a more detailed explanation, see SO_RCVTIME and SO_RCVTIMEO not affecting Boost.Asio operations) when everything worked on Windows. The use of the non_blocking method and the corresponding handling of error::would_block (WSAEWOULDBLOCK) and error::try_again (EAGAIN) errors helped me.

Demulsify answered 20/1, 2023 at 16:38 Comment(0)
P
-2

On *nix, you'd use alarm() so your socket call would fail with EINTR

Penman answered 15/11, 2008 at 5:11 Comment(1)
Wouldn't all your socket calls, in all your threads fail with EINTR? That sounds bad.Bootblack

© 2022 - 2024 — McMap. All rights reserved.