From da1bf978f03b3ec64ca6989d25bd4ad4f0f2308c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 28 Aug 2025 09:43:20 +0200 Subject: [PATCH] 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 --- cli/tsc/dts/lib.deno.ns.d.ts | 12 ++++++++++++ cli/tsc/dts/lib.deno_net.d.ts | 12 ++++++++++++ ext/http/00_serve.ts | 1 + ext/net/01_net.js | 1 + ext/net/02_tls.js | 3 ++- ext/net/ops.rs | 7 ++++--- ext/net/ops_tls.rs | 5 +++-- ext/net/tcp.rs | 23 ++++++++++++++--------- 8 files changed, 49 insertions(+), 15 deletions(-) diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 744a98b01a..569ba723d7 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -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; } /** diff --git a/cli/tsc/dts/lib.deno_net.d.ts b/cli/tsc/dts/lib.deno_net.d.ts index 738dbb0704..b078acc315 100644 --- a/cli/tsc/dts/lib.deno_net.d.ts +++ b/cli/tsc/dts/lib.deno_net.d.ts @@ -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 */ diff --git a/ext/http/00_serve.ts b/ext/http/00_serve.ts index 7121996986..07141f324c 100644 --- a/ext/http/00_serve.ts +++ b/ext/http/00_serve.ts @@ -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) { diff --git a/ext/net/01_net.js b/ext/net/01_net.js index a01112d3c8..f99598b7bb 100644 --- a/ext/net/01_net.js +++ b/ext/net/01_net.js @@ -598,6 +598,7 @@ function listen(args) { }, args.reusePort, args.loadBalanced ?? false, + args.tcpBacklog ?? 511, ); addr.transport = "tcp"; return new Listener(rid, addr, "tcp"); diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js index 662c6600ca..0fe34b1ec5 100644 --- a/ext/net/02_tls.js +++ b/ext/net/02_tls.js @@ -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); diff --git a/ext/net/ops.rs b/ext/net/ops.rs index 41dbccea0f..1b2280cad4 100644 --- a/ext/net/ops.rs +++ b/ext/net/ops.rs @@ -574,6 +574,7 @@ pub fn op_net_listen_tcp( #[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(); diff --git a/ext/net/ops_tls.rs b/ext/net/ops_tls.rs index a4c3fe6676..cc1bab470b 100644 --- a/ext/net/ops_tls.rs +++ b/ext/net/ops_tls.rs @@ -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 diff --git a/ext/net/tcp.rs b/ext/net/tcp.rs index 7912ab4645..bcd9cbffcb 100644 --- a/ext/net/tcp.rs +++ b/ext/net/tcp.rs @@ -31,8 +31,8 @@ pub struct TcpConnection { impl TcpConnection { /// Boot a load-balanced TCP connection - pub fn start(key: SocketAddr) -> std::io::Result { - let listener = bind_socket_and_listen(key, false)?; + pub fn start(key: SocketAddr, backlog: i32) -> std::io::Result { + 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 { 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 { // 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 { + pub fn bind_load_balanced( + socket_addr: SocketAddr, + backlog: i32, + ) -> std::io::Result { 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 { 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) }