问题现象
环境: datacenter 客户端-服务端通信场景
触发条件:
- 启动 datacenter
- 启动 server 端
- 启动 client 端
- 断开 server 端
观察到的异常:
- 客户端进程的文件描述符 (fd) 持续增长
lsof显示大量eventfd未释放- Valgrind 报告 "invalid file descriptor in syscall eventfd 2 ()"
- 进程最终因达到 fd 上限而崩溃
问题定位过程
1. FD 泄漏检测
bash
# 监控 fd 数量变化
watch -n 1 'ls /proc/`pidof service_cli_demo`/fd | wc -l' 结果: fd 数量持续增长
2. Lsof 分析
bash
lsof -p <pid> 发现: 大量 eventfd 未释放
service_c 863452 user 131 u a_inode 0,14 0 12612 [eventfd]
service_c 863452 user 132 u a_inode 0,14 0 12612 [eventfd]
service_c 863452 user 133 u a_inode 0,14 0 12612 [eventfd]
... 3. Valgrind 内存检查
bash
valgrind --leak-check=full --track-fds=yes --log-file=valgrind.log ./service_cli_demo 1 关键发现:
cpp
==982074== Warning: invalid file descriptor 1031 in syscall eventfd2()
==982074== FILE DESCRIPTORS: 1024 open at exit.
==982074== Open file descriptor 1023:
==982074== at 0x502654B: eventfd (syscall-template.S:78)
==982074== by 0x5128D2D: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x51513A3: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x512CB76: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x5154FAD: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x51219F9: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x514AB52: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x51520C6: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x511696B: ??? (in /usr/lib/x86_64-linux-gnu/libzmq.so.5.2.2)
==982074== by 0x499A2CB: zmq::socket_t::socket_t(zmq::context_t&, int) (zmq.hpp:1600)
==982074== by 0x499A378: zmq::socket_t::socket_t(zmq::context_t&, zmq::socket_type) (zmq.hpp:1608)
==982074== by 0x49D68E0: datacenter::MsgCenterBaseImpl::QryServerPort(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, datacenter::proto::ServerType) (msg_center_base_impl_broker.cpp:122) 调用栈指向:
cpp
datacenter::MsgCenterBaseImpl::QryServerPort()
-> zmq::socket_t::socket_t()
-> libzmq.so eventfd() 4. GDB 堆栈分析
bash
gdb attach <pid> 发现 ZMQ IO 线程阻塞:
cpp
Thread 5 (LWP 14473):
#0 __GI___poll (fds=0xffff8dff8d00, nfds=1, timeout=...)
#1 ?? () from /lib/libzmq.so.5
...
#5 zmq_msg_send () from /lib/libzmq.so.5 结论: ZMQ IO 线程阻塞在 zmq_msg_send() 调用中
5. 日志分析
重连成功后出现大量 Monitor 事件:
log
[2026-02-05 12:27:56.306] [net] [debug] Monitor event: 4, info: tcp://127.0.0.1:36035
[2026-02-05 12:27:56.306] [net] [debug] Monitor event: 2, info: tcp://127.0.0.1:36035
[2026-02-05 12:27:56.306] [net] [debug] Monitor event: 128, info: tcp://127.0.0.1:36035
... (重复数百次) Monitor 事件对照表:
| 事件宏 | 十进制值 | 说明 |
|---|---|---|
ZMQ_EVENT_CONNECT_DELAYED | 2 | 连接被延迟 |
ZMQ_EVENT_CONNECT_RETRIED | 4 | 连接重试 |
ZMQ_EVENT_CLOSED | 128 | 套接字关闭 |
ZMQ_EVENT_CONNECTED | 1 | 连接成功 |
ZMQ_EVENT_HANDSHAKE_SUCCEED | 4096 | 握手成功(十六进制 0 x 1000) |
根因分析
问题链路
1. Server 端断开
↓
2. Client 端 Monitor Socket 检测到断开事件
↓
3. Boost 定时器被取消,Monitor recv 停止从缓冲区取数据
↓
4. 主 Socket 持续重连,产生大量 Monitor 事件
↓
5. Monitor Socket 发送缓冲区满(消息积压)
↓
6. zmq_msg_send() 阻塞(部分 socket 类型会阻塞发送)
↓
7. ZMQ IO 线程阻塞
↓
8. 新创建的 zmq::socket_t 中 eventfd 无法释放
↓
9. FD 泄漏 核心问题
Monitor 接收端暂停消费
- 断开连接时,Boost 定时器被取消
- Monitor socket 的
recv操作停止 - 接收缓冲区消息积压
Monitor 发送端持续发送
- 主 socket 在重连过程中产生大量事件
- Monitor socket 持续发送事件到 inproc 通道
- 发送缓冲区满后阻塞
IO 线程阻塞
- 某些类型的
zmq::socket_t缓冲区满时会阻塞发送 - ZMQ IO 线程被
zmq_msg_send()阻塞
- 某些类型的
资源无法释放
- IO 线程阻塞导致 socket 析构被延迟
- Socket 内部的
eventfd无法正常关闭
实验验证
调大 Monitor 接收缓冲区:
- FD 增长速度明显变慢
- 验证了缓冲区满是触发条件
解决方案
临时方案
- Register Socket 优化()cpp
// 使用常驻 socket 避免频繁创建销毁 // 避免重连时重复创建 socket 导致 eventfd 泄漏
register socket 作为常驻 socket 时一旦 zmq io 阻塞,代码逻辑会执行两次 send,zmq 报错:
bash
terminate called after throwing an instance of 'zmq::error_t'
what(): Operation cannot be accomplished in current state - 屏蔽重连事件(已实施)cpp
// 在 Monitor 中过滤 CONNECT_RETRIED 和 CONNECT_DELAYED 事件 // 减少消息积压
待深入研究的问题
核心疑问:
为什么 ZMQ IO 线程阻塞会导致申请的
eventfd无法释放?
可能的原因:
zmq::socket_t析构函数需要 IO 线程协作完成eventfd的关闭操作在 IO 线程中执行- 析构过程中需要等待 IO 线程清空发送队列
需要验证:
- [ ] 查看 libzmq 源码中 socket 析构流程
- [ ] 确认 eventfd 创建和释放的具体时机
- [ ] 分析 IO 线程与主线程的同步机制