Discussion:
[Boost-users] std::future::wait_for() not working in Boost UDP socket async receive operation
j***@gmail.com
2016-11-28 21:55:20 UTC
Permalink
The good people over at stackoverflow were not able to help me with this
issue that I expected to be trivial.

I am writing a UDP server app with Boost that should listen on a socket for
5 seconds and if no datagram has been received within these 5 seconds, move
on to do other things.

After some research I decided to try the solution based on std::future.

The problem is that the call to wait_for() always times out as if no data
was received. But if I set a breakpoint on the line that executes after the
timeout and that I inspect variables I see that the buffer contains the
received datagram and the remote_endpoint object contains the address of
the client. In other words the socket receive works as expected but the
std::future::wait_for() does not react. why?

Here is my test server code:

{
try
{
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
std::thread thread([&io_service]() { io_service.run(); });

udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000));
char recv_buf[8];

for (;;)
{
ZeroMemory(recv_buf, 8);
udp::endpoint remote_endpoint;
std::future<std::size_t> recv_length;

recv_length = socket.async_receive_from(
boost::asio::buffer(recv_buf),
remote_endpoint,
0,
boost::asio::use_future);

if (recv_length.wait_for(
std::chrono::seconds(5)) == std::future_status::timeout)
{
printf("time out. Nothing received.\n");
}
else
{
printf("received something: %s\n", recv_buf);
}
}
io_service.stop();
thread.join();
}
catch (std::exception& e)
{
printf("Error: %s\n", e.what());
}

return 0;
}
TONGARI J
2016-11-29 03:26:59 UTC
Permalink
Post by j***@gmail.com
The good people over at stackoverflow were not able to help me with this
issue that I expected to be trivial.
I am writing a UDP server app with Boost that should listen on a socket
for 5 seconds and if no datagram has been received within these 5 seconds,
move on to do other things.
After some research I decided to try the solution based on std::future.
The problem is that the call to wait_for() always times out as if no data
was received. But if I set a breakpoint on the line that executes after the
timeout and that I inspect variables I see that the buffer contains the
received datagram and the remote_endpoint object contains the address of
the client. In other words the socket receive works as expected but the
std::future::wait_for() does not react. why?
{
try
{
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
std::thread thread([&io_service]() { io_service.run(); });
udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000));
char recv_buf[8];
for (;;)
{
ZeroMemory(recv_buf, 8);
udp::endpoint remote_endpoint;
std::future<std::size_t> recv_length;
recv_length = socket.async_receive_from(
boost::asio::buffer(recv_buf),
remote_endpoint,
0,
boost::asio::use_future);
if (recv_length.wait_for(
std::chrono::seconds(5)) == std::future_status::timeout)
{
printf("time out. Nothing received.\n");
}
else
{
printf("received something: %s\n", recv_buf);
}
}
io_service.stop();
thread.join();
}
catch (std::exception& e)
{
printf("Error: %s\n", e.what());
}
return 0;
}
The problem only happens if you have previous timeout.
You can call socket.cancel() in the timeout branch.

I think the problem is that, if you don't call socket.cancel() on timeout,
in the next round you're calling async_receive_from before the previous
call completes, which is not allowed.
Gavin Lambert
2016-11-29 04:01:28 UTC
Permalink
Post by TONGARI J
The problem only happens if you have previous timeout.
You can call socket.cancel() in the timeout branch.
I think the problem is that, if you don't call socket.cancel() on
timeout, in the next round you're calling async_receive_from before the
previous call completes, which is not allowed.
Note that even if you call cancel(), you still aren't allowed to
immediately call async_receive_from() again.

You need to cancel *and then* wait without timeout for the future to be
ready before you can make another request. This should be fast (since
you're just waiting for the cancellation to complete) but may not be
zero time.

(If you were using completion handlers rather than futures, this is
equivalent to waiting for the handler to be called with a cancellation
error before making another request.)
TONGARI J
2016-11-29 10:09:30 UTC
Permalink
Post by Gavin Lambert
Post by TONGARI J
The problem only happens if you have previous timeout.
You can call socket.cancel() in the timeout branch.
I think the problem is that, if you don't call socket.cancel() on
timeout, in the next round you're calling async_receive_from before the
previous call completes, which is not allowed.
Note that even if you call cancel(), you still aren't allowed to
immediately call async_receive_from() again.
You need to cancel *and then* wait without timeout for the future to be
ready before you can make another request. This should be fast (since
you're just waiting for the cancellation to complete) but may not be zero
time.
(If you were using completion handlers rather than futures, this is
equivalent to waiting for the handler to be called with a cancellation
error before making another request.)
I think we're wrong about whether it's allowed to call async_receive_from()
multiple times before it completes.
It was from my impression that we can't do that, but skimming over the ASIO
docs I found nothing says that.
Experiments show that it works, so I think my argument in this regard was
wrong, at least ASIO itself does not enforce the restriction.

So calling socket.cancel() w/o waiting the cancellation to complete should
be OK.
JC Francois
2016-11-29 21:14:42 UTC
Permalink
Post by TONGARI J
So calling socket.cancel() w/o waiting the cancellation to complete
should be OK.
Yes indeed, this was the missing element. I added a call to
socket.cancel() in response to the timeout condition and it worked.

Thanks a lot!

/~JC
Gavin Lambert
2016-11-30 00:12:48 UTC
Permalink
Post by TONGARI J
I think we're wrong about whether it's allowed to call
async_receive_from() multiple times before it completes.
It was from my impression that we can't do that, but skimming over the
ASIO docs I found nothing says that.
Experiments show that it works, so I think my argument in this regard
was wrong, at least ASIO itself does not enforce the restriction.
It's technically permitted but it's rarely a good idea to have multiple
pending operations of the same type (eg. more than one read or more than
one write) on the same socket/whatever, as it becomes undefined what
order things end up at the other end, which is rarely acceptable. (You
can sometimes get away with concurrent writes with UDP, if done
carefully; you typically can't get away with it with TCP. It's almost
never sensible to have concurrent reads with either.)

And also because user code will tend to supply the same buffers for
multiple such operations of the same type, and that's definitely not
allowed to use the same buffer in multiple concurrent operations.

It's ok to have a pending read and a pending write on the same
socket/whatever (as long as they use separate buffers), but then you
can't selectively cancel() one of them, which may not be what you want.

(It is actually possible to cancel an individual operation in WinAPI,
but I don't think ASIO exposes this; possibly this isn't supported on
Linux or because ASIO's own API doesn't really provide operation handles
to let you specify what you want to be cancelled.)

Loading...