Info
This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Need help understanding tokio::time::timeout mechanics
Hello all! I'm new both to Rust and tokio, and I find myself at sea even reading the docs. The timeout function seems straightforward enough, but then you have this scary sentence:
Note that the timeout is checked before polling the future, so if the future does not yield during execution then it is possible for the future to complete and exceed the timeout without returning an error.
Does that mean the future could exceed the timeout by microseconds and not be caught in time to trigger a timeout, therefore technically timing out without an error, or does it mean a long-running blocking task could run forever without triggering timeout? I assumed the former, but now I'm hitting a sticking point in my code that suggests the latter.
What I'm trying to accomplish is to measure timeouts caused by synchronous std::net::tcp::TcpStream read/write operations within an async function, but to make sure I understood the tokio timeout mechanics I wrote the following test code:
// main.rs:
use tokio::time::{Duration, timeout};
use wrinkledytime::{concurrent_test_sleeper, sync_test_busy_driver, sync_test_sleeper_driver};
#[tokio::main(flavor = "current_thread")]
async fn main() {
let res_async_sleep = match timeout(
Duration::from_secs(5),
concurrent_test_sleeper(Duration::from_secs(10)),
)
.await
{
Ok(_) => String::from("uh oh, async time is disobeying me!"),
Err(e) => format!("async sleep timed out as expected with {}", e),
};
dbg!(res_async_sleep);
let res_sync_sleep = match timeout(
Duration::from_secs(5),
sync_test_sleeper_driver(Duration::from_secs(10)),
)
.await
{
Ok(_) => String::from("uh oh, sync sleep time is disobeying me!"),
Err(e) => format!("sync sleep timed out as expected with {}", e),
};
dbg!(res_sync_sleep);
let res_sync_busy = match timeout(
Duration::from_secs(5),
sync_test_busy_driver(Duration::from_secs(10)),
)
.await
{
Ok(_) => String::from("uh oh, sync busy time is disobeying me!"),
Err(e) => format!("sync busy timed out as expected with {}", e),
};
dbg!(res_sync_busy);
}
// lib.rs
use tokio::time::{Duration, sleep};
pub async fn concurrent_test_sleeper(duration: Duration) -> std::result::Result<(), &'static str> {
sleep(duration).await;
Ok(())
}
pub async fn sync_test_sleeper_driver(duration: Duration) -> std::result::Result<(), &'static str> {
sync_test_sleeper(duration)
}
pub async fn sync_test_busy_driver(duration: Duration) -> std::result::Result<(), &'static str> {
sync_test_busy(duration)
}
pub fn sync_test_sleeper(duration: Duration) -> std::result::Result<(), &'static str> {
std::thread::sleep(duration);
Ok(())
}
pub fn sync_test_busy(duration: Duration) -> std::result::Result<(), &'static str> {
let clock = std::time::SystemTime::now();
loop {
if clock.elapsed().unwrap() >= duration {
break;
}
}
dbg!("busy for {:?} ms", clock.elapsed().unwrap());
Ok(())
}
The concurrent_test_sleeper() future's timeout is detected successfully and uses the classic example with tokio::time::sleep(). However, neither of the sync test function timeouts were detected. If I understand correctly, the sync_test_sleeper() and sync_test_busy() functions are both essentially what I would be seeing if std::net::tcp::TcpStream operations ran long, essentially blocking the calling thread. As I feared, tokio timeout allows both those functions to run as long as they want, double the timeout duration. It doesn't detect that a timeout occurred after the fact either, returning an Ok result.
This insight raises the following questions for me:
thanks very much!
4 posts - 2 participants
🏷️ rust_feed