1use grpc_quic_metrics::record_connection;
4use grpc_quic_transport::{QuicConnection, QuicEndpoint, TlsConfig};
5use std::net::SocketAddr;
6use std::sync::Arc;
7use tokio::sync::Semaphore;
8use tracing::{error, info};
9
10use crate::acceptor::handle_request;
11use crate::error::ServerError;
12
13#[derive(Debug)]
15pub struct QuicServerBuilder {
16 tls: Option<TlsConfig>,
17 max_concurrent_streams: Option<u32>,
18 graceful_timeout: std::time::Duration,
19}
20
21impl Default for QuicServerBuilder {
22 fn default() -> Self {
23 Self {
24 tls: None,
25 max_concurrent_streams: None,
26 graceful_timeout: std::time::Duration::from_secs(30),
27 }
28 }
29}
30
31impl QuicServerBuilder {
32 pub fn tls(mut self, tls: TlsConfig) -> Self {
34 self.tls = Some(tls);
35 self
36 }
37
38 pub fn max_concurrent_streams(mut self, limit: u32) -> Self {
40 self.max_concurrent_streams = Some(limit);
41 self
42 }
43
44 pub fn graceful_timeout(mut self, timeout: std::time::Duration) -> Self {
46 self.graceful_timeout = timeout;
47 self
48 }
49
50 pub fn build(self) -> QuicServer {
53 QuicServer {
54 tls: self.tls,
55 max_concurrent_streams: self.max_concurrent_streams.unwrap_or(256),
56 graceful_timeout: self.graceful_timeout,
57 }
58 }
59}
60
61#[derive(Debug)]
80pub struct QuicServer {
81 pub(crate) tls: Option<TlsConfig>,
82 pub(crate) max_concurrent_streams: u32,
83 pub(crate) graceful_timeout: std::time::Duration,
84}
85
86impl QuicServer {
87 pub fn builder() -> QuicServerBuilder {
89 QuicServerBuilder::default()
90 }
91
92 pub async fn serve<S>(self, addr: SocketAddr, service: S) -> Result<(), ServerError>
94 where
95 S: tower::Service<
96 http::Request<tonic::body::BoxBody>,
97 Response = http::Response<tonic::body::BoxBody>,
98 > + Clone
99 + Send
100 + Sync
101 + 'static,
102 S::Future: Send + 'static,
103 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + Send,
104 {
105 self.serve_with_shutdown(addr, service, std::future::pending())
106 .await
107 }
108
109 pub async fn serve_with_shutdown<S, F>(
111 self,
112 addr: SocketAddr,
113 service: S,
114 signal: F,
115 ) -> Result<(), ServerError>
116 where
117 S: tower::Service<
118 http::Request<tonic::body::BoxBody>,
119 Response = http::Response<tonic::body::BoxBody>,
120 > + Clone
121 + Send
122 + Sync
123 + 'static,
124 S::Future: Send + 'static,
125 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + Send,
126 F: std::future::Future<Output = ()> + Send + 'static,
127 {
128 let tls = self.tls.clone().ok_or_else(|| {
129 ServerError::Transport(grpc_quic_transport::TransportError::Tls(
130 "TLS config is required".into(),
131 ))
132 })?;
133
134 let endpoint = grpc_quic_transport::QuicEndpoint::server(addr, tls)?;
135 self.serve_with_incoming_shutdown(endpoint, service, signal)
136 .await
137 }
138
139 pub async fn serve_with_incoming<S>(
141 self,
142 endpoint: QuicEndpoint,
143 service: S,
144 ) -> Result<(), ServerError>
145 where
146 S: tower::Service<
147 http::Request<tonic::body::BoxBody>,
148 Response = http::Response<tonic::body::BoxBody>,
149 > + Clone
150 + Send
151 + Sync
152 + 'static,
153 S::Future: Send + 'static,
154 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + Send,
155 {
156 self.serve_with_incoming_shutdown(endpoint, service, std::future::pending())
157 .await
158 }
159
160 #[tracing::instrument(skip(self, endpoint, service, signal))]
162 pub async fn serve_with_incoming_shutdown<S, F>(
163 self,
164 endpoint: QuicEndpoint,
165 service: S,
166 signal: F,
167 ) -> Result<(), ServerError>
168 where
169 S: tower::Service<
170 http::Request<tonic::body::BoxBody>,
171 Response = http::Response<tonic::body::BoxBody>,
172 > + Clone
173 + Send
174 + Sync
175 + 'static,
176 S::Future: Send + 'static,
177 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + Send,
178 F: std::future::Future<Output = ()> + Send + 'static,
179 {
180 info!(
181 local_addr = ?endpoint.local_addr(),
182 max_concurrent_streams = self.max_concurrent_streams,
183 "QuicServer listening"
184 );
185
186 let mut signal = Box::pin(signal);
187
188 let stream_limit = (self.max_concurrent_streams as usize).max(64) * 4;
192 let stream_semaphore = Arc::new(Semaphore::new(stream_limit));
193
194 let mut join_set = tokio::task::JoinSet::new();
195 let (cancel_tx, _) = tokio::sync::broadcast::channel::<()>(1);
196
197 loop {
198 tokio::select! {
199 _ = &mut signal => {
200 info!("shutdown signal received, rejecting new connections");
201 endpoint.reject_new_connections();
202 let _ = cancel_tx.send(());
203 break;
204 }
205 conn_res = endpoint.accept() => {
206 let conn_res = match conn_res {
207 Some(res) => res,
208 None => break,
209 };
210 let conn = match conn_res {
211 Ok(c) => {
212 record_connection("server");
213 c
214 }
215 Err(e) => {
216 error!(error = %e, "failed to accept connection");
217 continue;
218 }
219 };
220
221 let service = service.clone();
222 let sem = stream_semaphore.clone();
223 let cancel_rx = cancel_tx.subscribe();
224 join_set.spawn(async move {
225 if let Err(e) = handle_connection(conn, service, sem, cancel_rx).await {
226 error!(error = %e, "connection handling error");
227 }
228 });
229 }
230 }
231 }
232
233 let wait_for_connections = async {
235 while let Some(result) = join_set.join_next().await {
236 if let Err(e) = result {
237 error!("connection task failed: {e}");
238 }
239 }
240 };
241
242 if tokio::time::timeout(self.graceful_timeout, wait_for_connections)
243 .await
244 .is_err()
245 {
246 error!("graceful shutdown timed out, closing endpoint forcefully");
247 endpoint.close(0, b"shutdown timeout");
248 }
249
250 Ok(())
251 }
252}
253
254#[tracing::instrument(skip(conn, service, semaphore, cancel_rx))]
255async fn handle_connection<S>(
256 conn: QuicConnection,
257 service: S,
258 semaphore: Arc<Semaphore>,
259 mut cancel_rx: tokio::sync::broadcast::Receiver<()>,
260) -> Result<(), ServerError>
261where
262 S: tower::Service<
263 http::Request<tonic::body::BoxBody>,
264 Response = http::Response<tonic::body::BoxBody>,
265 > + Clone
266 + Send
267 + Sync
268 + 'static,
269 S::Future: Send + 'static,
270 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + Send,
271{
272 use grpc_quic_core::server::build_server_conn;
273
274 let mut h3_conn = match build_server_conn(conn.get_ref().clone()).await {
275 Ok(c) => c,
276 Err(e) => {
277 error!("failed to build h3 server connection: {e}");
278 return Ok(());
279 }
280 };
281
282 let mut request_join_set = tokio::task::JoinSet::new();
283
284 loop {
285 tokio::select! {
286 _ = cancel_rx.recv() => {
287 let _ = h3_conn.shutdown(0).await;
288 break;
291 }
292 accept_res = h3_conn.accept() => {
293 let resolver = match accept_res {
294 Ok(Some(r)) => r,
295 Ok(None) => break,
296 Err(e) => {
297 error!("h3 accept error: {e}");
298 break;
299 }
300 };
301
302 let (req, stream) = match resolver.resolve_request().await {
303 Ok(pair) => pair,
304 Err(e) => {
305 error!("resolve request error: {e}");
306 continue;
307 }
308 };
309
310 let permit = match semaphore.clone().try_acquire_owned() {
311 Ok(p) => p,
312 Err(_) => {
313 error!("server overloaded — dropping request");
314 continue;
315 }
316 };
317
318 let service = service.clone();
319 request_join_set.spawn(async move {
320 let _permit = permit;
321 if let Err(e) = handle_request(req, stream, service).await {
322 error!(error = %e, "request handling error");
323 }
324 });
325 }
326 }
327 }
328
329 while let Some(res) = request_join_set.join_next().await {
331 if let Err(e) = res {
332 error!("request task failed: {e}");
333 }
334 }
335
336 drop(h3_conn);
337
338 Ok(())
339}