Skip to content

修复的 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 的根本原因是在高并发的连接创建/销毁场景下:

  1. 共享资源竞争导致死锁
  2. 生命周期管理不当导致 UAF 和资源泄漏
  3. 事件时序依赖导致阻塞
  4. 线程安全性不足导致状态不一致

修复方案通过资源独立化、操作原子化、主动清理等手段彻底解决了这些并发问题。

基于 VitePress 构建