fn record_udp_send_path(connected: bool, count: u64) {
let event = if connected {
crate::perf_profile::Event::UdpSendConnected
} else {
crate::perf_profile::Event::UdpSendWildcard
};
crate::perf_profile::record_event_count(event, count);
}
fn is_send_backpressure(err: &std::io::Error) -> bool {
err.kind() == std::io::ErrorKind::WouldBlock
|| err.raw_os_error().is_some_and(raw_send_backpressure_code)
}
#[cfg(unix)]
fn raw_send_backpressure_code(code: i32) -> bool {
code == libc::ENOBUFS || code == libc::ENOMEM
}
#[cfg(windows)]
fn raw_send_backpressure_code(code: i32) -> bool {
const WSAENOBUFS: i32 = 10055;
const ERROR_NOT_ENOUGH_MEMORY: i32 = 8;
code == WSAENOBUFS || code == ERROR_NOT_ENOUGH_MEMORY
}
#[cfg(not(any(unix, windows)))]
fn raw_send_backpressure_code(_code: i32) -> bool {
false
}
#[derive(Default)]
struct SendBackpressurePacer {
consecutive_full: u32,
full_since_sleep: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SendBackpressureAction {
Yield,
Sleep,
DropBulk,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SendBackpressureDecision {
Retry,
DropCurrentBulk,
}
fn send_backpressure_decision(
pacer_requested_drop: bool,
drop_on_backpressure: bool,
) -> SendBackpressureDecision {
if pacer_requested_drop && drop_on_backpressure {
SendBackpressureDecision::DropCurrentBulk
} else {
SendBackpressureDecision::Retry
}
}
impl SendBackpressurePacer {
fn record_success(&mut self) {
self.consecutive_full = 0;
self.full_since_sleep = 0;
}
fn next_action(
&mut self,
would_block: bool,
sleep_after: u32,
drop_after: u32,
) -> SendBackpressureAction {
if would_block {
self.record_success();
return SendBackpressureAction::Yield;
}
self.consecutive_full = self.consecutive_full.saturating_add(1);
self.full_since_sleep = self.full_since_sleep.saturating_add(1);
if drop_after > 0 && self.consecutive_full >= drop_after {
self.record_success();
return SendBackpressureAction::DropBulk;
}
if sleep_after > 0 && self.full_since_sleep >= sleep_after {
self.full_since_sleep = 0;
return SendBackpressureAction::Sleep;
}
SendBackpressureAction::Yield
}
fn pause(&mut self, err: &std::io::Error) -> bool {
crate::perf_profile::record_event(crate::perf_profile::Event::UdpSendBackpressure);
if err.kind() == std::io::ErrorKind::WouldBlock {
let action = self.next_action(
true,
send_backpressure_sleep_after(),
send_backpressure_drop_after(),
);
debug_assert_eq!(action, SendBackpressureAction::Yield);
std::thread::yield_now();
return false;
}
static SEND_BACKPRESSURE_COUNT: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
let n = SEND_BACKPRESSURE_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if n < 8 || n.is_multiple_of(100_000) {
warn!(
error = %err,
events = n + 1,
"UDP send queue full; applying kernel backpressure"
);
}
match self.next_action(
false,
send_backpressure_sleep_after(),
send_backpressure_drop_after(),
) {
SendBackpressureAction::DropBulk => return true,
SendBackpressureAction::Sleep => {
crate::perf_profile::record_event(
crate::perf_profile::Event::UdpSendBackpressureSleep,
);
std::thread::sleep(std::time::Duration::from_micros(
send_backpressure_sleep_micros(),
));
}
SendBackpressureAction::Yield => std::thread::yield_now(),
}
false
}
}
fn send_backpressure_sleep_after() -> u32 {
static VALUE: OnceLock<u32> = OnceLock::new();
*VALUE.get_or_init(|| {
std::env::var("FIPS_SEND_BACKPRESSURE_SLEEP_AFTER")
.ok()
.and_then(|raw| raw.trim().parse::<u32>().ok())
.unwrap_or(default_send_backpressure_sleep_after())
})
}
fn send_backpressure_sleep_micros() -> u64 {
static VALUE: OnceLock<u64> = OnceLock::new();
*VALUE.get_or_init(|| {
std::env::var("FIPS_SEND_BACKPRESSURE_SLEEP_MICROS")
.ok()
.and_then(|raw| raw.trim().parse::<u64>().ok())
.unwrap_or(default_send_backpressure_sleep_micros())
.max(1)
})
}
fn send_backpressure_drop_after() -> u32 {
static VALUE: OnceLock<u32> = OnceLock::new();
*VALUE.get_or_init(|| {
std::env::var("FIPS_SEND_BACKPRESSURE_DROP_AFTER")
.ok()
.and_then(|raw| raw.trim().parse::<u32>().ok())
.unwrap_or(default_send_backpressure_drop_after())
})
}
#[cfg(target_os = "macos")]
fn default_send_backpressure_sleep_after() -> u32 {
4
}
#[cfg(not(target_os = "macos"))]
fn default_send_backpressure_sleep_after() -> u32 {
0
}
#[cfg(target_os = "macos")]
fn default_send_backpressure_sleep_micros() -> u64 {
100
}
#[cfg(not(target_os = "macos"))]
fn default_send_backpressure_sleep_micros() -> u64 {
1
}
#[cfg(target_os = "macos")]
fn default_send_backpressure_drop_after() -> u32 {
256
}
#[cfg(not(target_os = "macos"))]
fn default_send_backpressure_drop_after() -> u32 {
0
}
#[cfg(test)]
mod send_backpressure_tests {
use super::*;
#[test]
fn send_backpressure_pacer_wouldblock_yields_and_resets() {
let mut pacer = SendBackpressurePacer {
consecutive_full: 7,
full_since_sleep: 3,
};
assert_eq!(pacer.next_action(true, 1, 1), SendBackpressureAction::Yield);
assert_eq!(pacer.consecutive_full, 0);
assert_eq!(pacer.full_since_sleep, 0);
}
#[test]
fn send_backpressure_pacer_drops_bulk_after_explicit_budget() {
let mut pacer = SendBackpressurePacer::default();
assert_eq!(
pacer.next_action(false, 0, 2),
SendBackpressureAction::Yield
);
assert_eq!(pacer.consecutive_full, 1);
assert_eq!(
pacer.next_action(false, 0, 2),
SendBackpressureAction::DropBulk
);
assert_eq!(
pacer.consecutive_full, 0,
"drop decision should reset the consecutive pressure budget"
);
assert_eq!(pacer.full_since_sleep, 0);
}
#[test]
fn send_backpressure_drop_decision_requires_packet_policy() {
assert_eq!(
send_backpressure_decision(true, true),
SendBackpressureDecision::DropCurrentBulk
);
assert_eq!(
send_backpressure_decision(true, false),
SendBackpressureDecision::Retry
);
assert_eq!(
send_backpressure_decision(false, true),
SendBackpressureDecision::Retry
);
}
#[test]
fn send_backpressure_pacer_sleep_does_not_reset_drop_budget() {
let mut pacer = SendBackpressurePacer::default();
assert_eq!(
pacer.next_action(false, 2, 3),
SendBackpressureAction::Yield
);
assert_eq!(
pacer.next_action(false, 2, 3),
SendBackpressureAction::Sleep
);
assert_eq!(
pacer.consecutive_full, 2,
"sleep throttles retry rate without hiding sustained pressure"
);
assert_eq!(
pacer.next_action(false, 2, 3),
SendBackpressureAction::DropBulk
);
}
#[test]
#[cfg(unix)]
fn send_backpressure_classifier_covers_socket_buffer_errors() {
assert!(is_send_backpressure(&std::io::Error::from(
std::io::ErrorKind::WouldBlock
)));
assert!(is_send_backpressure(&std::io::Error::from_raw_os_error(
libc::ENOBUFS
)));
assert!(is_send_backpressure(&std::io::Error::from_raw_os_error(
libc::ENOMEM
)));
assert!(!is_send_backpressure(&std::io::Error::from(
std::io::ErrorKind::PermissionDenied
)));
}
}
#[cfg(unix)]
fn record_udp_send_backpressure_drop(err: &std::io::Error) {
crate::perf_profile::record_event(crate::perf_profile::Event::UdpSendBulkDropped);
static SEND_BACKPRESSURE_DROP_COUNT: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
let n = SEND_BACKPRESSURE_DROP_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if n < 8 || n.is_multiple_of(100_000) {
warn!(
error = %err,
drops = n + 1,
"UDP send queue full; dropping bulk data packet"
);
}
}
#[cfg(target_os = "macos")]
struct MacSendRatePacer {
bytes_per_sec: f64,
burst_bytes: f64,
credit_bytes: f64,
last: std::time::Instant,
}
#[cfg(target_os = "macos")]
impl Default for MacSendRatePacer {
fn default() -> Self {
let mbps = std::env::var("FIPS_MACOS_SEND_PACE_MBPS")
.ok()
.and_then(|raw| raw.trim().parse::<f64>().ok())
.unwrap_or(350.0);
let bytes_per_sec = if mbps.is_finite() && mbps > 0.0 {
mbps * 1_000_000.0 / 8.0
} else {
0.0
};
let burst_bytes = std::env::var("FIPS_MACOS_SEND_PACE_BURST_BYTES")
.ok()
.and_then(|raw| raw.trim().parse::<f64>().ok())
.filter(|value| value.is_finite() && *value > 0.0)
.unwrap_or(256.0 * 1024.0);
Self {
bytes_per_sec,
burst_bytes,
credit_bytes: burst_bytes,
last: std::time::Instant::now(),
}
}
}
#[cfg(target_os = "macos")]
thread_local! {
static MAC_DIRECT_SEND_RATE_PACER: RefCell<MacSendRatePacer> =
RefCell::new(MacSendRatePacer::default());
}
#[cfg(target_os = "macos")]
impl MacSendRatePacer {
fn pace(&mut self, bytes: usize) {
if self.bytes_per_sec <= 0.0 || bytes == 0 {
return;
}
let needed = bytes as f64;
let now = std::time::Instant::now();
let elapsed = now.saturating_duration_since(self.last).as_secs_f64();
self.credit_bytes =
(self.credit_bytes + elapsed * self.bytes_per_sec).min(self.burst_bytes);
self.last = now;
if self.credit_bytes >= needed {
self.credit_bytes -= needed;
return;
}
let wait_secs = (needed - self.credit_bytes) / self.bytes_per_sec;
self.credit_bytes = 0.0;
let deadline = now + std::time::Duration::from_secs_f64(wait_secs);
let spin_window = std::time::Duration::from_micros(75);
loop {
let now = std::time::Instant::now();
if now >= deadline {
self.last = now;
break;
}
let remaining = deadline - now;
if remaining > spin_window {
std::thread::sleep(remaining - spin_window);
} else {
std::hint::spin_loop();
}
}
}
}
#[cfg(target_os = "linux")]
static GSO_DISABLED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
#[cfg(target_os = "linux")]
fn is_gso_capability_error(err: &std::io::Error) -> bool {
err.kind() == std::io::ErrorKind::InvalidInput
|| matches!(err.raw_os_error(), Some(code)
if code == libc::EOPNOTSUPP || code == libc::ENOPROTOOPT || code == libc::EIO)
}
#[cfg(target_os = "linux")]
fn gso_eligible_sizes_ref(packets: &[Vec<u8>]) -> bool {
if packets.len() < 2 {
return false;
}
let seg = packets[0].len();
if seg == 0 {
return false;
}
for p in &packets[..packets.len() - 1] {
if p.len() != seg {
return false;
}
}
packets[packets.len() - 1].len() <= seg
}
#[cfg(target_os = "linux")]
fn send_batch_gso(
fd: std::os::unix::io::RawFd,
packets: &[Vec<u8>],
dest: SocketAddr,
connected: bool,
) -> std::io::Result<()> {
debug_assert!(!packets.is_empty());
let n = packets.len().min(LINUX_UDP_SEND_BATCH_MAX);
if n == 0 {
return Ok(());
}
let seg_size = packets[0].len() as u16;
let sa: socket2::SockAddr = dest.into();
let mut iovs: [libc::iovec; LINUX_UDP_SEND_BATCH_MAX] = unsafe { std::mem::zeroed() };
for (i, data) in packets[..n].iter().enumerate() {
iovs[i].iov_base = data.as_ptr() as *mut libc::c_void;
iovs[i].iov_len = data.len();
}
let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let sa_len = sa.len();
if !connected {
unsafe {
std::ptr::copy_nonoverlapping(
sa.as_ptr() as *const u8,
&mut storage as *mut _ as *mut u8,
sa_len as usize,
);
}
}
let cmsg_space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<u16>() as u32) as usize };
let mut cmsg_buf = [0u8; 64];
debug_assert!(cmsg_space <= cmsg_buf.len());
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
if connected {
msg.msg_name = std::ptr::null_mut();
msg.msg_namelen = 0;
} else {
msg.msg_name = &mut storage as *mut _ as *mut libc::c_void;
msg.msg_namelen = sa_len;
}
msg.msg_iov = iovs.as_mut_ptr();
msg.msg_iovlen = n as _;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut libc::c_void;
msg.msg_controllen = cmsg_space as _;
unsafe {
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err(std::io::Error::other("CMSG_FIRSTHDR returned null"));
}
(*cmsg).cmsg_level = libc::IPPROTO_UDP as _;
(*cmsg).cmsg_type = libc::UDP_SEGMENT as _;
(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<u16>() as u32) as _;
let data = libc::CMSG_DATA(cmsg) as *mut u16;
*data = seg_size;
}
let r = unsafe { libc::sendmsg(fd, &msg, 0) };
if r < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}
#[cfg(target_os = "linux")]
fn send_batch_raw(
fd: std::os::unix::io::RawFd,
packets: &[Vec<u8>],
dest: SocketAddr,
connected: bool,
) -> std::io::Result<usize> {
let n = packets.len().min(LINUX_UDP_SEND_BATCH_MAX);
if n == 0 {
return Ok(0);
}
let mut iovs: [libc::iovec; LINUX_UDP_SEND_BATCH_MAX] = unsafe { std::mem::zeroed() };
let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut storage_len: libc::socklen_t = 0;
let mut msgs: [libc::mmsghdr; LINUX_UDP_SEND_BATCH_MAX] = unsafe { std::mem::zeroed() };
if !connected {
let sa: socket2::SockAddr = dest.into();
let sa_len = sa.len();
unsafe {
std::ptr::copy_nonoverlapping(
sa.as_ptr() as *const u8,
&mut storage as *mut _ as *mut u8,
sa_len as usize,
);
}
storage_len = sa_len;
}
for i in 0..n {
let data = &packets[i];
iovs[i].iov_base = data.as_ptr() as *mut libc::c_void;
iovs[i].iov_len = data.len();
msgs[i].msg_hdr.msg_iov = &mut iovs[i];
msgs[i].msg_hdr.msg_iovlen = 1 as _;
if connected {
msgs[i].msg_hdr.msg_name = std::ptr::null_mut();
msgs[i].msg_hdr.msg_namelen = 0;
} else {
msgs[i].msg_hdr.msg_name = &mut storage as *mut _ as *mut libc::c_void;
msgs[i].msg_hdr.msg_namelen = storage_len;
}
}
let r = unsafe { libc::sendmmsg(fd, msgs.as_mut_ptr(), n as libc::c_uint, 0) };
if r < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(r as usize)
}
}