kumo_server_common/listener.rs
1//! Binding of TCP listeners with an operator-controllable accept queue.
2//!
3//! The async runtime builds listeners through mio, which hardcodes the
4//! `listen(2)` backlog to 128 with no way to override it. That queue has been
5//! observed to overflow under a connection burst well before CPU saturates,
6//! dropping connections the kernel could otherwise have queued. This module
7//! binds the socket directly so the backlog can be set, defaulting to the
8//! kernel's ceiling.
9
10use anyhow::Context;
11use socket2::{Domain, Protocol, Socket, Type};
12use std::net::{TcpListener, ToSocketAddrs};
13
14/// Returns the kernel's maximum accept-queue length, which is the largest
15/// backlog `listen(2)` will honor. A larger request is silently clamped down
16/// to this value. On Linux this is `net.core.somaxconn`. Elsewhere, and when
17/// it cannot be read, returns a conservative default that the kernel will
18/// clamp down further if needed.
19pub fn kernel_max_backlog() -> u32 {
20 #[cfg(target_os = "linux")]
21 {
22 if let Ok(contents) = std::fs::read_to_string("/proc/sys/net/core/somaxconn") {
23 if let Ok(value) = contents.trim().parse::<u32>() {
24 return value;
25 }
26 }
27 }
28 1024
29}
30
31/// Binds a listening TCP socket at `listen`, requesting an accept queue of
32/// `backlog` entries, or the kernel maximum when `backlog` is `None`. The
33/// returned listener is in non-blocking mode, ready to hand to an async
34/// runtime.
35///
36/// A `backlog` larger than the kernel's ceiling is accepted here but silently
37/// clamped by `listen(2)`.
38pub fn bind_with_backlog(listen: &str, backlog: Option<u32>) -> anyhow::Result<TcpListener> {
39 let backlog = backlog.unwrap_or_else(kernel_max_backlog);
40 let backlog = backlog.min(i32::MAX as u32) as i32;
41
42 let addrs = listen
43 .to_socket_addrs()
44 .with_context(|| format!("resolving {listen}"))?;
45
46 let mut last_err = None;
47 for addr in addrs {
48 match bind_one(addr, backlog) {
49 Ok(listener) => return Ok(listener),
50 Err(err) => last_err = Some(err),
51 }
52 }
53
54 Err(last_err.unwrap_or_else(|| anyhow::anyhow!("{listen} did not resolve to any address")))
55}
56
57fn bind_one(addr: std::net::SocketAddr, backlog: i32) -> anyhow::Result<TcpListener> {
58 let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))
59 .context("creating socket")?;
60 // Match the runtime's listeners, which set SO_REUSEADDR: without it, a
61 // restart cannot rebind the address until the previous socket's TIME_WAIT
62 // state expires.
63 socket
64 .set_reuse_address(true)
65 .context("setting SO_REUSEADDR")?;
66 socket
67 .bind(&addr.into())
68 .with_context(|| format!("binding to {addr}"))?;
69 socket
70 .listen(backlog)
71 .with_context(|| format!("listening on {addr}"))?;
72 socket
73 .set_nonblocking(true)
74 .context("setting nonblocking")?;
75 Ok(socket.into())
76}