feat: add tcpBacklog arg to Deno.listen{Tls} and Deno.serve (#30541)

This commit adds `tcpBacklog` argument to `Deno.listen`,
`Deno.listenTls` and `Deno.serve` APIs.

The argument specifies maximum number of pending connections in the
listen queue, and by default is set to 511. 

Users that expect huge bursts of traffic can customize this
option to a higher value.

Ref https://github.com/denoland/deno/pull/30471
Closes https://github.com/denoland/deno/issues/30388
This commit is contained in:
Bartek Iwańczuk 2025-08-28 09:43:20 +02:00 committed by GitHub
parent 9dbcd025d6
commit da1bf978f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 49 additions and 15 deletions

View file

@ -5157,6 +5157,18 @@ declare namespace Deno {
/** Sets `SO_REUSEPORT` on POSIX systems. */
reusePort?: boolean;
/** Maximum number of pending connections in the listen queue.
*
* This parameter controls how many incoming connections can be queued by the
* operating system while waiting for the application to accept them. If more
* connections arrive when the queue is full, they will be refused.
*
* The kernel may adjust this value (e.g., rounding up to the next power of 2
* plus 1). Different operating systems have different maximum limits.
*
* @default {511} */
tcpBacklog?: number;
}
/**

View file

@ -191,6 +191,18 @@ declare namespace Deno {
*
* @default {"0.0.0.0"} */
hostname?: string;
/** Maximum number of pending connections in the listen queue.
*
* This parameter controls how many incoming connections can be queued by the
* operating system while waiting for the application to accept them. If more
* connections arrive when the queue is full, they will be refused.
*
* The kernel may adjust this value (e.g., rounding up to the next power of 2
* plus 1). Different operating systems have different maximum limits.
*
* @default {511} */
tcpBacklog?: number;
}
/** @category Network */

View file

@ -936,6 +936,7 @@ function serveInner(options, handler) {
port: options.port ?? 8000,
reusePort: options.reusePort ?? false,
loadBalanced: options[kLoadBalanced] ?? false,
backlog: options.backlog,
};
if (options.certFile || options.keyFile) {

View file

@ -598,6 +598,7 @@ function listen(args) {
},
args.reusePort,
args.loadBalanced ?? false,
args.tcpBacklog ?? 511,
);
addr.transport = "tcp";
return new Listener(rid, addr, "tcp");

View file

