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}