Skip to main content

tako_rs_server/
config.rs

1//! Production-readiness configuration shared by every Tako server transport.
2
3use std::time::Duration;
4
5/// Selectable QUIC congestion controller. Mirrors the controllers shipped by
6/// `quinn::congestion`. Exposed here so HTTP/3 deployments can pick a profile
7/// without depending on quinn directly from the application crate.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum H3Congestion {
10  /// CUBIC — quinn's default and the most widely deployed.
11  #[default]
12  Cubic,
13  /// `NewReno` — older, conservative.
14  NewReno,
15  /// BBR — Google's bandwidth-delay-product controller; useful on
16  /// high-bandwidth, lossy links.
17  Bbr,
18}
19
20/// Production-readiness knobs shared by every Tako server transport.
21///
22/// `Default` mirrors the historical hardcoded values (30 s drain, 30 s header
23/// read, 100 H2 streams, …) so existing call sites keep their behavior. Pass
24/// a populated `ServerConfig` to `*_with_config` entry points to override
25/// individual knobs.
26#[derive(Debug, Clone)]
27pub struct ServerConfig {
28  /// Maximum time the coordinator waits for in-flight connections to finish
29  /// after a shutdown signal. After this elapses, remaining tasks are aborted.
30  pub drain_timeout: Duration,
31  /// Maximum time hyper waits for the request line + headers to arrive.
32  /// `None` disables the timeout (the previous behavior).
33  pub header_read_timeout: Option<Duration>,
34  /// HTTP/1 keep-alive (default `true`).
35  pub keep_alive: bool,
36  /// HTTP/1 keep-alive idle timeout (Hyper default applies if `None`).
37  pub keep_alive_timeout: Option<Duration>,
38  /// HTTP/2 `SETTINGS_MAX_CONCURRENT_STREAMS` cap.
39  pub h2_max_concurrent_streams: u32,
40  /// HTTP/2 `SETTINGS_MAX_HEADER_LIST_SIZE` cap (bytes).
41  pub h2_max_header_list_size: u32,
42  /// HTTP/2 send-buffer cap per stream (bytes).
43  pub h2_max_send_buf_size: usize,
44  /// HTTP/2 pending-accept `RST_STREAM` cap (CVE-2023-44487 mitigation).
45  pub h2_max_pending_accept_reset_streams: usize,
46  /// HTTP/2 keep-alive ping interval. `None` disables.
47  pub h2_keep_alive_interval: Option<Duration>,
48  /// HTTP/3 cap on concurrent client-initiated bidirectional streams. Maps to
49  /// `quinn::TransportConfig::max_concurrent_bidi_streams`.
50  pub h3_max_concurrent_bidi_streams: u32,
51  /// HTTP/3 cap on concurrent client-initiated unidirectional streams. Maps to
52  /// `quinn::TransportConfig::max_concurrent_uni_streams`.
53  pub h3_max_concurrent_uni_streams: u32,
54  /// HTTP/3 idle-timeout (no QUIC packets in either direction). `None` lets
55  /// quinn pick its default; `Some(d)` caps the connection lifetime.
56  pub h3_max_idle_timeout: Option<Duration>,
57  /// HTTP/3 congestion controller selection.
58  pub h3_congestion: H3Congestion,
59  /// Enable QUIC datagrams (RFC 9221) on HTTP/3 connections. Required for
60  /// downstream WebTransport-style traffic.
61  pub h3_enable_datagrams: bool,
62  /// Issue a QUIC Retry packet for each new connection whose source address
63  /// has not been validated. Mitigates UDP source-address-spoofing
64  /// amplification attacks at the cost of one extra round-trip per new client.
65  pub h3_use_retry: bool,
66  /// Per-connection grace given to in-flight HTTP/3 streams to finish after
67  /// the per-connection GOAWAY.
68  ///
69  /// The effective grace at runtime is `min(h3_goaway_grace, drain_timeout)`
70  /// — the server clamps this so a long per-connection grace cannot push the
71  /// total shutdown past the global drain budget. Configuring
72  /// `h3_goaway_grace` larger than `drain_timeout` is therefore a no-op
73  /// beyond the global ceiling.
74  pub h3_goaway_grace: Duration,
75  /// Optional ceiling on concurrent in-flight connections. Enforced via a
76  /// semaphore in the accept loop; `None` disables.
77  pub max_connections: Option<usize>,
78  /// Read deadline applied before the PROXY protocol header is parsed.
79  pub proxy_read_timeout: Duration,
80  /// Maximum time the TLS acceptor waits for the client to complete its
81  /// handshake. A slow / stalled handshake holds a `max_connections` permit
82  /// open indefinitely otherwise — TLS slowloris. Default 10 seconds.
83  pub tls_handshake_timeout: Duration,
84  /// Backoff schedule for `accept()` errors (typically EMFILE/ENFILE).
85  pub accept_backoff: AcceptBackoff,
86}
87
88impl Default for ServerConfig {
89  fn default() -> Self {
90    Self {
91      drain_timeout: Duration::from_secs(30),
92      header_read_timeout: Some(Duration::from_secs(30)),
93      keep_alive: true,
94      keep_alive_timeout: None,
95      h2_max_concurrent_streams: 100,
96      h2_max_header_list_size: 16 * 1024,
97      h2_max_send_buf_size: 1024 * 1024,
98      h2_max_pending_accept_reset_streams: 50,
99      h2_keep_alive_interval: None,
100      h3_max_concurrent_bidi_streams: 100,
101      h3_max_concurrent_uni_streams: 8,
102      h3_max_idle_timeout: Some(Duration::from_secs(30)),
103      h3_congestion: H3Congestion::default(),
104      h3_enable_datagrams: false,
105      h3_use_retry: false,
106      h3_goaway_grace: Duration::from_secs(10),
107      max_connections: None,
108      proxy_read_timeout: Duration::from_secs(10),
109      tls_handshake_timeout: Duration::from_secs(10),
110      accept_backoff: AcceptBackoff::new(),
111    }
112  }
113}
114
115/// Exponential backoff state for `listener.accept()` retry loops.
116///
117/// Accept errors (typically `EMFILE`/`ENFILE` when the process has run out of
118/// file descriptors, or transient `ConnectionAborted` under load) are not fatal
119/// to the listener. Servers should log, sleep, and re-poll. Use [`AcceptBackoff`]
120/// to keep the sleep schedule consistent across transports without duplicating
121/// the constants in every `serve_*` implementation.
122#[derive(Debug, Clone, Copy)]
123pub struct AcceptBackoff {
124  current: Duration,
125  max: Duration,
126}
127
128impl Default for AcceptBackoff {
129  fn default() -> Self {
130    Self::new()
131  }
132}
133
134impl AcceptBackoff {
135  /// Construct with the default 5 ms → 1 s schedule.
136  #[must_use]
137  pub const fn new() -> Self {
138    Self {
139      current: Duration::from_millis(5),
140      max: Duration::from_secs(1),
141    }
142  }
143
144  /// Reset the schedule after a successful accept.
145  #[inline]
146  pub fn reset(&mut self) {
147    self.current = Duration::from_millis(5);
148  }
149
150  /// Sleep for the current backoff and double it (capped at `max`).
151  /// Use the tokio `sleep` so this is cooperative on the runtime that runs
152  /// the accept loop.
153  pub async fn sleep_and_grow(&mut self) {
154    let d = self.current_and_grow();
155    tokio::time::sleep(d).await;
156  }
157
158  /// Returns the current backoff duration and doubles the internal counter
159  /// (capped at `max`). Use this when you need to drive the sleep with a
160  /// non-tokio timer (e.g. `compio::time::sleep`).
161  pub fn current_and_grow(&mut self) -> Duration {
162    let d = self.current;
163    self.current = (self.current * 2).min(self.max);
164    d
165  }
166}