mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-02 00:00:11 +02:00
time: make timeout robust against budget-depleting tasks (#4314)
This commit is contained in:
+7
-11
@@ -59,13 +59,9 @@ impl Budget {
|
|||||||
const fn unconstrained() -> Budget {
|
const fn unconstrained() -> Budget {
|
||||||
Budget(None)
|
Budget(None)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cfg_rt_multi_thread! {
|
fn has_remaining(self) -> bool {
|
||||||
impl Budget {
|
self.0.map(|budget| budget > 0).unwrap_or(true)
|
||||||
fn has_remaining(self) -> bool {
|
|
||||||
self.0.map(|budget| budget > 0).unwrap_or(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,16 +103,16 @@ fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn has_budget_remaining() -> bool {
|
||||||
|
CURRENT.with(|cell| cell.get().has_remaining())
|
||||||
|
}
|
||||||
|
|
||||||
cfg_rt_multi_thread! {
|
cfg_rt_multi_thread! {
|
||||||
/// Sets the current task's budget.
|
/// Sets the current task's budget.
|
||||||
pub(crate) fn set(budget: Budget) {
|
pub(crate) fn set(budget: Budget) {
|
||||||
CURRENT.with(|cell| cell.set(budget))
|
CURRENT.with(|cell| cell.set(budget))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn has_budget_remaining() -> bool {
|
|
||||||
CURRENT.with(|cell| cell.get().has_remaining())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_rt! {
|
cfg_rt! {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
//! [`Timeout`]: struct@Timeout
|
//! [`Timeout`]: struct@Timeout
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
coop,
|
||||||
time::{error::Elapsed, sleep_until, Duration, Instant, Sleep},
|
time::{error::Elapsed, sleep_until, Duration, Instant, Sleep},
|
||||||
util::trace,
|
util::trace,
|
||||||
};
|
};
|
||||||
@@ -169,15 +170,33 @@ where
|
|||||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
let me = self.project();
|
let me = self.project();
|
||||||
|
|
||||||
|
let had_budget_before = coop::has_budget_remaining();
|
||||||
|
|
||||||
// First, try polling the future
|
// First, try polling the future
|
||||||
if let Poll::Ready(v) = me.value.poll(cx) {
|
if let Poll::Ready(v) = me.value.poll(cx) {
|
||||||
return Poll::Ready(Ok(v));
|
return Poll::Ready(Ok(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check the timer
|
let has_budget_now = coop::has_budget_remaining();
|
||||||
match me.delay.poll(cx) {
|
|
||||||
Poll::Ready(()) => Poll::Ready(Err(Elapsed::new())),
|
let delay = me.delay;
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
|
let poll_delay = || -> Poll<Self::Output> {
|
||||||
|
match delay.poll(cx) {
|
||||||
|
Poll::Ready(()) => Poll::Ready(Err(Elapsed::new())),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let (true, false) = (had_budget_before, has_budget_now) {
|
||||||
|
// if it is the underlying future that exhausted the budget, we poll
|
||||||
|
// the `delay` with an unconstrained one. This prevents pathological
|
||||||
|
// cases where the underlying future always exhausts the budget and
|
||||||
|
// we never get a chance to evaluate whether the timeout was hit or
|
||||||
|
// not.
|
||||||
|
coop::with_unconstrained(poll_delay)
|
||||||
|
} else {
|
||||||
|
poll_delay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,3 +135,16 @@ async fn deadline_future_elapses() {
|
|||||||
fn ms(n: u64) -> Duration {
|
fn ms(n: u64) -> Duration {
|
||||||
Duration::from_millis(n)
|
Duration::from_millis(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn timeout_is_not_exhausted_by_future() {
|
||||||
|
let fut = timeout(ms(1), async {
|
||||||
|
let mut buffer = [0u8; 1];
|
||||||
|
loop {
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
let _ = tokio::io::empty().read(&mut buffer).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(fut.await.is_err());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user