fix(quic): 入站 channel 满时丢包而非阻塞整条接收循环

QuicDataInbound::send 此前用异步 send 等待 channel 容量,消费端
(QUIC endpoint 驱动)处理不过来时,256 容量的 channel 一满就
阻塞整条接收循环,所有对端的入站流量被头部阻塞。

改为 try_send:满则丢包并告警(IP 包语义下可接受),关闭才报错。

测试:test_send_drops_when_channel_full(满时不阻塞)、
test_send_errors_when_channel_closed(关闭后报错)。
This commit is contained in:
lbl
2026-08-21 02:25:40 +08:00
parent 4f41468725
commit 8ecbfca939
@@ -19,10 +19,18 @@ pub struct QuicDataInbound {
}
impl QuicDataInbound {
pub async fn send(&self, data: Bytes, addr: Ipv4Addr) -> anyhow::Result<()> {
self.sender
.send((data, addr))
.await
.map_err(|_e| anyhow!("quic data inbound error"))
match self.sender.try_send((data, addr)) {
Ok(()) => Ok(()),
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
// 消费端处理不过来时丢包:channel 满不能阻塞整条 QUIC 接收循环,
// 否则一个慢消费者会卡住所有对端的入站流量
log::warn!("quic data inbound channel full, dropping packet from {addr}");
Ok(())
}
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
Err(anyhow!("quic data inbound error"))
}
}
}
}
impl Debug for QuicInnerInboundReceiver {
@@ -91,3 +99,42 @@ impl QuicInnerInboundReceiver {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
/// channel 满时 send 必须立即返回(丢包),不能阻塞接收循环
#[tokio::test]
async fn test_send_drops_when_channel_full() {
let (inbound, _receiver) = create_enhanced_inbound();
// 填满 channel(容量 256
for _ in 0..256 {
inbound
.send(Bytes::from_static(b"x"), Ipv4Addr::LOCALHOST)
.await
.unwrap();
}
// 再发送:旧实现会永久阻塞,修复后应立即返回 Ok(丢包)
let rs = tokio::time::timeout(
Duration::from_millis(200),
inbound.send(Bytes::from_static(b"y"), Ipv4Addr::LOCALHOST),
)
.await;
assert!(rs.is_ok(), "send blocked on full channel");
rs.unwrap().unwrap();
}
/// channel 关闭后 send 返回错误
#[tokio::test]
async fn test_send_errors_when_channel_closed() {
let (inbound, receiver) = create_enhanced_inbound();
drop(receiver);
let rs = inbound
.send(Bytes::from_static(b"x"), Ipv4Addr::LOCALHOST)
.await;
assert!(rs.is_err());
}
}