Skip to content

Commit

Permalink
Use cool down period if TCP accept fails
Browse files Browse the repository at this point in the history
  • Loading branch information
mvd-ows authored and faern committed Oct 3, 2023
1 parent 5d7c1fe commit 6277a0d
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Line wrap the file at 100 chars. Th


## [Unreleased]
### Changed
- Use cool down period if TCP accept fails. This avoids excessive CPU usage e.g. when there are no
free file descriptors available to be allocated.


## [0.3.0] - 2023-02-28
Expand Down
35 changes: 35 additions & 0 deletions src/exponential_backoff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::cmp;
use std::time::Duration;

/// Simple exponential backoff
pub struct ExponentialBackoff {
start_delay: Duration,
max_delay: Duration,
current_delay: Duration,
}

impl ExponentialBackoff {
/// Creates a new exponential backoff instance starting with delay
/// `start_delay` and maxing out at `max_delay`.
pub fn new(start_delay: Duration, max_delay: Duration) -> Self {
Self {
start_delay,
max_delay,
current_delay: start_delay,
}
}

/// Resets the exponential backoff so that the next delay is the start delay again.
pub fn reset(&mut self) {
self.current_delay = self.start_delay;
}

/// Returns the next delay. This is twice as long as last returned delay,
/// up until `max_delay` is reached.
pub fn next_delay(&mut self) -> Duration {
let delay = self.current_delay;
let next_cooldown = self.current_delay.mul_f64(2.0);
self.current_delay = cmp::min(next_cooldown, self.max_delay);
delay
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub use udp2tcp::Udp2Tcp;
mod forward_traffic;
mod logging;
mod tcp_options;
mod exponential_backoff;

pub use tcp_options::{ApplyTcpOptionsError, TcpOptions};

Expand Down
17 changes: 16 additions & 1 deletion src/tcp2udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
//! to UDP.

use crate::logging::Redact;
use crate::exponential_backoff::ExponentialBackoff;
use err_context::{BoxedErrorExt as _, ErrorExt as _, ResultExt as _};
use std::convert::Infallible;
use std::fmt;
use std::io;
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket};
use tokio::time::sleep;

#[derive(Debug)]
#[cfg_attr(feature = "clap", derive(clap::Parser))]
Expand Down Expand Up @@ -149,6 +151,8 @@ async fn process_tcp_listener(
tcp_recv_timeout: Option<Duration>,
tcp_nodelay: bool,
) -> ! {
let mut cooldown =
ExponentialBackoff::new(Duration::from_millis(50), Duration::from_millis(5000));
loop {
match tcp_listener.accept().await {
Ok((tcp_stream, tcp_peer_addr)) => {
Expand All @@ -169,8 +173,17 @@ async fn process_tcp_listener(
log::error!("Error: {}", error.display("\nCaused by: "));
}
});
cooldown.reset();
}
Err(error) => {
log::error!("Error when accepting incoming TCP connection: {}", error);

// If the process runs out of file descriptors, it will fail to accept a socket.
// But that socket will also remain in the queue, so it will fail again immediately.
// This will busy loop consuming the CPU and filling any logs. To prevent this,
// delay between failed socket accept operations.
sleep(cooldown.next_delay()).await;
}
Err(error) => log::error!("Error when accepting incoming TCP connection: {}", error),
}
}
}
Expand Down Expand Up @@ -215,3 +228,5 @@ async fn process_socket(

Ok(())
}


0 comments on commit 6277a0d

Please sign in to comment.