Skip to content

Commit afb6ecf

Browse files
opt(s2n-quic-dc): clamp TCP server concurrency (#2827)
The lock contention referenced in the comment is a significant problem as the number of cores increases. In the future we can consider other mitigations for it (e.g., io_uring's fixed fd pool), but that's not possible until we can raise our minimal kernel baseline and is a much larger effort.
1 parent 7faf3d8 commit afb6ecf

File tree

1 file changed

+17
-4
lines changed
  • dc/s2n-quic-dc/src/stream/server

1 file changed

+17
-4
lines changed

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,10 @@ impl<H: Handshake + Clone, S: event::Subscriber + Clone> Start<'_, H, S> {
349349
// find a port and spawn the initial listeners
350350
self.spawn_initial_wildcard_pair()?;
351351
// spawn the rest of the concurrency
352-
self.spawn_count(self.concurrency - 1)?;
352+
self.spawn_count(self.concurrency - 1, 1)?;
353353
} else {
354354
// otherwise spawn things as normal
355-
self.spawn_count(self.concurrency)?;
355+
self.spawn_count(self.concurrency, 0)?;
356356
}
357357

358358
debug_assert_ne!(
@@ -399,20 +399,33 @@ impl<H: Handshake + Clone, S: event::Subscriber + Clone> Start<'_, H, S> {
399399
}
400400

401401
#[inline]
402-
fn spawn_count(&mut self, count: usize) -> io::Result<()> {
402+
fn spawn_count(&mut self, count: usize, already_running: usize) -> io::Result<()> {
403403
for protocol in [socket::Protocol::Udp, socket::Protocol::Tcp] {
404404
match protocol {
405405
socket::Protocol::Udp => ensure!(self.enable_udp, continue),
406406
socket::Protocol::Tcp => ensure!(self.enable_tcp, continue),
407407
_ => continue,
408408
}
409-
for _ in 0..count {
409+
410+
for idx in 0..count {
410411
match protocol {
411412
socket::Protocol::Udp => {
412413
let socket = self.socket_opts(self.server.local_addr).build_udp()?;
413414
self.spawn_udp(socket)?;
414415
}
415416
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 {
426+
continue;
427+
}
428+
416429
let socket = self
417430
.socket_opts(self.server.local_addr)
418431
.build_tcp_listener()?;

0 commit comments

Comments
 (0)