@@ -3,7 +3,7 @@ use std::path::Path;
33use std:: {
44 fmt,
55 net:: SocketAddr ,
6- num:: { NonZeroU8 , NonZeroU32 } ,
6+ num:: { NonZeroU8 , NonZeroU32 , NonZeroUsize } ,
77 sync:: Arc ,
88} ;
99
@@ -59,6 +59,9 @@ pub struct TransportConfig {
5959
6060 pub ( crate ) enable_segmentation_offload : bool ,
6161
62+ pub ( crate ) max_transmit_segments : NonZeroUsize ,
63+ pub ( crate ) max_transmit_datagrams : NonZeroUsize ,
64+
6265 pub ( crate ) address_discovery_role : address_discovery:: Role ,
6366
6467 pub ( crate ) max_concurrent_multipath_paths : Option < NonZeroU32 > ,
@@ -364,6 +367,39 @@ impl TransportConfig {
364367 self
365368 }
366369
370+ /// Maximum number of UDP segments encoded into a single GSO `sendmsg` batch.
371+ ///
372+ /// Effective batch size is also bounded by the platform's GSO capabilities (Linux with
373+ /// kernel ≥ 4.18 reports up to 64 via `UDP_MAX_SEGMENTS`; macOS reports `BATCH_SIZE`;
374+ /// Windows reports up to 512). When GSO is unavailable or disabled this setting has
375+ /// no effect, every `Transmit` carries a single datagram.
376+ ///
377+ /// Defaults to **10**, a conservative compromise tuned for multi-connection workloads.
378+ /// Single-connection bulk-transfer workloads on Linux+GSO benefit from raising this
379+ /// to 40 (≈ 51 KB per `sendmsg` at MTU 1280, 60 KB at MTU 1500), at the cost of a
380+ /// proportional increase in per-`Connection` send-buffer pre-allocation.
381+ ///
382+ /// Worst-case memory cost: `max_transmit_segments * current_mtu` bytes of pre-allocated
383+ /// buffer per active `Connection`.
384+ pub fn max_transmit_segments ( & mut self , value : NonZeroUsize ) -> & mut Self {
385+ self . max_transmit_segments = value;
386+ self
387+ }
388+
389+ /// Maximum number of datagrams produced in a single `drive_transmit` call.
390+ ///
391+ /// This caps the amount of CPU spent generating datagrams in one go, allowing other
392+ /// tasks (notably ACK reception and timer wakeups) to run. It is intended to be kept
393+ /// in lockstep with [`TransportConfig::max_transmit_segments`] (recommended ratio: 2×)
394+ /// so a single GSO super-segment can hold a couple of transmit drives back-to-back
395+ /// without bouncing to the scheduler.
396+ ///
397+ /// Defaults to **20**.
398+ pub fn max_transmit_datagrams ( & mut self , value : NonZeroUsize ) -> & mut Self {
399+ self . max_transmit_datagrams = value;
400+ self
401+ }
402+
367403 /// Whether to send observed address reports to peers.
368404 ///
369405 /// This will aid peers in inferring their reachable address, which in most NATd networks
@@ -563,6 +599,9 @@ impl Default for TransportConfig {
563599
564600 enable_segmentation_offload : true ,
565601
602+ max_transmit_segments : NonZeroUsize :: new ( 10 ) . expect ( "nonzero" ) ,
603+ max_transmit_datagrams : NonZeroUsize :: new ( 20 ) . expect ( "nonzero" ) ,
604+
566605 address_discovery_role : address_discovery:: Role :: default ( ) ,
567606
568607 // disabled multipath by default
@@ -608,6 +647,8 @@ impl fmt::Debug for TransportConfig {
608647 deterministic_packet_numbers : _,
609648 congestion_controller_factory : _,
610649 enable_segmentation_offload,
650+ max_transmit_segments,
651+ max_transmit_datagrams,
611652 address_discovery_role,
612653 max_concurrent_multipath_paths,
613654 default_path_max_idle_timeout,
@@ -648,6 +689,8 @@ impl fmt::Debug for TransportConfig {
648689 . field ( "datagram_send_buffer_size" , datagram_send_buffer_size)
649690 // congestion_controller_factory not debug
650691 . field ( "enable_segmentation_offload" , enable_segmentation_offload)
692+ . field ( "max_transmit_segments" , max_transmit_segments)
693+ . field ( "max_transmit_datagrams" , max_transmit_datagrams)
651694 . field ( "address_discovery_role" , address_discovery_role)
652695 . field (
653696 "max_concurrent_multipath_paths" ,
0 commit comments