Skip to main content

vibeio_http/h3/
mod.rs

1//! Native HTTP/3 server (RFC 9114) over the [`transport`] abstraction.
2//!
3//! A single connection task owns the control plane ([`control`]) and
4//! accept loops; each accepted request stream is handed to its own task
5//! through an async [`tokio::sync::Mutex`], sharing the connection's
6//! QPACK codecs ([`stream::SharedCodecs`]) with the driver. Requests and
7//! responses are streamed with trailers; `100 Continue` and `103 Early
8//! Hints` interim responses are supported, as are `Date` header caching
9//! and graceful shutdown via a [`CancellationToken`].
10
11mod control;
12mod date;
13mod error;
14mod frame;
15mod options;
16pub mod qpack;
17#[cfg(feature = "h3-quinn")]
18pub mod quinn;
19mod settings;
20mod stream;
21pub mod transport;
22mod upgrade;
23
24pub use error::{H3Error, TransportError};
25pub use frame::{Frame, FrameDecoder, FrameError, Settings};
26pub use options::*;
27
28use std::{
29    pin::Pin,
30    rc::Rc,
31    sync::{
32        atomic::{AtomicBool, Ordering},
33        Arc,
34    },
35    task::{Context, Poll},
36};
37
38use bytes::Bytes;
39use futures_util::stream::FuturesUnordered;
40use futures_util::{ready, Future, FutureExt, StreamExt};
41use http::{Request, Response, StatusCode};
42use http_body::{Body, Frame as BodyFrame};
43use http_body_util::BodyExt;
44use tokio_util::sync::CancellationToken;
45
46use crate::{
47    h3::{
48        control::{ControlEvent, ControlStreams},
49        date::DateCache,
50        stream::{RequestStream, SharedCodecs, StreamError},
51    },
52    EarlyHints, HttpProtocol, Incoming, Upgrade, Upgraded,
53};
54
55/// Application error codes from RFC 9114 Section 8.1 used by the driver.
56const H3_NO_ERROR: u64 = 0x0100;
57const H3_REQUEST_REJECTED: u64 = 0x010b;
58
59/// Per-connection budgets for the resets a hostile peer can force the
60/// server to send or observe (RFC 9114 Section 10.5); `None` disables a
61/// budget.
62#[derive(Debug, Clone, Copy)]
63pub(super) struct ResetLimits {
64    pub(super) max_local_error_resets: Option<usize>,
65    pub(super) max_pending_accept_resets: Option<usize>,
66}
67
68/// Connection-level reset accounting, shared between the driver and the
69/// per-request tasks (which cannot themselves close the QUIC connection).
70#[derive(Debug)]
71struct ConnResetState {
72    limits: ResetLimits,
73    /// RESET_STREAM frames this endpoint has sent for the peer's protocol
74    /// errors (bounded by `limits.max_local_error_resets`).
75    local_error_resets: usize,
76    /// Streams the peer terminated (RESET_STREAM or STOP_SENDING) before
77    /// this endpoint accepted them (bounded by
78    /// `limits.max_pending_accept_resets`).
79    pending_accept_resets: usize,
80    /// Application error code the connection must close with; the driver
81    /// drains it on its next turn.
82    close_code: Option<u64>,
83}
84
85impl ConnResetState {
86    /// Records a locally sent protocol-error reset; returns the code the
87    /// connection must close with when the budget is exceeded.
88    #[inline]
89    fn note_local_error_reset(&mut self) -> Option<u64> {
90        match self.limits.max_local_error_resets {
91            Some(max) if self.local_error_resets >= max => Some(H3Error::ExcessiveLoad.code()),
92            _ => {
93                self.local_error_resets += 1;
94                None
95            }
96        }
97    }
98
99    /// Records a peer-terminated, never-accepted stream; returns the code
100    /// the connection must close with when the budget is exceeded.
101    #[inline]
102    fn note_pending_accept_reset(&mut self) -> Option<u64> {
103        match self.limits.max_pending_accept_resets {
104            Some(max) if self.pending_accept_resets >= max => Some(H3Error::ExcessiveLoad.code()),
105            _ => {
106                self.pending_accept_resets += 1;
107                None
108            }
109        }
110    }
111}
112
113/// The shared handle on a request stream: the connection task, the request
114/// task, the response body, and a possible upgrade all work through it.
115type SharedRequest = Arc<tokio::sync::Mutex<RequestStream>>;
116
117static HTTP3_INVALID_HEADERS: [http::header::HeaderName; 5] = [
118    http::header::HeaderName::from_static("keep-alive"),
119    http::header::HeaderName::from_static("proxy-connection"),
120    http::header::CONNECTION,
121    http::header::TRANSFER_ENCODING,
122    http::header::UPGRADE,
123];
124
125/// The read half of a shared request stream, as a [`Body`].
126struct H3BodyState {
127    stream: SharedRequest,
128    data_done: bool,
129    send_continue_body: Option<Arc<AtomicBool>>,
130}
131
132pub(crate) struct H3Body {
133    inner: tokio::sync::Mutex<H3BodyState>,
134}
135
136impl H3Body {
137    #[inline]
138    fn new(stream: SharedRequest, send_continue_body: Option<Arc<AtomicBool>>) -> Self {
139        Self {
140            inner: tokio::sync::Mutex::new(H3BodyState {
141                stream,
142                data_done: false,
143                send_continue_body,
144            }),
145        }
146    }
147}
148
149impl Body for H3Body {
150    type Data = Bytes;
151    type Error = std::io::Error;
152
153    #[inline]
154    fn poll_frame(
155        self: Pin<&mut Self>,
156        cx: &mut Context<'_>,
157    ) -> Poll<Option<Result<BodyFrame<Self::Data>, Self::Error>>> {
158        let mut inner = match std::pin::pin!(self.inner.lock()).poll_unpin(cx) {
159            Poll::Ready(inner) => inner,
160            Poll::Pending => return Poll::Pending,
161        };
162
163        if !inner.data_done {
164            loop {
165                let mut stream = match std::pin::pin!(inner.stream.lock()).poll_unpin(cx) {
166                    Poll::Ready(stream) => stream,
167                    Poll::Pending => return Poll::Pending,
168                };
169                match stream.poll_recv_data(cx) {
170                    Poll::Ready(Ok(Some(data))) => {
171                        if data.is_empty() {
172                            continue;
173                        }
174                        return Poll::Ready(Some(Ok(BodyFrame::data(data))));
175                    }
176                    Poll::Ready(Ok(None)) => {
177                        drop(stream);
178                        inner.data_done = true;
179                        break;
180                    }
181                    Poll::Ready(Err(err)) => {
182                        return Poll::Ready(Some(Err(h3_stream_error_to_io(err))));
183                    }
184                    Poll::Pending => {
185                        if let Some(scb) = inner.send_continue_body.as_ref() {
186                            scb.store(true, std::sync::atomic::Ordering::Relaxed);
187                        }
188                        return Poll::Pending;
189                    }
190                };
191            }
192        }
193
194        let mut stream = match std::pin::pin!(inner.stream.lock()).poll_unpin(cx) {
195            Poll::Ready(stream) => stream,
196            Poll::Pending => {
197                if let Some(scb) = inner.send_continue_body.as_ref() {
198                    scb.store(true, std::sync::atomic::Ordering::Relaxed);
199                }
200                return Poll::Pending;
201            }
202        };
203        match stream.poll_recv_trailers(cx) {
204            Poll::Ready(Ok(Some(trailers))) => Poll::Ready(Some(Ok(BodyFrame::trailers(trailers)))),
205            Poll::Ready(Ok(None)) => Poll::Ready(None),
206            Poll::Ready(Err(err)) => Poll::Ready(Some(Err(h3_stream_error_to_io(err)))),
207            Poll::Pending => {
208                if let Some(scb) = inner.send_continue_body.as_ref() {
209                    scb.store(true, std::sync::atomic::Ordering::Relaxed);
210                }
211                Poll::Pending
212            }
213        }
214    }
215}
216
217#[inline]
218fn h3_control_error_to_io(error: control::ControlError) -> std::io::Error {
219    std::io::Error::other(error)
220}
221
222#[inline]
223fn h3_transport_error_to_io(error: TransportError) -> std::io::Error {
224    std::io::Error::other(error)
225}
226
227#[inline]
228fn h3_stream_error_to_io(error: stream::StreamError) -> std::io::Error {
229    std::io::Error::other(error)
230}
231
232#[inline]
233fn remove_invalid_http3_headers(headers: &mut http::HeaderMap) {
234    for header in &HTTP3_INVALID_HEADERS {
235        headers.remove(header);
236    }
237    if headers
238        .get(http::header::TE)
239        .is_some_and(|v| v != "trailers")
240    {
241        headers.remove(http::header::TE);
242    }
243}
244
245/// Waits until the peer's SETTINGS bound the QPACK encoder, so field
246/// sections can be encoded (RFC 9204 Section 5).
247///
248/// The control plane wakes this task (via the shared waiters map) when
249/// the SETTINGS frame arrives.
250#[inline]
251async fn wait_for_encoder(shared: &Arc<parking_lot::Mutex<SharedCodecs>>, stream_id: u64) {
252    std::future::poll_fn(|cx| {
253        let mut shared = shared.lock();
254        if shared.encoder.is_some() {
255            shared.waiters.remove(&stream_id);
256            return Poll::Ready(());
257        }
258        shared.waiters.insert(stream_id, cx.waker().clone());
259        Poll::Pending
260    })
261    .await
262}
263
264/// Writes an interim (1xx) response HEADERS frame.
265#[inline]
266async fn send_interim_response(
267    stream: &SharedRequest,
268    status: StatusCode,
269) -> Result<(), std::io::Error> {
270    let mut guard = stream.lock().await;
271    std::future::poll_fn(|cx| guard.poll_send_response(cx, status, &http::HeaderMap::new()))
272        .await
273        .map_err(h3_stream_error_to_io)
274}
275
276/// Writes the response HEADERS frame for `status`/`headers`, waiting for
277/// the peer's SETTINGS first.
278#[inline]
279async fn send_response(
280    stream: &SharedRequest,
281    shared: &Arc<parking_lot::Mutex<SharedCodecs>>,
282    stream_id: u64,
283    status: StatusCode,
284    headers: &http::HeaderMap,
285) -> Result<(), std::io::Error> {
286    wait_for_encoder(shared, stream_id).await;
287    let mut guard = stream.lock().await;
288    let res = std::future::poll_fn(|cx| guard.poll_send_response(cx, status, headers))
289        .await
290        .map_err(h3_stream_error_to_io);
291    res
292}
293
294/// Writes one response DATA frame.
295#[inline]
296async fn send_data(stream: &SharedRequest, data: Bytes) -> Result<(), std::io::Error> {
297    let mut guard = stream.lock().await;
298    std::future::poll_fn(|cx| guard.poll_send_data(cx, data.clone()))
299        .await
300        .map_err(h3_stream_error_to_io)
301}
302
303/// Writes the response trailers HEADERS frame.
304#[inline]
305async fn send_trailers(
306    stream: &SharedRequest,
307    trailers: &http::HeaderMap,
308) -> Result<(), std::io::Error> {
309    let mut guard = stream.lock().await;
310    std::future::poll_fn(|cx| guard.poll_send_trailers(cx, trailers))
311        .await
312        .map_err(h3_stream_error_to_io)
313}
314
315/// Finishes the response (`FIN`).
316#[inline]
317async fn send_finish(stream: &SharedRequest) -> Result<(), std::io::Error> {
318    let mut guard = stream.lock().await;
319    std::future::poll_fn(|cx| guard.poll_finish(cx))
320        .await
321        .map_err(h3_stream_error_to_io)
322}
323
324/// A request task's end is observed by the connection driver through the
325/// oneshot completion channel it holds in its `FuturesUnordered`; the
326/// sender is dropped when the task finishes.
327///
328/// Drives one accepted request stream to completion.
329#[allow(clippy::type_complexity)]
330#[allow(clippy::too_many_arguments)]
331async fn handle_request<F, Fut, ResB, ResBE, ResE>(
332    stream: SharedRequest,
333    shared: Arc<parking_lot::Mutex<SharedCodecs>>,
334    stream_id: u64,
335    request_fn: Rc<F>,
336    date_cache: DateCache,
337    send_continue_response: bool,
338    send_date_header: bool,
339    conn_state: Arc<parking_lot::Mutex<ConnResetState>>,
340) where
341    F: Fn(Request<Incoming>) -> Fut,
342    Fut: std::future::Future<Output = Result<Response<ResB>, ResE>>,
343    ResB: Body<Data = Bytes, Error = ResBE> + Unpin,
344    ResE: std::error::Error,
345    ResBE: std::error::Error,
346{
347    // Read the request.
348    let request_headers = {
349        let mut guard = stream.lock().await;
350        std::future::poll_fn(|cx| guard.poll_headers(cx)).await
351    };
352    let request = match request_headers {
353        Ok(Some(request)) => request,
354        // The stream ended without a request: nothing to respond to.
355        Ok(None) => return,
356        Err(err) => {
357            // The peer terminated the stream (RESET_STREAM or
358            // STOP_SENDING) before its request was read: a reset for a
359            // stream that never reached the handler. Bound how many of
360            // these a peer may churn through (RFC 9114 Section 10.5).
361            if err.is_stream_scoped() {
362                let mut state = conn_state.lock();
363                if let Some(code) = state.note_pending_accept_reset() {
364                    state.close_code = Some(code);
365                }
366                return;
367            }
368            // A malformed request message: abort the stream with
369            // `H3_MESSAGE_ERROR` rather than the whole connection (RFC
370            // 9114 Section 4.1.2), bounded by the local-reset budget.
371            if matches!(err, StreamError::Message) {
372                let mut guard = stream.lock().await;
373                let code = err.h3_code();
374                let _ = std::future::poll_fn(|cx| guard.poll_reset(cx, code)).await;
375                let _ = std::future::poll_fn(|cx| guard.poll_stop_sending(cx, code)).await;
376                drop(guard);
377                let mut state = conn_state.lock();
378                if let Some(code) = state.note_local_error_reset() {
379                    state.close_code = Some(code);
380                }
381                return;
382            }
383            // A connection-scoped protocol violation (a malformed frame,
384            // an invalid frame sequence, or a QPACK error): force the
385            // connection to close with the matching H3 code.
386            conn_state.lock().close_code = Some(err.h3_code());
387            return;
388        }
389    };
390
391    // 100 Continue
392    let is_100_continue = send_continue_response
393        && request
394            .headers()
395            .get(http::header::EXPECT)
396            .and_then(|v| v.to_str().ok())
397            .is_some_and(|v| v.eq_ignore_ascii_case("100-continue"));
398
399    let send_continue_body = is_100_continue.then(|| Arc::new(AtomicBool::new(false)));
400    let (request_parts, _) = request.into_parts();
401    let (request_body, upgrade) = if request_parts.method == http::Method::CONNECT {
402        (Incoming::Empty, Some(stream.clone()))
403    } else {
404        (
405            Incoming::Boxed(Box::pin(H3Body::new(
406                stream.clone(),
407                send_continue_body.clone(),
408            ))),
409            None,
410        )
411    };
412    let mut request = Request::from_parts(request_parts, request_body);
413
414    // Install early hints
415    let (early_hints, mut early_hints_rx) = EarlyHints::new_lazy();
416    request.extensions_mut().insert(early_hints);
417
418    // Install HTTP upgrade
419    let upgrade = if let Some(recv_stream) = upgrade {
420        let (upgrade_tx, upgrade_rx) = oneshot::async_channel();
421        let upgrade = Upgrade::new(upgrade_rx);
422        let upgraded = upgrade.upgraded.clone();
423        request.extensions_mut().insert(upgrade);
424        Some((upgrade_tx, upgraded, recv_stream))
425    } else {
426        None
427    };
428
429    let mut response_fut = std::pin::pin!(request_fn(request));
430    let mut early_hints_open = true;
431    let mut continue_sent = false;
432    let response_result = loop {
433        if !early_hints_open {
434            break response_fut.as_mut().await;
435        }
436
437        let next = std::future::poll_fn(|cx| {
438            if let Poll::Ready(res) = response_fut.as_mut().poll(cx) {
439                return Poll::Ready(Some(futures_util::future::Either::Left(res)));
440            }
441
442            match early_hints_rx.poll_recv(cx) {
443                Poll::Ready(Some(msg)) => {
444                    return Poll::Ready(Some(futures_util::future::Either::Right(Ok(msg))))
445                }
446                Poll::Ready(None) => {
447                    return Poll::Ready(Some(futures_util::future::Either::Right(Err(()))))
448                }
449                Poll::Pending => {}
450            }
451
452            if !continue_sent
453                && is_100_continue
454                && send_continue_body
455                    .as_ref()
456                    .is_some_and(|b| b.load(Ordering::Relaxed))
457            {
458                continue_sent = true;
459                return Poll::Ready(None);
460            }
461
462            Poll::Pending
463        })
464        .await;
465
466        match next {
467            // HTTP response
468            Some(futures_util::future::Either::Left(response_result)) => {
469                break response_result;
470            }
471            // 103 Early Hints
472            Some(futures_util::future::Either::Right(Ok((headers, sender)))) => {
473                sender
474                    .into_inner()
475                    .send(
476                        send_response(
477                            &stream,
478                            &shared,
479                            stream_id,
480                            StatusCode::EARLY_HINTS,
481                            &headers,
482                        )
483                        .await,
484                    )
485                    .ok();
486            }
487            Some(futures_util::future::Either::Right(Err(()))) => {
488                early_hints_open = false;
489            }
490            // 100 Continue
491            None => {
492                if send_interim_response(&stream, StatusCode::CONTINUE)
493                    .await
494                    .is_err()
495                {
496                    return;
497                }
498            }
499        }
500    };
501
502    let Ok(mut response) = response_result else {
503        // Return early if the request handler returns an error
504        return;
505    };
506
507    {
508        let response_headers = response.headers_mut();
509        if send_date_header {
510            if let Some(http_date) = date_cache.get_date_header_value() {
511                response_headers
512                    .entry(http::header::DATE)
513                    .or_insert(http_date);
514            }
515        }
516        remove_invalid_http3_headers(response_headers);
517    }
518
519    let response_is_end_stream = response.body().is_end_stream();
520    if !response_is_end_stream {
521        if let Some(content_length) = response.body().size_hint().exact() {
522            if !response
523                .headers()
524                .contains_key(http::header::CONTENT_LENGTH)
525            {
526                response
527                    .headers_mut()
528                    .insert(http::header::CONTENT_LENGTH, content_length.into());
529            }
530        }
531    }
532
533    if is_100_continue
534        && !continue_sent
535        && !response.status().is_client_error()
536        && !response.status().is_server_error()
537        && send_interim_response(&stream, StatusCode::CONTINUE)
538            .await
539            .is_err()
540    {
541        return;
542    }
543
544    let (response_parts, mut response_body) = response.into_parts();
545    if send_response(
546        &stream,
547        &shared,
548        stream_id,
549        response_parts.status,
550        &response_parts.headers,
551    )
552    .await
553    .is_err()
554    {
555        return;
556    }
557
558    if let Some((upgrade_tx, upgraded, recv_stream)) = upgrade {
559        if upgraded.load(Ordering::Relaxed) {
560            let (upgraded, task) = self::upgrade::pair(recv_stream);
561            let _ = upgrade_tx.send(Upgraded::new(upgraded, None));
562            task.await;
563            return;
564        }
565    }
566
567    if !response_is_end_stream {
568        while let Some(chunk) = response_body.frame().await {
569            match chunk {
570                Ok(frame) => {
571                    if frame.is_data() {
572                        match frame.into_data() {
573                            Ok(data) => {
574                                if data.is_empty() {
575                                    // Don't waste bandwidth using empty frames...
576                                    continue;
577                                }
578                                if send_data(&stream, data).await.is_err() {
579                                    return;
580                                }
581                            }
582                            Err(_) => {
583                                return;
584                            }
585                        }
586                    } else if frame.is_trailers() {
587                        match frame.into_trailers() {
588                            Ok(mut trailers) => {
589                                remove_invalid_http3_headers(&mut trailers);
590                                if send_trailers(&stream, &trailers).await.is_err() {
591                                    return;
592                                }
593                                break;
594                            }
595                            Err(_) => {
596                                return;
597                            }
598                        }
599                    }
600                }
601                Err(_) => {
602                    return;
603                }
604            }
605        }
606    }
607
608    let _ = send_finish(&stream).await;
609}
610
611/// An HTTP/3 connection handler.
612///
613/// `Http3` wraps a QUIC connection (`Io`) and drives the HTTP/3 server
614/// connection over the native transport stack. It supports:
615///
616/// - Concurrent request stream handling
617/// - Streaming request/response bodies and trailers
618/// - Automatic `100 Continue` and `103 Early Hints` interim responses
619/// - Per-connection `Date` header caching
620/// - Graceful shutdown via a [`CancellationToken`]
621///
622/// # Construction
623///
624/// ```rust,ignore
625/// let http3 = Http3::new(quic_connection, Http3Options::default());
626/// ```
627///
628/// # Serving requests
629///
630/// Use the [`HttpProtocol`] trait methods ([`handle`](HttpProtocol::handle) /
631/// [`handle_with_error_fn`](HttpProtocol::handle_with_error_fn)) to drive the
632/// connection to completion.
633pub struct Http3<Io> {
634    io_to_handshake: Option<Io>,
635    date_header_value_cached: DateCache,
636    options: Http3Options,
637    cancel_token: Option<CancellationToken>,
638}
639
640impl<Io> Http3<Io>
641where
642    Io: transport::Connection + Unpin + 'static,
643{
644    /// Creates a new `Http3` connection handler wrapping the given QUIC
645    /// connection.
646    ///
647    /// The `options` value controls HTTP/3 protocol configuration, connection
648    /// setup and accept timeouts, and optional behaviour such as automatic
649    /// `100 Continue` responses; see [`Http3Options`] for details.
650    ///
651    /// # Example
652    ///
653    /// ```rust,ignore
654    /// let http3 = Http3::new(quic_connection, Http3Options::default());
655    /// ```
656    #[inline]
657    pub fn new(io: Io, options: Http3Options) -> Self {
658        Self {
659            io_to_handshake: Some(io),
660            date_header_value_cached: DateCache::default(),
661            options,
662            cancel_token: None,
663        }
664    }
665
666    /// Attaches a [`CancellationToken`] for graceful shutdown.
667    ///
668    /// When the token is cancelled, the handler sends an HTTP/3 graceful
669    /// shutdown signal (GOAWAY), stops accepting new request streams, and
670    /// exits cleanly once the in-flight requests have drained.
671    #[inline]
672    pub fn graceful_shutdown_token(mut self, token: CancellationToken) -> Self {
673        self.cancel_token = Some(token);
674        self
675    }
676}
677
678impl<Io> HttpProtocol for Http3<Io>
679where
680    Io: transport::Connection + Unpin + 'static,
681{
682    #[allow(clippy::manual_async_fn)]
683    #[inline]
684    fn handle<F, Fut, ResB, ResBE, ResE>(
685        self,
686        request_fn: F,
687    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
688    where
689        F: Fn(Request<super::Incoming>) -> Fut + 'static,
690        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
691        ResB: http_body::Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
692        ResE: std::error::Error + 'static,
693        ResBE: std::error::Error + 'static,
694    {
695        async move {
696            let request_fn = Rc::new(request_fn);
697            let Http3 {
698                mut io_to_handshake,
699                date_header_value_cached,
700                options,
701                cancel_token,
702            } = self;
703            let mut conn = io_to_handshake
704                .take()
705                .ok_or_else(|| std::io::Error::other("no io to handshake"))?;
706            let date_cache = date_header_value_cached;
707            let send_continue_response = options.send_continue_response;
708            let send_date_header = options.send_date_header;
709
710            // The QUIC handshake may not be complete yet (0-RTT); wait for
711            // it, bounded by the handshake timeout. Server-side QUIC
712            // connections are already complete when handed over.
713            if let Some(timeout) = options.handshake_timeout {
714                vibeio::time::timeout(timeout, async {
715                    while !conn.is_handshake_complete() {
716                        vibeio::time::sleep(std::time::Duration::from_millis(1)).await;
717                    }
718                })
719                .await
720                .map_err(|_| {
721                    std::io::Error::new(std::io::ErrorKind::TimedOut, "handshake timeout")
722                })?;
723            } else {
724                while !conn.is_handshake_complete() {
725                    vibeio::time::sleep(std::time::Duration::from_millis(1)).await;
726                }
727            }
728
729            let mut controls = ControlStreams::new(options.local_settings.clone());
730            let shared = controls.shared().clone();
731            // Request-stream tasks record connection-scoped errors and reset
732            // accounting here; the driver closes the connection with the
733            // recorded H3 code (a request task cannot itself close the QUIC
734            // connection).
735            let conn_state: Arc<parking_lot::Mutex<ConnResetState>> =
736                Arc::new(parking_lot::Mutex::new(ConnResetState {
737                    limits: ResetLimits {
738                        max_local_error_resets: options.max_local_error_reset_streams,
739                        max_pending_accept_resets: options.max_pending_accept_reset_streams,
740                    },
741                    local_error_resets: 0,
742                    pending_accept_resets: 0,
743                    close_code: None,
744                }));
745            let mut ongoing: FuturesUnordered<oneshot::AsyncReceiver<()>> = FuturesUnordered::new();
746            let mut cancel_fut: Option<Pin<Box<dyn std::future::Future<Output = ()> + Send>>> =
747                None;
748            if let Some(token) = cancel_token.as_ref() {
749                cancel_fut = Some(Box::pin(token.cancelled()));
750            }
751            let mut accept_sleep: Option<Pin<Box<vibeio::time::Sleep>>> = None;
752            let mut shutdown_sleep: Option<Pin<Box<vibeio::time::Sleep>>> = None;
753            // Once graceful shutdown has drained every in-flight request we
754            // must not issue `CONNECTION_CLOSE` immediately: quinn's
755            // `close()` sends exactly one frame and then stops transmitting,
756            // discarding any response bytes still buffered in the send
757            // scheduler. A short grace window lets the background transmit
758            // flush those bytes so the peer observes the response, not the
759            // close.
760            let mut drain_grace: Option<Pin<Box<vibeio::time::Sleep>>> = None;
761            let mut shutdown = false;
762            let mut control_dead = false;
763            // When set, the connection is being torn down by a protocol error
764            // and must close with this H3 code (rather than the graceful
765            // GOAWAY + H3_NO_ERROR path).
766            let mut closing_with: Option<u64> = None;
767            let mut outcome: Option<Result<(), std::io::Error>> = None;
768            let mut last_request_id = 0u64;
769
770            // Bring up the control plane (control stream plus QPACK
771            // encoder/decoder streams) and write the initial SETTINGS.
772            std::future::poll_fn(|cx| -> Poll<Result<(), std::io::Error>> {
773                ready!(controls
774                    .poll_init(&mut conn, cx)
775                    .map_err(h3_control_error_to_io))?;
776                ready!(controls.poll_flush(cx).map_err(h3_control_error_to_io))?;
777                Poll::Ready(Ok(()))
778            })
779            .await?;
780
781            std::future::poll_fn(|cx| loop {
782                // A request-stream task hit a connection-scoped error: close
783                // the connection with the H3 code it recorded.
784                if let Some(code) = conn_state.lock().close_code.take() {
785                    if closing_with.is_none() {
786                        closing_with = Some(code);
787                        shutdown = true;
788                        control_dead = true;
789                    }
790                }
791
792                // Accept-timeout window: refreshed whenever a request
793                // stream is accepted; it bounds waiting for the next one.
794                let mut timeout_fired = false;
795                if let Some(sleep) = accept_sleep.as_mut() {
796                    if let Poll::Ready(()) = sleep.as_mut().poll(cx) {
797                        accept_sleep = None;
798                        timeout_fired = true;
799                    }
800                } else if let Some(accept_timeout) = options.accept_timeout {
801                    accept_sleep = Some(Box::pin(vibeio::time::sleep(accept_timeout)));
802                    continue;
803                }
804
805                // Shutdown backstop: while graceful shutdown is pending on
806                // in-flight requests, re-poll periodically so the close
807                // with `H3_NO_ERROR` cannot be starved by a lost wake-up.
808                if let Some(sleep) = shutdown_sleep.as_mut() {
809                    if let Poll::Ready(()) = sleep.as_mut().poll(cx) {
810                        shutdown_sleep = None;
811                    }
812                }
813
814                // Graceful shutdown trigger.
815                let mut cancel_fired = false;
816                if let Some(fut) = cancel_fut.as_mut() {
817                    if let Poll::Ready(()) = fut.as_mut().poll(cx) {
818                        cancel_fired = true;
819                    }
820                }
821                if !shutdown {
822                    if cancel_fired {
823                        shutdown = true;
824                        outcome = Some(Ok(()));
825                    } else if timeout_fired {
826                        shutdown = true;
827                        outcome = Some(Err(std::io::Error::new(
828                            std::io::ErrorKind::TimedOut,
829                            "accept timeout",
830                        )));
831                    }
832                }
833
834                // Hand the request streams' queued QPACK encoder
835                // instructions to the control plane.
836                {
837                    let mut shared = shared.lock();
838                    controls.queue_encoder_streams(&mut shared.encoder_stream);
839                }
840
841                // Write the control plane's outbound streams. If the peer tore
842                // it down while we were shutting down (e.g. an h3 0.0.8 client
843                // resets its receive side once it sees GOAWAY), we stop trying
844                // to flush — the connection is already draining — but we must
845                // NOT close yet: in-flight requests still need to be served so
846                // the peer receives their responses before the application
847                // close. `control_dead` records that state so we neither spin
848                // on a terminal error nor send a close prematurely.
849                if !control_dead {
850                    match controls.poll_flush(cx) {
851                        Poll::Ready(Ok(())) => {}
852                        Poll::Ready(Err(err)) => {
853                            if shutdown {
854                                control_dead = true;
855                            } else {
856                                closing_with = Some(err.h3_code());
857                                shutdown = true;
858                                control_dead = true;
859                            }
860                        }
861                        Poll::Pending => {}
862                    }
863                }
864
865                // Read the peer's control plane and react to its events.
866                if !control_dead {
867                    loop {
868                        match controls.poll_read(&mut conn, cx) {
869                            Poll::Ready(Ok(Some(ControlEvent::Goaway { .. }))) => {
870                                // The client is going away: stop accepting new
871                                // request streams and close once the in-flight
872                                // ones drain.
873                                if !shutdown {
874                                    shutdown = true;
875                                    outcome = Some(Ok(()));
876                                }
877                            }
878                            Poll::Ready(Ok(Some(_))) => {}
879                            Poll::Ready(Ok(None)) => {}
880                            Poll::Ready(Err(err)) => {
881                                if shutdown {
882                                    control_dead = true;
883                                    break;
884                                }
885                                closing_with = Some(err.h3_code());
886                                shutdown = true;
887                                control_dead = true;
888                                break;
889                            }
890                            Poll::Pending => break,
891                        }
892                    }
893                }
894
895                // Shutdown: either a protocol error (close immediately with
896                // the recorded H3 code) or a graceful drain (GOAWAY, then
897                // H3_NO_ERROR once every in-flight request has drained).
898                if shutdown {
899                    if let Some(code) = closing_with {
900                        ready!(conn
901                            .poll_shutdown(cx, code)
902                            .map_err(h3_transport_error_to_io))?;
903                        return Poll::Ready(outcome.take().unwrap_or(Ok(())));
904                    }
905                    if controls.goaway_sent().is_none() {
906                        controls.send_goaway(last_request_id);
907                    }
908                    if ongoing.is_empty() {
909                        // Give the send scheduler a grace window to flush
910                        // the last response before we close. See the comment
911                        // on `drain_grace` above.
912                        if let Some(grace) = drain_grace.as_mut() {
913                            if grace.as_mut().poll(cx).is_ready() {
914                                ready!(conn
915                                    .poll_shutdown(cx, H3_NO_ERROR)
916                                    .map_err(h3_transport_error_to_io))?;
917                                return Poll::Ready(outcome.take().unwrap_or(Ok(())));
918                            }
919                        } else {
920                            drain_grace = Some(Box::pin(vibeio::time::sleep(
921                                std::time::Duration::from_millis(50),
922                            )));
923                        }
924                    } else if shutdown_sleep.is_none() {
925                        shutdown_sleep = Some(Box::pin(vibeio::time::sleep(
926                            std::time::Duration::from_millis(10),
927                        )));
928                    }
929                }
930
931                // Accept request streams.
932                match conn.poll_accept(cx) {
933                    Poll::Ready(Ok(Some(stream))) => {
934                        let id = stream.id();
935                        last_request_id = last_request_id.max(id);
936                        accept_sleep = None;
937                        if shutdown
938                            && (controls.goaway_sent().is_none()
939                                || id > controls.goaway_sent().unwrap_or(u64::MAX))
940                        {
941                            // A request after our GOAWAY: reject it
942                            // (RFC 9114 Section 5.2).
943                            let mut rejected = RequestStream::new(stream, shared.clone());
944                            let _ = rejected.poll_reset(cx, H3_REQUEST_REJECTED);
945                        } else {
946                            let (end_tx, end_rx) = oneshot::async_channel();
947                            ongoing.push(end_rx);
948                            let request_stream = Arc::new(tokio::sync::Mutex::new(
949                                RequestStream::new(stream, shared.clone()),
950                            ));
951                            let request_fn = request_fn.clone();
952                            let date_cache = date_cache.clone();
953                            let shared = shared.clone();
954                            let conn_state_for_task = conn_state.clone();
955                            vibeio::spawn(async move {
956                                let _end = end_tx;
957                                handle_request(
958                                    request_stream,
959                                    shared.clone(),
960                                    id,
961                                    request_fn,
962                                    date_cache,
963                                    send_continue_response,
964                                    send_date_header,
965                                    conn_state_for_task,
966                                )
967                                .await;
968                            });
969                        }
970                    }
971                    // The connection closed: nothing left to do.
972                    Poll::Ready(Ok(None)) => {
973                        return Poll::Ready(Ok(()));
974                    }
975                    Poll::Ready(Err(err)) => {
976                        return Poll::Ready(Err(h3_transport_error_to_io(err)));
977                    }
978                    Poll::Pending => {}
979                }
980
981                // Collect finished request tasks. Their completion
982                // receivers were registered on first poll, so parking
983                // below wakes whenever one finishes; a completion can
984                // race the registration, so re-check before parking. An
985                // empty set yields `None` from `poll_next` (there are no
986                // completions to observe).
987                match ongoing.poll_next_unpin(cx) {
988                    Poll::Ready(Some(Ok(()))) => continue,
989                    Poll::Ready(Some(Err(_))) => continue,
990                    Poll::Ready(None) => {}
991                    Poll::Pending => {}
992                }
993                if ongoing.is_empty() && shutdown {
994                    continue;
995                }
996
997                return Poll::Pending;
998            })
999            .await
1000        }
1001    }
1002}