Skip to main content

sui_http/
listener.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::time::Duration;
5
6/// Types that can listen for connections.
7pub trait Listener: Send + 'static {
8    /// The listener's IO type.
9    type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static;
10
11    /// The listener's address type.
12    // all these bounds are necessary to add this information in a request extension
13    type Addr: Clone + Send + Sync + 'static;
14
15    /// Accept a new incoming connection to this listener.
16    ///
17    /// If the underlying accept call can return an error, this function must
18    /// take care of logging and retrying.
19    fn accept(&mut self) -> impl std::future::Future<Output = (Self::Io, Self::Addr)> + Send;
20
21    /// Returns the local address that this listener is bound to.
22    fn local_addr(&self) -> std::io::Result<Self::Addr>;
23}
24
25/// Extensions to [`Listener`].
26pub trait ListenerExt: Listener + Sized {
27    /// Run a mutable closure on every accepted `Io`.
28    ///
29    /// # Example
30    ///
31    /// ```
32    /// use sui_http::ListenerExt;
33    /// use tracing::trace;
34    ///
35    /// # async {
36    /// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
37    ///     .await
38    ///     .unwrap()
39    ///     .tap_io(|tcp_stream| {
40    ///         if let Err(err) = tcp_stream.set_nodelay(true) {
41    ///             trace!("failed to set TCP_NODELAY on incoming connection: {err:#}");
42    ///         }
43    ///     });
44    /// # };
45    /// ```
46    fn tap_io<F>(self, tap_fn: F) -> TapIo<Self, F>
47    where
48        F: FnMut(&mut Self::Io) + Send + 'static,
49    {
50        TapIo {
51            listener: self,
52            tap_fn,
53        }
54    }
55}
56
57impl<L: Listener> ListenerExt for L {}
58
59impl Listener for tokio::net::TcpListener {
60    type Io = tokio::net::TcpStream;
61    type Addr = std::net::SocketAddr;
62
63    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
64        let mut backoff = AcceptBackoff::new();
65        loop {
66            match Self::accept(self).await {
67                Ok(tup) => return tup,
68                Err(e) => backoff.handle_accept_error(e).await,
69            }
70        }
71    }
72
73    #[inline]
74    fn local_addr(&self) -> std::io::Result<Self::Addr> {
75        Self::local_addr(self)
76    }
77}
78
79#[derive(Debug)]
80pub struct TcpListenerWithOptions {
81    inner: tokio::net::TcpListener,
82    nodelay: bool,
83    keepalive: Option<Duration>,
84}
85
86impl TcpListenerWithOptions {
87    pub fn new<A: std::net::ToSocketAddrs>(
88        addr: A,
89        nodelay: bool,
90        keepalive: Option<Duration>,
91    ) -> Result<Self, crate::BoxError> {
92        let mut last_error = None;
93        for addr in addr.to_socket_addrs()? {
94            match Self::bind(addr) {
95                Ok(listener) => return Ok(Self::from_listener(listener, nodelay, keepalive)),
96                Err(e) => last_error = Some(e),
97            }
98        }
99
100        Err(last_error
101            .unwrap_or_else(|| {
102                std::io::Error::new(
103                    std::io::ErrorKind::InvalidInput,
104                    "could not resolve to any address",
105                )
106            })
107            .into())
108    }
109
110    fn bind(addr: std::net::SocketAddr) -> std::io::Result<tokio::net::TcpListener> {
111        let socket = socket2::Socket::new(
112            socket2::Domain::for_address(addr),
113            socket2::Type::STREAM,
114            Some(socket2::Protocol::TCP),
115        )?;
116
117        // Match tokio's `TcpListener::bind`: SO_REUSEADDR allows rebinding
118        // the port immediately after a restart, while connections from the
119        // previous process linger in TIME_WAIT. Not set on Windows, where
120        // the flag instead allows stealing a port that is actively bound.
121        #[cfg(not(windows))]
122        socket.set_reuse_address(true)?;
123        socket.set_nonblocking(true)?;
124        socket.bind(&addr.into())?;
125        socket.listen(1024)?;
126
127        tokio::net::TcpListener::from_std(socket.into())
128    }
129
130    /// Creates a new `TcpIncoming` from an existing `tokio::net::TcpListener`.
131    pub fn from_listener(
132        listener: tokio::net::TcpListener,
133        nodelay: bool,
134        keepalive: Option<Duration>,
135    ) -> Self {
136        Self {
137            inner: listener,
138            nodelay,
139            keepalive,
140        }
141    }
142
143    // Consistent with hyper-0.14, this function does not return an error.
144    fn set_accepted_socket_options(&self, stream: &tokio::net::TcpStream) {
145        if self.nodelay
146            && let Err(e) = stream.set_nodelay(true)
147        {
148            tracing::warn!("error trying to set TCP nodelay: {}", e);
149        }
150
151        if let Some(timeout) = self.keepalive {
152            let sock_ref = socket2::SockRef::from(&stream);
153            let sock_keepalive = socket2::TcpKeepalive::new().with_time(timeout);
154
155            if let Err(e) = sock_ref.set_tcp_keepalive(&sock_keepalive) {
156                tracing::warn!("error trying to set TCP keepalive: {}", e);
157            }
158        }
159    }
160}
161
162impl Listener for TcpListenerWithOptions {
163    type Io = tokio::net::TcpStream;
164    type Addr = std::net::SocketAddr;
165
166    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
167        let (io, addr) = Listener::accept(&mut self.inner).await;
168        self.set_accepted_socket_options(&io);
169        (io, addr)
170    }
171
172    #[inline]
173    fn local_addr(&self) -> std::io::Result<Self::Addr> {
174        Listener::local_addr(&self.inner)
175    }
176}
177
178// Uncomment once we update tokio to >=1.41.0
179// #[cfg(unix)]
180// impl Listener for tokio::net::UnixListener {
181//     type Io = tokio::net::UnixStream;
182//     type Addr = std::os::unix::net::SocketAddr;
183
184//     async fn accept(&mut self) -> (Self::Io, Self::Addr) {
185//         loop {
186//             match Self::accept(self).await {
187//                 Ok((io, addr)) => return (io, addr.into()),
188//                 Err(e) => handle_accept_error(e).await,
189//             }
190//         }
191//     }
192
193//     #[inline]
194//     fn local_addr(&self) -> std::io::Result<Self::Addr> {
195//         Self::local_addr(self).map(Into::into)
196//     }
197// }
198
199/// Return type of [`ListenerExt::tap_io`].
200///
201/// See that method for details.
202pub struct TapIo<L, F> {
203    listener: L,
204    tap_fn: F,
205}
206
207impl<L, F> std::fmt::Debug for TapIo<L, F>
208where
209    L: Listener + std::fmt::Debug,
210{
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        f.debug_struct("TapIo")
213            .field("listener", &self.listener)
214            .finish_non_exhaustive()
215    }
216}
217
218impl<L, F> Listener for TapIo<L, F>
219where
220    L: Listener,
221    F: FnMut(&mut L::Io) + Send + 'static,
222{
223    type Io = L::Io;
224    type Addr = L::Addr;
225
226    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
227        let (mut io, addr) = self.listener.accept().await;
228        (self.tap_fn)(&mut io);
229        (io, addr)
230    }
231
232    fn local_addr(&self) -> std::io::Result<Self::Addr> {
233        self.listener.local_addr()
234    }
235}
236
237/// Exponential backoff for recoverable `accept()` errors.
238///
239/// Certain errors (notably `EMFILE`/`ENFILE`, when the process has exhausted its
240/// file descriptor limit) leave the listener in a persistently-readable state,
241/// causing `accept()` to return immediately on retry. Without backoff the serve
242/// loop would spin a CPU core and flood logs.
243///
244/// A fixed 1 second sleep (as in hyper 0.14 and still in axum today) avoids the
245/// spin but delays recovery once descriptors free up. Instead we follow Go's
246/// `net/http` and HashiCorp Vault: start at 5ms and double on each consecutive
247/// error, capped at 1 second. Reset-on-success is implicit because a fresh
248/// `AcceptBackoff` is constructed per call to `accept()`.
249struct AcceptBackoff {
250    next_delay: Duration,
251}
252
253impl AcceptBackoff {
254    const MIN: Duration = Duration::from_millis(5);
255    const MAX: Duration = Duration::from_secs(1);
256
257    fn new() -> Self {
258        Self {
259            next_delay: Self::MIN,
260        }
261    }
262
263    async fn handle_accept_error(&mut self, e: std::io::Error) {
264        if is_connection_error(&e) {
265            return;
266        }
267
268        tracing::error!(backoff = ?self.next_delay, "accept error: {e}");
269        tokio::time::sleep(self.next_delay).await;
270        self.next_delay = (self.next_delay * 2).min(Self::MAX);
271    }
272}
273
274fn is_connection_error(e: &std::io::Error) -> bool {
275    use std::io::ErrorKind;
276
277    matches!(
278        e.kind(),
279        ErrorKind::ConnectionRefused
280            | ErrorKind::ConnectionAborted
281            | ErrorKind::ConnectionReset
282            | ErrorKind::BrokenPipe
283            | ErrorKind::Interrupted
284            | ErrorKind::WouldBlock
285            | ErrorKind::TimedOut
286    )
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    /// Whether an immediate rebind after shutdown succeeds while old
294    /// connections sit in TIME_WAIT is platform dependent, so the
295    /// end-to-end scenario cannot deterministically catch a missing
296    /// SO_REUSEADDR everywhere. Assert the socket option directly.
297    #[cfg(not(windows))]
298    #[tokio::test]
299    async fn listener_sets_reuse_address() {
300        let listener = TcpListenerWithOptions::new(("localhost", 0), true, None).unwrap();
301        let sock_ref = socket2::SockRef::from(&listener.inner);
302        assert!(sock_ref.reuse_address().unwrap());
303    }
304}