@@ -223,9 +223,9 @@ impl Builder {
223223 ) ;
224224
225225 let concurrency: usize = self . workers . unwrap_or_else ( || {
226- std :: thread :: available_parallelism ( )
227- . unwrap_or_else ( |_| 1 . try_into ( ) . unwrap ( ) )
228- . into ( )
226+ usize :: from (
227+ std :: thread :: available_parallelism ( ) . unwrap_or_else ( |_| 1 . try_into ( ) . unwrap ( ) ) ,
228+ )
229229 } ) ;
230230
231231 let backlog: usize = self . backlog . map ( NonZeroU16 :: get) . unwrap_or ( DEFAULT_BACKLOG ) as usize ;
@@ -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