修复的 Bug 及原因
1. 代理线程停止时的死锁/阻塞问题
Bug 原因:
- 旧代码在
ServerManager::DestoryMachine()中使用共享的m_ctrl_socket通过connect/disconnect方式向代理线程发送终止信号 - 多线程环境下, 多个
DestoryMachine调用可能同时connect到同一个 socket, 导致竞态条件 - ZMQ socket 的
connect/disconnect不是线程安全的操作 - 可能导致 socket 发送永久阻塞或连接错误
修复方案:
- 为每个
ReqRepMachine创建独立的m_socket_ctrl_client, 在构造函数中初始化并连接到自己的控制通道 - 设置
ZMQ_SNDTIMEO = 1000ms, 防止发送永久阻塞 - 新增
StopProxy()方法, 直接通过自己的 client socket 发送终止信号, 避免共享资源竞争
2. 监控停止顺序导致的事件发送阻塞
Bug 原因:
- 旧代码先调用
SocketCancelMonitor()取消监控 - 取消监控时 ZMQ 会向监控 socket 发送
ZMQ_EVENT_MONITOR_STOPPED事件 - 但如果立即关闭接收监控套接字, 可能导致事件发送阻塞,从而导致 ZMQ 内部 IO 线程阻塞
修复方案:
- 在
DestoryMachine中调整顺序:先停止监控 → 再停止代理 → 最后删除对象 - 新增
StopMonitor()方法独立处理监控停止 - 注释掉
Stop()中的SocketCancelMonitor()调用, 改为在DestoryMachine中统一处理
3. 监控事件处理中的 UAF (Use-After-Free) 风险
Bug 原因:
- 在处理
ZMQ_EVENT_HANDSHAKE_SUCCEEDED等事件时, 旧代码在 switch 前就获取ser_name = dict[item.socket] - 如果 socket 不在 dict 中 (已被删除), 会访问到悬空引用
修复方案:
- 改为在需要时才从 dict 获取
ser_name HANDSHAKE_SUCCEEDED事件使用dict[item.socket]直接记录日志, 不提前复制DISCONNECTED事件在使用前才赋值ser_name
4. 监控事件未及时到达导致的资源泄漏
Bug 原因:
- 旧代码在
ZMQ_EVENT_MONITOR_STOPPED事件中清理监控 socket - 但注释指出: "可能会收不到监控停止事件"
- 导致
m_servers_monitor_map中的 socket 无法释放
修复方案:
- 在
DestoryMachine中主动关闭并删除监控 socket, 不依赖事件 - 将
ZMQ_EVENT_MONITOR_STOPPED的清理逻辑注释掉, 改为在销毁时统一处理
5. CreateMachine 中的锁作用域问题
Bug 原因:
- 旧代码在
machine->Start(pg)成功后才加锁, 然后插入到 map 中 - 在加锁前的间隙, 其他线程可能已经创建了同名的 machine, 导致覆盖或状态不一致
修复方案:
- 将
lock_guard的作用域提前到创建machine之前 - 确保整个创建和插入过程都在锁保护中, 保证原子性
6. 监控 socket 配置不足
Bug 原因:
- 监控 socket 没有设置超时和高水位标记
- 大量事件可能导致缓冲区溢出或阻塞
修复方案:
- 添加
ZMQ_LINGER = 0(立即关闭, 不等待消息发送) - 添加
ZMQ_RCVHWM = 100000(接收高水位标记, 防止内存溢出)
7. 事件计数逻辑不严谨
Bug 原因:
- 旧代码只在
ser_cnt[item.socket] == 0时销毁,但实际可能出现负数 (多次 disconnect)
修复方案:
- 改为
ser_cnt[item.socket] <= 0判断, 更加健壮 - 添加
ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL事件处理, 增加计数正确性
8. 拼写错误
Bug: m_servers_moitor_map 拼写错误
修复: 改为正确的 m_servers_monitor_map
核心问题总结
这些 bug 的根本原因是在高并发的连接创建/销毁场景下:
- 共享资源竞争导致死锁
- 生命周期管理不当导致 UAF 和资源泄漏
- 事件时序依赖导致阻塞
- 线程安全性不足导致状态不一致
修复方案通过资源独立化、操作原子化、主动清理等手段彻底解决了这些并发问题。