Skip to content

UDP写操作阻塞导致读操作停滞及内核接收缓冲区满溢 #26

Closed

Description

@silencelbl

所使用的 tnet 版本
v1.0.1

当前程序GO版本:
go1.23.2

操作系统和处理器架构
AlmaLinux 8.10 (Cerulean Leopard) x86_64 Intel(R) Xeon(R) Platinum 8255C CPU @ 2.50GHz Core(s) per socket: 8

操作步骤
服务器使用tnet的UDP模块,开启多路复用,配置为isIONonblocking=false, useBusinessRoutinePool=true
监听多个客户端向服务器发送的UDP数据包,在正常逻辑中,按一定频率固定返回消息给同组的客户端。

期望结果
即使写操作失败(EAGAIN),writing锁应合理释放,并通知底层再次读取需要发送的数据继续发送

实际观察到的结果
通过ss -numpa查看服务器UDP连接状态,程序多次出现UDP监听端口接收数据缓存溢出现象
UNCONN 46214656 0 0.0.0.0:25000 0.0.0.0:* users:(("server",pid=724130,fd=42))
skmem:(r46214656,rb46214400,t0,tb26214400,f512,w0,o0,bl0,d16490)

以下为dlv工具捕获数据:
inBuffer: trpc.group/trpc-go/tnet/internal/buffer.Buffer {
head: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc164e6ec30),
tail: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc164e6ec30),
rnode: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc164e6ec30),
wnode: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc164e6ec30),
rlock: (*sync.Mutex)(0xc003274b98),
wlock: (sync.Mutex)(0xc003274ba0),
rlen: 0,
wlen: 92,
nodeBlockSize: 4096,
enableAutoNodeBlockSize: true,},
outBuffer: trpc.group/trpc-go/tnet/internal/buffer.Buffer {
head: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc0c9f78e70),
tail: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc13fcc2b70),
rnode: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc0c9f78e70),
wnode: ("trpc.group/trpc-go/tnet/internal/buffer.node")(0xc13fcc2b70),
rlock: (sync.Mutex)(0xc003274bd8),
wlock: (sync.Mutex)(0xc003274be0),
rlen: 4370476,
wlen: 0,
nodeBlockSize: 16384,
enableAutoNodeBlockSize: true,}
rtimer: trpc.group/trpc-go/tnet/internal/timer.Timer nil,
wtimer: trpc.group/trpc-go/tnet/internal/timer.Timer nil,
nfd: trpc.group/trpc-go/tnet.netFD {
desc: ("trpc.group/trpc-go/tnet/internal/poller.Desc")(0xc000430e60),
sock: trpc.group/trpc-go/tnet.goSockCloser(net.UDPConn) ...,
laddr: net.Addr(net.UDPAddr) ...,
raddr: net.Addr nil,
network: "udp",
fd: 42,
fdtype: fdUDP (1),
closed: (
"go.uber.org/atomic.Bool")(0xc003274c60),
locker: (sync.Mutex)(0xc003274c68),
udpBufferSize: 548,},
closer: trpc.group/trpc-go/tnet.closer {
sysReadJob: (
"trpc.group/trpc-go/tnet/internal/safejob.ExclusiveUnblockJob")(0xc003274c78),
sysWriteJob: (
"trpc.group/trpc-go/tnet/internal/safejob.ExclusiveUnblockJob")(0xc003274c88),
apiReadJob: (
"trpc.group/trpc-go/tnet/internal/safejob.ExclusiveBlockJob")(0xc003274c98),
apiWriteJob: (
"trpc.group/trpc-go/tnet/internal/safejob.ConcurrentJob")(0xc003274ca8),
apiCtrlJob: (
"trpc.group/trpc-go/tnet/internal/safejob.ExclusiveBlockJob")(0xc003274cc8),
closeAllJob: (
"trpc.group/trpc-go/tnet/internal/safejob.OnceJob")(0xc003274cd8),},
postpone: trpc.group/trpc-go/tnet/internal/autopostpone.PostponeWrite {
prevPacketsNum: 0,
readingTryLockFailed: (
"go.uber.org/atomic.Uint32")(0xc003274ce8),
loopCnt: 0,
consecutiveSamePacketsNumCounts: 0,
enable: true,},
reading: 0,
writing: 1,
nonblocking: false,}

outBuffer:持续堆积数据outBuffer.rlen≈4.3MB;
由于outBuffer始终非空,writing锁(writing=1)被一直持有,底层epoll未再次收到可写事件,导致数据一直驻留在outBuffer缓存中;
因为IO操作都在同一个poller协程中,因为发送阻塞了,引起了接受缓冲区溢出?

Activity

  1. changed the title [-]UDP 同步模式下写操作阻塞导致读操作停滞及内核接收缓冲区满溢[/-] [+]UDP写操作阻塞导致读操作停滞及内核接收缓冲区满溢[/+] on Aug 10, 2025
  2. silencelbl commented on Aug 11, 2025

    @silencelbl
    Author

    @WineChord 大佬有空看看呢

  3. Rememorio commented on Jun 18, 2026

    @Rememorio
    Contributor

    感谢反馈。看现象和代码路径,这个问题不像是单纯使用方式问题,WithNonBlocking(false) + 业务协程池处理 UDP 请求本身是 tnet 支持的模式。

    我初步看了下 UDP 写路径:当 WriteTo 无法立即写出时,数据会进入 outBuffer,并通过 ModReadWriteable 由 poller 后续触发 udpOnWrite 发送;udpOnWrite 遇到 EAGAIN/EWOULDBLOCK 时当前实现会直接返回,并保持 writing 状态,只有 outBuffer 清空后才会切回只读事件并释放 writing。所以你看到的 outBuffer 堆积和 writing=1 与当前实现路径是吻合的。

    不过 writing=1 本身不一定等价于死锁,它也表示当前连接已经进入“由 poller 负责继续刷写”的状态。真正需要确认的是:在 UDP 发送侧持续背压时,是否会导致写事件长期占用/反复触发,从而拖慢读事件处理,最终造成内核接收缓冲区溢出。

    我倾向于把这个问题按 tnet UDP 写侧背压/公平性问题继续看,后续可能需要补充:

    1. UDP outBuffer 的上限或丢弃/返回错误策略;
    2. udpOnWrite 遇到 EAGAIN 后的事件处理策略;
    3. 写事件和读事件在同一个 poller 上的公平性验证。

    如果方便的话,也麻烦补充一下:

    • 发送给客户端的包大小和频率;
    • 客户端是否可能不读、读得慢,或者存在不可达地址;
    • 出现问题时 UDPSendMMsgFails / UDPSendMMsgPackets 等指标是否有异常;
    • 服务端是否开启 reuseport,以及 poller 数量配置。
  4. silencelbl commented on Jun 18, 2026

    @silencelbl
    Author

    包不大,500字节以内,当时频率有点高。
    不是客户端不读,是数据根本没进入网络底层进行发包。
    当时观察UDPSendMMsgFails / UDPSendMMsgPackets没见异常。
    有开启 reuseport,poller当时是8个。

    时间有点久远了,当时不止遇到一次这个问题,表现就是outBuffer持续堆积数据,writing锁(writing=1)被一直持有,底层不在处理读写事件。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions