From 00fc7d70ddc8aba120f1263a36d32633d2c25e47 Mon Sep 17 00:00:00 2001 From: Antonio Vicente Date: Tue, 27 Jan 2026 11:46:05 -0800 Subject: [PATCH] Move on_timeout handling to the main part of the work loop This change simplifies the event loop and removes the need to bias the select! to avoid starvation by serializing all the event loop actions and clarifying the start and end of the work loop iteration. --- tokio-quiche/src/quic/io/worker.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tokio-quiche/src/quic/io/worker.rs b/tokio-quiche/src/quic/io/worker.rs index d5841d6e362..5440a159598 100644 --- a/tokio-quiche/src/quic/io/worker.rs +++ b/tokio-quiche/src/quic/io/worker.rs @@ -225,6 +225,16 @@ where loop { let now = Instant::now(); + if let Some(deadline) = current_deadline { + if deadline <= now { + qconn.on_timeout(); + + self.write_state.next_release_time = None; + current_deadline = None; + sleep.as_mut().reset((now + DEFAULT_SLEEP).into()); + } + } + self.write_state.has_pending_data = true; while self.write_state.has_pending_data { @@ -313,19 +323,8 @@ where select! { biased; - () = &mut sleep => { - // It's very important that we keep the timeout arm at the top of this loop so - // that we poll it every time we need to. Since this is a biased `select!`, if - // we put this behind another arm, we could theoretically starve the sleep arm - // and hang connections. - // - // See https://docs.rs/tokio/latest/tokio/macro.select.html#fairness for more - qconn.on_timeout(); - - self.write_state.next_release_time = None; - current_deadline = None; - sleep.as_mut().reset((now + DEFAULT_SLEEP).into()); - } + // The sleep branch will be handled by the current_deadline and on_timeout check on the next iteration of the loop. + () = &mut sleep => (), Some(pkt) = incoming_recv.recv() => ctx.in_pkt = Some(pkt), directive = self.wait_for_data_or_handshake(qconn, application) => { match directive? {