Skip to content

Commit

Permalink
ref(server): make idle time of a http connection configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
Dav1dde committed Nov 13, 2024
1 parent f650706 commit f92738f
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 69 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Allow `sample_rate` to be float type when deserializing `DynamicSamplingContext`. ([#4181](https://github.com/getsentry/relay/pull/4181))
- Support inbound filters for profiles. ([#4176](https://github.com/getsentry/relay/pull/4176))
- Scrub lower-case redis commands. ([#4235](https://github.com/getsentry/relay/pull/4235))
- Makes the maximum idle time of a HTTP connection configurable. ([#4248](https://github.com/getsentry/relay/pull/4248))

**Internal**:

Expand Down
15 changes: 14 additions & 1 deletion relay-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,10 +633,17 @@ struct Limits {
/// The maximum number of seconds to wait for pending envelopes after receiving a shutdown
/// signal.
shutdown_timeout: u64,
/// server keep-alive timeout in seconds.
/// Server keep-alive timeout in seconds.
///
/// By default keep-alive is set to a 5 seconds.
keepalive_timeout: u64,
/// Server idle timeout in seconds.
///
/// The idle timeout limits the amount of time a connection is kept open without activity.
/// Setting this too short may abort connections before Relay is able to send a response.
///
/// By default there is no idle timeout.
idle_timeout: Option<u64>,
/// The TCP listen backlog.
///
/// Configures the TCP listen backlog for the listening socket of Relay.
Expand Down Expand Up @@ -673,6 +680,7 @@ impl Default for Limits {
query_timeout: 30,
shutdown_timeout: 10,
keepalive_timeout: 5,
idle_timeout: None,
tcp_listen_backlog: 1024,
}
}
Expand Down Expand Up @@ -2319,6 +2327,11 @@ impl Config {
Duration::from_secs(self.values.limits.keepalive_timeout)
}

/// Returns the server idle timeout in seconds.
pub fn idle_timeout(&self) -> Option<Duration> {
self.values.limits.idle_timeout.map(Duration::from_secs)
}

/// TCP listen backlog to configure on Relay's listening socket.
pub fn tcp_listen_backlog(&self) -> u32 {
self.values.limits.tcp_listen_backlog
Expand Down
87 changes: 54 additions & 33 deletions relay-server/src/services/server/acceptor.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,79 @@
use std::io;
use std::time::Duration;

use axum_server::accept::Accept;
use relay_config::Config;
use socket2::TcpKeepalive;
use tokio::net::TcpStream;

use crate::services::server::io::IdleTimeout;
use crate::statsd::RelayCounters;

#[derive(Clone, Debug, Default)]
pub struct RelayAcceptor(Option<TcpKeepalive>);
pub struct RelayAcceptor {
tcp_keepalive: Option<TcpKeepalive>,
idle_timeout: Option<Duration>,
}

impl RelayAcceptor {
/// Create a new acceptor that sets `TCP_NODELAY` and keep-alive.
pub fn new(config: &Config, keepalive_retries: u32) -> Self {
Self(build_keepalive(config, keepalive_retries))
/// Creates a new [`RelayAcceptor`] which only configures `TCP_NODELAY`.
pub fn new() -> Self {
Default::default()
}

/// Configures the acceptor to enable TCP keep-alive.
///
/// The `timeout` is used to configure the keep-alive time as well as interval.
/// A zero duration timeout disables TCP keep-alive.
///
/// `retries` configures the amount of keep-alive probes.
pub fn tcp_keepalive(mut self, timeout: Duration, retries: u32) -> Self {
if timeout.is_zero() {
self.tcp_keepalive = None;
return self;
}

let mut ka = socket2::TcpKeepalive::new().with_time(timeout);
#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "solaris")))]
{
ka = ka.with_interval(timeout);
}
#[cfg(not(any(
target_os = "openbsd",
target_os = "redox",
target_os = "solaris",
target_os = "windows"
)))]
{
ka = ka.with_retries(retries);
}
self.tcp_keepalive = Some(ka);

self
}

/// Configures an idle timeout for the connection.
///
/// Whenever there is no activity on a connection for the specified timeout,
/// the connection is closed.
///
/// Note: This limits the total idle time of a duration and unlike read and write timeouts
/// also limits the time a connection is kept alive without requests.
pub fn idle_timeout(mut self, idle_timeout: Option<Duration>) -> Self {
self.idle_timeout = idle_timeout;
self
}
}

impl<S> Accept<TcpStream, S> for RelayAcceptor {
type Stream = std::pin::Pin<Box<IdleTimeout<TcpStream>>>;
type Stream = IdleTimeout<TcpStream>;
type Service = S;
type Future = std::future::Ready<io::Result<(Self::Stream, Self::Service)>>;

fn accept(&self, stream: TcpStream, service: S) -> Self::Future {
let mut keepalive = "ok";
let mut nodelay = "ok";

if let Self(Some(ref tcp_keepalive)) = self {
if let Some(tcp_keepalive) = &self.tcp_keepalive {
let sock_ref = socket2::SockRef::from(&stream);
if let Err(e) = sock_ref.set_tcp_keepalive(tcp_keepalive) {
relay_log::trace!("error trying to set TCP keepalive: {e}");
Expand All @@ -46,32 +92,7 @@ impl<S> Accept<TcpStream, S> for RelayAcceptor {
nodelay = nodelay
);

let stream = Box::pin(IdleTimeout::new(stream, std::time::Duration::from_secs(5)));
let stream = IdleTimeout::new(stream, self.idle_timeout);
std::future::ready(Ok((stream, service)))
}
}

fn build_keepalive(config: &Config, keepalive_retries: u32) -> Option<TcpKeepalive> {
let ka_timeout = config.keepalive_timeout();
if ka_timeout.is_zero() {
return None;
}

let mut ka = TcpKeepalive::new().with_time(ka_timeout);
#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "solaris")))]
{
ka = ka.with_interval(ka_timeout);
}

#[cfg(not(any(
target_os = "openbsd",
target_os = "redox",
target_os = "solaris",
target_os = "windows"
)))]
{
ka = ka.with_retries(keepalive_retries);
}

Some(ka)
}
Loading

0 comments on commit f92738f

Please sign in to comment.