mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-18 00:00:09 +02:00
time: do not panic on timeout(Duration::MAX) (#3551)
It is tempting to use very large `Duration` value to get a practically infinite timeout. Before this commit Tokio panics on checked Instant + Duration overflow. This commit implements very simple fix: if Instant + Duration overflows, we use duration = 30 years. Better fix should avoid firing a timer on duration overflow. It requires deeper understanding how timers work, but also it is not clear, for example, what `Sleep::deadline` function should return. Similar fix is done for `sleep`.
This commit is contained in:
@@ -58,7 +58,10 @@ pub fn sleep_until(deadline: Instant) -> Sleep {
|
||||
// Alias for old name in 0.x
|
||||
#[cfg_attr(docsrs, doc(alias = "delay_for"))]
|
||||
pub fn sleep(duration: Duration) -> Sleep {
|
||||
sleep_until(Instant::now() + duration)
|
||||
match Instant::now().checked_add(duration) {
|
||||
Some(deadline) => sleep_until(deadline),
|
||||
None => sleep_until(Instant::far_future()),
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
@@ -168,6 +171,10 @@ impl Sleep {
|
||||
Sleep { deadline, entry }
|
||||
}
|
||||
|
||||
pub(crate) fn far_future() -> Sleep {
|
||||
Self::new_timeout(Instant::far_future())
|
||||
}
|
||||
|
||||
/// Returns the instant at which the future will complete.
|
||||
pub fn deadline(&self) -> Instant {
|
||||
self.deadline
|
||||
|
||||
@@ -54,6 +54,14 @@ impl Instant {
|
||||
Instant { std }
|
||||
}
|
||||
|
||||
pub(crate) fn far_future() -> Instant {
|
||||
// Roughly 30 years from now.
|
||||
// API does not provide a way to obtain max `Instant`
|
||||
// or convert specific date in the future to instant.
|
||||
// 1000 years overflows on macOS, 100 years overflows on FreeBSD.
|
||||
Self::now() + Duration::from_secs(86400 * 365 * 30)
|
||||
}
|
||||
|
||||
/// Convert the value into a `std::time::Instant`.
|
||||
pub fn into_std(self) -> std::time::Instant {
|
||||
self.std
|
||||
|
||||
@@ -49,7 +49,11 @@ pub fn timeout<T>(duration: Duration, future: T) -> Timeout<T>
|
||||
where
|
||||
T: Future,
|
||||
{
|
||||
let delay = Sleep::new_timeout(Instant::now() + duration);
|
||||
let deadline = Instant::now().checked_add(duration);
|
||||
let delay = match deadline {
|
||||
Some(deadline) => Sleep::new_timeout(deadline),
|
||||
None => Sleep::far_future(),
|
||||
};
|
||||
Timeout::new_with_delay(future, delay)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,33 @@ async fn future_and_timeout_in_future() {
|
||||
assert_ready_ok!(fut.poll()).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn very_large_timeout() {
|
||||
time::pause();
|
||||
|
||||
// Not yet complete
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// copy-paste unstable `Duration::MAX`
|
||||
let duration_max = Duration::from_secs(u64::MAX) + Duration::from_nanos(999_999_999);
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = task::spawn(timeout(duration_max, rx));
|
||||
|
||||
// Ready!
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
// Turn the timer, it runs for the elapsed time
|
||||
time::advance(Duration::from_secs(86400 * 365 * 10)).await;
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
// Complete the future
|
||||
tx.send(()).unwrap();
|
||||
|
||||
assert_ready_ok!(fut.poll()).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deadline_now_elapses() {
|
||||
use futures::future::pending;
|
||||
|
||||
Reference in New Issue
Block a user