diff --git a/rust/connlib/tunnel/src/io.rs b/rust/connlib/tunnel/src/io.rs index fd4733c8a..68e3f8c48 100644 --- a/rust/connlib/tunnel/src/io.rs +++ b/rust/connlib/tunnel/src/io.rs @@ -94,15 +94,6 @@ impl Io { return Poll::Ready(Ok(Input::DnsResponse(query, response))); } - if let Some(timeout) = self.timeout.as_mut() { - if timeout.poll_unpin(cx).is_ready() { - let deadline = timeout.deadline().into(); - self.timeout.as_mut().take(); // Clear the timeout. - - return Poll::Ready(Ok(Input::Timeout(deadline))); - } - } - if let Poll::Ready(network) = self.sockets.poll_recv_from(ip4_buffer, ip6_bffer, cx)? { return Poll::Ready(Ok(Input::Network(network))); } @@ -113,6 +104,15 @@ impl Io { return Poll::Ready(Ok(Input::Device(packet))); } + if let Some(timeout) = self.timeout.as_mut() { + if timeout.poll_unpin(cx).is_ready() { + let deadline = timeout.deadline().into(); + self.timeout.as_mut().take(); // Clear the timeout. + + return Poll::Ready(Ok(Input::Timeout(deadline))); + } + } + Poll::Pending } diff --git a/rust/connlib/tunnel/src/lib.rs b/rust/connlib/tunnel/src/lib.rs index e1956eb8d..dce7b1eb9 100644 --- a/rust/connlib/tunnel/src/lib.rs +++ b/rust/connlib/tunnel/src/lib.rs @@ -40,6 +40,14 @@ mod tests; const MAX_UDP_SIZE: usize = (1 << 16) - 1; const REALM: &str = "firezone"; +/// How many times we will at most loop before force-yielding from [`ClientTunnel::poll_next_event`] & [`GatewayTunnel::poll_next_event`]. +/// +/// It is obviously system-dependent, how long it takes for the event loop to exhaust these iterations. +/// It has been measured that on GitHub's standard Linux runners, 3000 iterations is roughly 1s during an iperf run. +/// With 5000, we could not reproduce the force-yielding to be needed. +/// Thus, it is chosen as a safe, upper boundary that is not meant to be hit (and thus doesn't affect performance), yet acts as a safe guard, just in case. +const MAX_EVENTLOOP_ITERS: u32 = 5000; + pub type GatewayTunnel = Tunnel; pub type ClientTunnel = Tunnel; @@ -93,7 +101,7 @@ impl ClientTunnel { } pub fn poll_next_event(&mut self, cx: &mut Context<'_>) -> Poll> { - loop { + for _ in 0..MAX_EVENTLOOP_ITERS { if let Some(e) = self.role_state.poll_event() { return Poll::Ready(Ok(e)); } @@ -164,6 +172,10 @@ impl ClientTunnel { return Poll::Pending; } + + self.role_state.handle_timeout(Instant::now()); // Ensure time advances, even if we are busy handling packets. + cx.waker().wake_by_ref(); // Schedule another wake-up with the runtime to avoid getting suspended forever. + Poll::Pending } } @@ -185,7 +197,7 @@ impl GatewayTunnel { } pub fn poll_next_event(&mut self, cx: &mut Context<'_>) -> Poll> { - loop { + for _ in 0..MAX_EVENTLOOP_ITERS { if let Some(other) = self.role_state.poll_event() { return Poll::Ready(Ok(other)); } @@ -246,6 +258,10 @@ impl GatewayTunnel { return Poll::Pending; } + + self.role_state.handle_timeout(Instant::now(), Utc::now()); // Ensure time advances, even if we are busy handling packets. + cx.waker().wake_by_ref(); // Schedule another wake-up with the runtime to avoid getting suspended forever. + Poll::Pending } }