@ -162,6 +162,7 @@ function listenTls({
transport = "tcp",
alpnProtocols = undefined,
reusePort = false,
tcpBacklog = 511,
}) {
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
@ -176,7 +177,7 @@ function listenTls({
const keyPair = loadTlsKeyPair("Deno.listenTls", arguments[0]);
const { 0: rid, 1: localAddr } = op_net_listen_tls(
{ hostname, port },
{ alpnProtocols, reusePort },
{ alpnProtocols, reusePort, tcpBacklog },
keyPair,
);
return new TlsListener(rid, localAddr);

View file

@ -574,6 +574,7 @@ pub fn op_net_listen_tcp<NP>(
#[serde] addr: IpAddr,
reuse_port: bool,
load_balanced: bool,
tcp_backlog: i32,
) -> Result<(ResourceId, IpAddr), NetError>
where
NP: NetPermissions + 'static,
@ -589,9 +590,9 @@ where
.ok_or_else(|| NetError::NoResolvedAddress)?;
let listener = if load_balanced {
TcpListener::bind_load_balanced(addr)
TcpListener::bind_load_balanced(addr, tcp_backlog)
} else {
TcpListener::bind_direct(addr, reuse_port)
TcpListener::bind_direct(addr, reuse_port, tcp_backlog)
}?;
let local_addr = listener.local_addr()?;
let listener_resource = NetworkListenerResource::new(listener);
@ -1483,7 +1484,7 @@ mod tests {
let sockets = Arc::new(Mutex::new(vec![]));
let clone_addr = addr.clone();
let addr = addr.to_socket_addrs().unwrap().next().unwrap();
let listener = TcpListener::bind_direct(addr, false).unwrap();
let listener = TcpListener::bind_direct(addr, false, 511).unwrap();
let accept_fut = listener.accept().boxed_local();
let store_fut = async move {
let socket = accept_fut.await.unwrap();

View file

@ -514,6 +514,7 @@ pub struct ListenTlsArgs {
reuse_port: bool,
#[serde(default)]
load_balanced: bool,
tcp_backlog: i32,
}
#[op2(stack_trace)]
@ -543,9 +544,9 @@ where
.ok_or(NetError::NoResolvedAddress)?;
let tcp_listener = if args.load_balanced {
TcpListener::bind_load_balanced(bind_addr)
TcpListener::bind_load_balanced(bind_addr, args.tcp_backlog)
} else {
TcpListener::bind_direct(bind_addr, args.reuse_port)
TcpListener::bind_direct(bind_addr, args.reuse_port, args.tcp_backlog)
}?;
let local_addr = tcp_listener.local_addr()?;
let alpn = args

View file

@ -31,8 +31,8 @@ pub struct TcpConnection {
impl TcpConnection {
/// Boot a load-balanced TCP connection
pub fn start(key: SocketAddr) -> std::io::Result<Self> {
let listener = bind_socket_and_listen(key, false)?;
pub fn start(key: SocketAddr, backlog: i32) -> std::io::Result<Self> {
let listener = bind_socket_and_listen(key, false, backlog)?;
let sock = listener.into();
Ok(Self { sock, key })
@ -78,11 +78,12 @@ impl TcpListener {
pub fn bind(
socket_addr: SocketAddr,
reuse_port: bool,
backlog: i32,
) -> std::io::Result<Self> {
if REUSE_PORT_LOAD_BALANCES && reuse_port {
Self::bind_load_balanced(socket_addr)
Self::bind_load_balanced(socket_addr, backlog)
} else {
Self::bind_direct(socket_addr, reuse_port)
Self::bind_direct(socket_addr, reuse_port, backlog)
}
}
@ -91,9 +92,10 @@ impl TcpListener {
pub fn bind_direct(
socket_addr: SocketAddr,
reuse_port: bool,
backlog: i32,
) -> std::io::Result<Self> {
// We ignore `reuse_port` on platforms other than Linux to match the existing behaviour.
let listener = bind_socket_and_listen(socket_addr, reuse_port)?;
let listener = bind_socket_and_listen(socket_addr, reuse_port, backlog)?;
Ok(Self {
listener: Some(tokio::net::TcpListener::from_std(listener)?),
conn: None,
@ -101,7 +103,10 @@ impl TcpListener {
}
/// Bind to the port in a load-balanced manner.
pub fn bind_load_balanced(socket_addr: SocketAddr) -> std::io::Result<Self> {
pub fn bind_load_balanced(
socket_addr: SocketAddr,
backlog: i32,
) -> std::io::Result<Self> {
let tcp = &mut CONNS.get_or_init(Default::default).lock().unwrap().tcp;
if let Some(conn) = tcp.get(&socket_addr) {
let listener = Some(conn.listener()?);
@ -110,7 +115,7 @@ impl TcpListener {
conn: Some(conn.clone()),
});
}
let conn = Arc::new(TcpConnection::start(socket_addr)?);
let conn = Arc::new(TcpConnection::start(socket_addr, backlog)?);
let listener = Some(conn.listener()?);
tcp.insert(socket_addr, conn.clone());
Ok(Self {
@ -151,6 +156,7 @@ impl Drop for TcpListener {
fn bind_socket_and_listen(
socket_addr: SocketAddr,
reuse_port: bool,
backlog: i32,
) -> Result<std::net::TcpListener, std::io::Error> {
let socket = if socket_addr.is_ipv4() {
socket2::Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?
@ -170,8 +176,7 @@ fn bind_socket_and_listen(
socket.set_reuse_address(true)?;
socket.set_nonblocking(true)?;
socket.bind(&socket_addr.into())?;
// Kernel will round it up to the next power of 2 + 1.
socket.listen(511)?;
socket.listen(backlog)?;
let listener = socket.into();
Ok(listener)
}