Skip to main content

sui_http/
connection_handler.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::future::Future;
5use std::pin::Pin;
6use std::pin::pin;
7use std::time::Duration;
8
9use http::Request;
10use http::Response;
11use hyper_util::rt::TokioExecutor;
12use hyper_util::server::conn::auto;
13use tracing::debug;
14use tracing::trace;
15
16use crate::ActiveConnections;
17use crate::BoxError;
18use crate::ConnectionId;
19use crate::fuse::Fuse;
20
21// This is moved to its own function as a way to get around
22// https://github.com/rust-lang/rust/issues/102211
23pub async fn serve_connection<IO, S, B, C>(
24    hyper_io: IO,
25    hyper_svc: S,
26    builder: auto::Builder<TokioExecutor>,
27    graceful_shutdown_token: tokio_util::sync::CancellationToken,
28    max_connection_age: Option<Duration>,
29    max_connection_age_grace: Option<Duration>,
30    on_connection_close: C,
31) where
32    B: http_body::Body + Send + 'static,
33    B::Data: Send,
34    B::Error: Into<BoxError>,
35    IO: hyper::rt::Read + hyper::rt::Write + Send + Unpin + 'static,
36    S: hyper::service::Service<Request<hyper::body::Incoming>, Response = Response<B>> + 'static,
37    S::Future: Send + 'static,
38    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
39{
40    // `serve_connection_with_upgrades` always sniffs the protocol from the
41    // first bytes and silently ignores `http2_only`, so an HTTP/2-only
42    // builder must use `serve_connection`, where the pinned version is
43    // honored, the sniff is skipped, and anything that is not an HTTP/2
44    // preface is rejected. The upgrades variant differs only in its HTTP/1
45    // arm (hyper's `with_upgrades` wrapper); HTTP/2 extended CONNECT
46    // behaves identically on both paths.
47    if builder.is_http1_available() {
48        let conn = pin!(builder.serve_connection_with_upgrades(hyper_io, hyper_svc));
49        drive_connection(
50            conn,
51            graceful_shutdown_token,
52            max_connection_age,
53            max_connection_age_grace,
54        )
55        .await;
56    } else {
57        let conn = pin!(builder.serve_connection(hyper_io, hyper_svc));
58        drive_connection(
59            conn,
60            graceful_shutdown_token,
61            max_connection_age,
62            max_connection_age_grace,
63        )
64        .await;
65    }
66
67    trace!("connection closed");
68    drop(on_connection_close);
69}
70
71/// The connection future types produced by hyper-util's auto builder,
72/// unified so [`drive_connection`] can drive either.
73trait GracefulConnection: Future<Output = Result<(), BoxError>> {
74    fn graceful_shutdown(self: Pin<&mut Self>);
75}
76
77impl<IO, S, B> GracefulConnection for auto::Connection<'_, IO, S, TokioExecutor>
78where
79    B: http_body::Body + Send + 'static,
80    B::Data: Send,
81    B::Error: Into<BoxError>,
82    IO: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
83    S: hyper::service::Service<Request<hyper::body::Incoming>, Response = Response<B>>,
84    S::Future: Send + 'static,
85    S::Error: Into<BoxError>,
86{
87    fn graceful_shutdown(self: Pin<&mut Self>) {
88        auto::Connection::graceful_shutdown(self)
89    }
90}
91
92impl<IO, S, B> GracefulConnection for auto::UpgradeableConnection<'_, IO, S, TokioExecutor>
93where
94    B: http_body::Body + Send + 'static,
95    B::Data: Send,
96    B::Error: Into<BoxError>,
97    IO: hyper::rt::Read + hyper::rt::Write + Send + Unpin + 'static,
98    S: hyper::service::Service<Request<hyper::body::Incoming>, Response = Response<B>>,
99    S::Future: Send + 'static,
100    S::Error: Into<BoxError>,
101{
102    fn graceful_shutdown(self: Pin<&mut Self>) {
103        auto::UpgradeableConnection::graceful_shutdown(self)
104    }
105}
106
107async fn drive_connection<C>(
108    mut conn: Pin<&mut C>,
109    graceful_shutdown_token: tokio_util::sync::CancellationToken,
110    max_connection_age: Option<Duration>,
111    max_connection_age_grace: Option<Duration>,
112) where
113    C: GracefulConnection,
114{
115    let mut sig = pin!(Fuse::new(graceful_shutdown_token.cancelled_owned()));
116
117    let sleep = sleep_or_pending(max_connection_age);
118    tokio::pin!(sleep);
119    let mut in_grace_period = false;
120
121    loop {
122        tokio::select! {
123            _ = &mut sig => {
124                conn.as_mut().graceful_shutdown();
125                // Bound the drain the same way an age-triggered shutdown
126                // is bounded, so a wedged stream cannot keep the
127                // connection alive past the grace period. If the age
128                // timer already started the grace period, keep its
129                // earlier deadline.
130                if !in_grace_period {
131                    in_grace_period = true;
132                    sleep.set(sleep_or_pending(max_connection_age_grace));
133                }
134            }
135            rv = &mut conn => {
136                if let Err(err) = rv {
137                    debug!("failed serving connection: {:#}", err);
138                }
139                break;
140            },
141            _ = &mut sleep  => {
142                if in_grace_period {
143                    // The grace period expired with streams still in
144                    // flight. A stream wedged on flow control (e.g. a
145                    // stalled peer that never reopens its receive window)
146                    // can never complete a graceful shutdown, so dropping
147                    // the connection is the only way to reclaim it.
148                    debug!("max connection age grace period expired, closing connection");
149                    break;
150                }
151                conn.as_mut().graceful_shutdown();
152                in_grace_period = true;
153                sleep.set(sleep_or_pending(max_connection_age_grace));
154            },
155        }
156    }
157}
158
159async fn sleep_or_pending(wait_for: Option<Duration>) {
160    match wait_for {
161        Some(wait) => tokio::time::sleep(wait).await,
162        None => std::future::pending().await,
163    };
164}
165
166pub(crate) struct OnConnectionClose<A> {
167    id: ConnectionId,
168    active_connections: ActiveConnections<A>,
169}
170
171impl<A> OnConnectionClose<A> {
172    pub(crate) fn new(id: ConnectionId, active_connections: ActiveConnections<A>) -> Self {
173        Self {
174            id,
175            active_connections,
176        }
177    }
178}
179
180impl<A> Drop for OnConnectionClose<A> {
181    fn drop(&mut self) {
182        self.active_connections.write().unwrap().remove(&self.id);
183    }
184}