Skip to content

Commit 881664d

Browse files
committed
fix(quinn): handle overdue timers without polling the async timer
drive_timer() used AsyncTimer::poll() to determine whether a protocol deadline had elapsed. Under Tokio's cooperative task budget, Sleep::poll() may return Poll::Pending for an already-expired deadline once the task's budget is exhausted, which can happen when process_conn_events() drains a busy channel. As a result, handle_timeout() is not called even though the deadline has already elapsed. For QUIC, timers such as PTO, loss detection, and idle timeouts are correctness-critical and should not be deferred to a later scheduling round. Fix this by checking runtime.now() >= deadline before consulting the async timer. The clock is not subject to cooperative budgeting. The timer remains responsible only for registering a wakeup when the deadline lies in the future.
1 parent bba5a46 commit 881664d

1 file changed

Lines changed: 11 additions & 4 deletions

File tree

quinn/src/connection.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,16 @@ impl State {
12071207
return false;
12081208
};
12091209

1210+
// Use the clock rather than the async timer to detect expiry: Sleep::poll
1211+
// respects Tokio's cooperative budget and can return Pending for elapsed
1212+
// deadlines.
1213+
let now = self.runtime.now();
1214+
if now >= deadline {
1215+
self.inner.handle_timeout(now);
1216+
self.timer_deadline = None;
1217+
return true;
1218+
}
1219+
12101220
match &mut self.timer {
12111221
// Avoid resetting the timer when the deadline is unchanged.
12121222
Some(delay) if self.timer_deadline != Some(deadline) => {
@@ -1225,13 +1235,10 @@ impl State {
12251235
.expect("timer must exist in this state")
12261236
.as_mut();
12271237
if delay.poll(cx).is_pending() {
1228-
// Since there wasn't a timeout event, there is nothing new
1229-
// for the connection to do
12301238
return false;
12311239
}
12321240

1233-
// A timer expired, so the caller needs to check for
1234-
// new transmits, which might cause new timers to be set.
1241+
// The deadline elapsed in the window between the clock check and poll.
12351242
self.inner.handle_timeout(self.runtime.now());
12361243
self.timer_deadline = None;
12371244
true

0 commit comments

Comments
 (0)