I'm relatively new to tokio (and also Rust) so it's possible that I'm missing something important here. Basically I have a control loop where I use select to poll some futures. My select will wait on a timeout (if there is one to wait on) and the arrival of a datagram on a UDP socket. It looks like this:
loop {
let next_timeout: Option<Instant> = self.timeouts.get_next();
tokio::select! {
_ = async { sleep_until(next_timeout.unwrap()).await }, if next_timeout.is_some() => {
println!("timeout expired:");
}
result = self.udpsocket.recv_from(&mut buf) => {
let (size, addr) = result?;
println!("received = {}", String::from_utf8(buf[..size].to_vec()).unwrap());
}
}
}
The problem is that the async block with the sleep_until never resolves, even if there is a timeout (next_timeout.is_some() == true) and it has expired. If I send a packet over the network, the recv_from branch gets executed immediately.
If I however disable the recv_from branch when there is a timeout to wait on:
result = self.udpsocket.recv_from(&mut buf), if next_timeout.is_none() =>
Then the sleep_until resolves as expected.
I also tried to put the async block behind a variable and pin it:
let f = async { sleep_until(next_timeout.unwrap()).await };
tokio::pin!(f);
but it didn't change anything.
I really don't know how to make sense of this behavior. It's the first time I'm really stuck while learning Rust/Tokio, so that's why I'm here any help is very appreciated! Thanks.
It will resolve as long as no UDP data arrives before next_timeout expires.
If next_timeout exists, what happens is this:
The async block containing sleep_until() will be evaluated and polled. However, until the timeout expires, there is no return value to work with, so tokio::select! will continue to poll both branches.
If no UDP data arrives before the expiry of next_timeout, the first branch returns and you get your "timeout expired:" message as expected.
But if you receive UDP data in the meantime, tokio::select! will return from the second branch instead. It will also cancel the sleep_until and enter the next iteration of your loop, which means you will never see the expiry message.
Waits on multiple concurrent branches, returning when the first branch completes, cancelling the remaining branches.
I'm guessing what you want is for the sleep_until function to block the tokio::select! so that no UDP data is read until the timeout expires. If that is the case, you can cut and paste the sleep_until into the code block containing println!("timeout expired:"). Something like this:
This will cause the first branch to return immediately when there is a timeout, which will cause sleep_until to execute, which in this case will block the current loop.
Yes! And looking at the docs of that function I now see that I need to set the nonblocking mode. I just did it and things work as expected. Thanks for pointing me to the right direction.