Skip to content

Commit 013eca3

Browse files
authored
ref(server): make idle time of a http connection configurable (#4248)
With the idle time configurable we can prevent a pile up of open connections which never see any activity. See also on the hyper issue tracker: ``` hyperium/hyper#3743 hyperium/hyper#1628 hyperium/hyper#2355 hyperium/hyper#2827 ```
1 parent ee46890 commit 013eca3

File tree

6 files changed

+391
-74
lines changed

6 files changed

+391
-74
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Allow `sample_rate` to be float type when deserializing `DynamicSamplingContext`. ([#4181](https://github.com/getsentry/relay/pull/4181))
1919
- Support inbound filters for profiles. ([#4176](https://github.com/getsentry/relay/pull/4176))
2020
- Scrub lower-case redis commands. ([#4235](https://github.com/getsentry/relay/pull/4235))
21+
- Make the maximum idle time of a HTTP connection configurable. ([#4248](https://github.com/getsentry/relay/pull/4248))
2122

2223
**Internal**:
2324

relay-config/src/config.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -633,10 +633,17 @@ struct Limits {
633633
/// The maximum number of seconds to wait for pending envelopes after receiving a shutdown
634634
/// signal.
635635
shutdown_timeout: u64,
636-
/// server keep-alive timeout in seconds.
636+
/// Server keep-alive timeout in seconds.
637637
///
638638
/// By default keep-alive is set to a 5 seconds.
639639
keepalive_timeout: u64,
640+
/// Server idle timeout in seconds.
641+
///
642+
/// The idle timeout limits the amount of time a connection is kept open without activity.
643+
/// Setting this too short may abort connections before Relay is able to send a response.
644+
///
645+
/// By default there is no idle timeout.
646+
idle_timeout: Option<u64>,
640647
/// The TCP listen backlog.
641648
///
642649
/// Configures the TCP listen backlog for the listening socket of Relay.
@@ -673,6 +680,7 @@ impl Default for Limits {
673680
query_timeout: 30,
674681
shutdown_timeout: 10,
675682
keepalive_timeout: 5,
683+
idle_timeout: None,
676684
tcp_listen_backlog: 1024,
677685
}
678686
}
@@ -2319,6 +2327,11 @@ impl Config {
23192327
Duration::from_secs(self.values.limits.keepalive_timeout)
23202328
}
23212329

2330+
/// Returns the server idle timeout in seconds.
2331+
pub fn idle_timeout(&self) -> Option<Duration> {
2332+
self.values.limits.idle_timeout.map(Duration::from_secs)
2333+
}
2334+
23222335
/// TCP listen backlog to configure on Relay's listening socket.
23232336
pub fn tcp_listen_backlog(&self) -> u32 {
23242337
self.values.limits.tcp_listen_backlog
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::io;
2+
use std::time::Duration;
3+
4+
use axum_server::accept::Accept;
5+
use socket2::TcpKeepalive;
6+
use tokio::net::TcpStream;
7+
8+
use crate::services::server::io::IdleTimeout;
9+
use crate::statsd::RelayCounters;
10+
11+
#[derive(Clone, Debug, Default)]
12+
pub struct RelayAcceptor {
13+
tcp_keepalive: Option<TcpKeepalive>,
14+
idle_timeout: Option<Duration>,
15+
}
16+
17+
impl RelayAcceptor {
18+
/// Creates a new [`RelayAcceptor`] which only configures `TCP_NODELAY`.
19+
pub fn new() -> Self {
20+
Default::default()
21+
}
22+
23+
/// Configures the acceptor to enable TCP keep-alive.
24+
///
25+
/// The `timeout` is used to configure the keep-alive time as well as interval.
26+
/// A zero duration timeout disables TCP keep-alive.
27+
///
28+
/// `retries` configures the amount of keep-alive probes.
29+
pub fn tcp_keepalive(mut self, timeout: Duration, retries: u32) -> Self {
30+
if timeout.is_zero() {
31+
self.tcp_keepalive = None;
32+
return self;
33+
}
34+
35+
let mut keepalive = socket2::TcpKeepalive::new().with_time(timeout);
36+
#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "solaris")))]
37+
{
38+
keepalive = keepalive.with_interval(timeout);
39+
}
40+
#[cfg(not(any(
41+
target_os = "openbsd",
42+
target_os = "redox",
43+
target_os = "solaris",
44+
target_os = "windows"
45+
)))]
46+
{
47+
keepalive = keepalive.with_retries(retries);
48+
}
49+
self.tcp_keepalive = Some(keepalive);
50+
51+
self
52+
}
53+
54+
/// Configures an idle timeout for the connection.
55+
///
56+
/// Whenever there is no activity on a connection for the specified timeout,
57+
/// the connection is closed.
58+
///
59+
/// Note: This limits the total idle time of a duration and unlike read and write timeouts
60+
/// also limits the time a connection is kept alive without requests.
61+
pub fn idle_timeout(mut self, idle_timeout: Option<Duration>) -> Self {
62+
self.idle_timeout = idle_timeout;
63+
self
64+
}
65+
}
66+
67+
impl<S> Accept<TcpStream, S> for RelayAcceptor {
68+
type Stream = IdleTimeout<TcpStream>;
69+
type Service = S;
70+
type Future = std::future::Ready<io::Result<(Self::Stream, Self::Service)>>;
71+
72+
fn accept(&self, stream: TcpStream, service: S) -> Self::Future {
73+
let mut keepalive = "ok";
74+
let mut nodelay = "ok";
75+
76+
if let Some(tcp_keepalive) = &self.tcp_keepalive {
77+
let sock_ref = socket2::SockRef::from(&stream);
78+
if let Err(e) = sock_ref.set_tcp_keepalive(tcp_keepalive) {
79+
relay_log::trace!("error trying to set TCP keepalive: {e}");
80+
keepalive = "error";
81+
}
82+
}
83+
84+
if let Err(e) = stream.set_nodelay(true) {
85+
relay_log::trace!("failed to set TCP_NODELAY: {e}");
86+
nodelay = "error";
87+
}
88+
89+
relay_statsd::metric!(
90+
counter(RelayCounters::ServerSocketAccept) += 1,
91+
keepalive = keepalive,
92+
nodelay = nodelay
93+
);
94+
95+
let stream = IdleTimeout::new(stream, self.idle_timeout);
96+
std::future::ready(Ok((stream, service)))
97+
}
98+
}

0 commit comments

Comments
 (0)