Timeout for boost::beast sync http client
Asked Answered
D

2

5

I am adapting the synchronous HTTP client from the Boost Beast examples. Unfortunately the example client does not include timeout options and sometimes gets stuck in my workloads. I tried adding timeouts with

beast::get_lowest_layer(stream).expires_after(NetworkSettings::BASIC_TIMEOUT);

before calling write/read operations, but those only seem to work when using async_read/write. From what I found, it seems that basic boost asio supports timeouts only for async operations. So my question is whether beast has any capabilities to use a timeout on the blocking connect/read/write calls.

Deweese answered 30/6, 2019 at 23:11 Comment(1)
I don't have experience in Beast, but Have you tried using std::future and its wait_for member function? asio library (wrapped by beast) supports use_future which emulates blocking behaviour as synchronous clients.Archicarp
U
6

Timeouts are not available for synchronous I/O in Asio. Since Beast is a layer above asio, it too does not support timeouts for synchronous I/O. If you want timeouts, you must use asynchronous APIs. You can use a stackful coroutine, or if you have a modern enough compiler you can experiment with stackless coroutines (co_await). These allow you to write code that appears synchronous but using asynchronous interfaces.

The Beast docs are clear on this: "For portability reasons, networking does not provide timeouts or cancellation features for synchronous stream operations."

https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/timeouts.html

If you want to have timeouts on connect operations, use an instance of beast::tcp_stream and call the async_connect member function: https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/timeouts.html#beast.using_io.timeouts.connecting

Undress answered 4/7, 2019 at 3:49 Comment(1)
Thanks for the clarification. I'll just use the asynchronous operations then.Deweese
A
0

You can use something like this.

try change stream.connect(results) to

auto Future = stream.async_connect(endpoint, net::use_future);
if(Future.wait_for(std::chrono::seconds(1)) == std::future_status::timeout){

   std::cout<<"timed_out";
   ....
}else {

}

Bunch of things to note:

1)You may need below header files

#include<boost/asio/use_future.hpp>
#include<chrono>
#include<future>

2) Since you are asyc_* initiating; you need to call ioc.run();

3) You need an another thread to execute ioc.run(); as we are mocking synchronous via asynchronous-- someone have to run the event-loop.

Another approach: you can explicitly set the socket option using its native handle (I've never done it). But before doing that, read this answer https://mcmap.net/q/345681/-how-to-set-a-timeout-on-blocking-sockets-in-boost-asio

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

https://linux.die.net/man/7/socket

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, or EINPROGRESS (for connect(2)) just as if the socket was specified to be nonblocking. 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), and so on.

Archicarp answered 2/7, 2019 at 7:42 Comment(8)
This would refute the idea of using async IO in the first place, right?Hydr
@Hydr Hmmm...for sure blocking IO isn't a good thing( (in-case of scale-ability), but it looks like, the OP is stuck experimenting synchronous client, so I gave a work-around.Archicarp
@Explorer_N Yes, there's no need for futures or non-portable socket options since Beast provides an asynchronous connect function that has a timeout (see boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/…) I have updated my answer to reflect this.Undress
@VinnieFalco, thanks for responding, you know, the OP is aware of timeout operation provided by async_* , but he said he is experimenting synchronous client which means he wants to block till the connect happens and it looks like there is no timeout for sync connect and that is the reason I suggested std::future... Sure co_await works too, but at the end, it is just an another asynchronous model like std::future..About setsockopt-- I leave it up-to the OP's discretion by linking an answer which talks about portability. you still think my answer is incorrect ?Archicarp
@VinnieFalco and you think std::future won't do the Job which OP is looking for?Archicarp
Well std::future uses the asynchronous API (e.g. async_connect with net::use_future). In that case, you might as well use beast::tcp_stream since that will actually cancel the connect for you on a timeout, instead of leaving the operation outstanding which is what happens with the future using the code above.Undress
@VinnieFalco "instead of leaving the operation outstanding which is what happens with the future " not necessary, OP can call cancel on timeout which indeed cancel the pending operation, moreover, the whole reason for future is OP wants to block, using timeout on beast::tcp_stream-- will it give a blocking behaviour?Archicarp
@VinnieFalco, Even from your response what I can see is (correct me if I am wrong) My answer isn't incorrect, yet, it is a plausible one.Archicarp

© 2022 - 2024 — McMap. All rights reserved.