@@ -22,6 +22,16 @@ use std::{io, net::SocketAddr, time::Duration};
2222use tokio:: io:: unix:: AsyncFd ;
2323use 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+
2535pub mod tcp;
2636pub 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