Skip to content

Commit 1a1bb54

Browse files
opt(s2n-quic-dc): Further tweak TCP acceptor concurrency (#2829)
1 parent 23ab59e commit 1a1bb54

File tree

1 file changed

+22
-11
lines changed
  • dc/s2n-quic-dc/src/stream/server

1 file changed

+22
-11
lines changed

dc/s2n-quic-dc/src/stream/server/tokio.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ use std::{io, net::SocketAddr, time::Duration};
2222
use tokio::io::unix::AsyncFd;
2323
use tracing::{trace, Instrument as _};
2424

25+
// The kernel contends on fdtable lock when calling accept to locate free file
26+
// descriptors, so even if we don't contend on the underlying socket (due to
27+
// REUSEPORT) it still ends up expensive to have large amounts of threads trying to
28+
// accept() within a single process. Clamp concurrency to at most 4 threads
29+
// executing the TCP acceptor tasks accordingly.
30+
//
31+
// With UDP there's ~no lock contention for receiving packets on separate UDP sockets,
32+
// so we don't clamp concurrency in that case.
33+
const MAX_TCP_WORKERS: usize = 4;
34+
2535
pub mod tcp;
2636
pub mod udp;
2737

@@ -300,7 +310,11 @@ impl Builder {
300310
};
301311

302312
// split the backlog between all of the workers
303-
let backlog = backlog.div_ceil(concurrency).max(1);
313+
//
314+
// this is only used in TCP, so clamp division to maximum TCP worker concurrency
315+
let backlog = backlog
316+
.div_ceil(concurrency.clamp(0, MAX_TCP_WORKERS))
317+
.max(1);
304318

305319
Start {
306320
enable_tcp: self.enable_tcp,
@@ -414,15 +428,7 @@ impl<H: Handshake + Clone, S: event::Subscriber + Clone> Start<'_, H, S> {
414428
self.spawn_udp(socket)?;
415429
}
416430
socket::Protocol::Tcp => {
417-
// The kernel contends on fdtable lock when calling accept to locate free file
418-
// descriptors, so even if we don't contend on the underlying socket (due to
419-
// REUSEPORT) it still ends up expensive to have large amounts of threads trying to
420-
// accept() within a single process. Clamp concurrency to at most 4 threads
421-
// executing the TCP acceptor tasks accordingly.
422-
//
423-
// With UDP there's ~no lock contention for receiving packets on separate UDP sockets,
424-
// so we don't clamp concurrency in that case.
425-
if idx + already_running >= 4 {
431+
if idx + already_running >= MAX_TCP_WORKERS {
426432
continue;
427433
}
428434

@@ -443,7 +449,12 @@ impl<H: Handshake + Clone, S: event::Subscriber + Clone> Start<'_, H, S> {
443449
fn socket_opts(&self, local_addr: SocketAddr) -> socket::Options {
444450
let mut options = socket::Options::new(local_addr);
445451

446-
options.backlog = self.backlog;
452+
// Explicitly do **not** set the socket backlog to self.backlog. While we split the
453+
// configured backlog amongst our in-process queues as concurrency increases, it doesn't
454+
// make sense to shrink the kernel backlogs -- that just causes packet drops and generally
455+
// bad behavior.
456+
//
457+
// This is especially true for TCP where we don't have workers matching concurrency.
447458
options.send_buffer = self.send_buffer;
448459
options.recv_buffer = self.recv_buffer;
449460
options.reuse_address = self.reuse_addr;

0 commit comments

Comments
 (0)