compio_pool/listener.rs
1//! A `SO_REUSEPORT` listener helper, for building your own thread-per-core
2//! accept loop around a [`Pool`](crate::Pool).
3
4use std::{io, net::SocketAddr};
5
6use socket2::{Domain, Protocol, Socket, Type};
7
8/// Create, configure, bind and listen a raw TCP socket that shares its address
9/// with the other threads' listeners.
10///
11/// `SO_REUSEADDR` and `SO_REUSEPORT` are set **before** `bind`, which is what
12/// lets every thread bind the same address and makes the kernel split incoming
13/// connections across the group by flow hash. With `incoming_cpu`, the socket
14/// also carries `SO_INCOMING_CPU = cpu`, so when packet steering delivers a flow
15/// on that CPU the kernel prefers this listener for it.
16///
17/// The result is a plain [`std::net::TcpListener`]: wrap it in the thread's
18/// runtime with [`compio::net::TcpListener::from_std`], which attaches it to that
19/// thread's ring.
20pub fn bind_reuseport(
21 addr: SocketAddr,
22 backlog: i32,
23 incoming_cpu: Option<usize>,
24) -> io::Result<std::net::TcpListener> {
25 let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?;
26 socket.set_reuse_address(true)?;
27 socket.set_reuse_port(true)?;
28 if let Some(cpu) = incoming_cpu {
29 set_incoming_cpu(&socket, cpu);
30 }
31 socket.bind(&addr.into())?;
32 socket.listen(backlog)?;
33 Ok(socket.into())
34}
35
36/// `SO_INCOMING_CPU` is a hint, and an old kernel or a non-Linux build has no
37/// way to honour it, so a failure here is not a reason to refuse to start.
38#[cfg(target_os = "linux")]
39fn set_incoming_cpu(socket: &Socket, cpu: usize) {
40 let _ = socket.set_cpu_affinity(cpu);
41}
42
43#[cfg(not(target_os = "linux"))]
44fn set_incoming_cpu(_socket: &Socket, _cpu: usize) {}