C++线程回收误用,导致批量设备随机掉线!

C++线程回收误用,导致批量设备随机掉线!

[TOC]

脚步不停,终达卓越!更多优质文章及代码资源详见公众号 《开源519》

引言

  前段时间,项目上出现一个严重的设备掉线问题,导致客户投诉,简单记录一下前因后果。

场景

  • 问题描述:
    [高优先级] xxx版本 - 远程批量控制终端时触发设备随机异常掉线,且掉线后无法自动恢复。

  • 复现步骤:

    • 后台对运行xxx版本的终端发起远程批量控制操作。
    • 控制下发后,受控设备存在随机异常掉线,且无法自动恢复,手动重启正常(高频率出现)。

根因排查

困难所在

  每次遇到这种概率性问题,解决起来都比较困难,主要原因有下:

  • 本地无法复现: 本地测试环境(网络条件、负载压力、周边设备干扰)与实际部署的复杂环境无法对齐,导致无法稳定复现问题。
  • 现场日志获取困难: 设备已交付分散各地,实地状态确认和关键日志提取(需现场操作/设备可能无响应)耗时耗力且困难。
  • 无法找到触发规律: 从描述看,问题触发具有较高随机性,实际中频繁出现,但本地测试难以复现,无法找到明确触发规律。
  • 根因定位复杂: 掉线问题可能由多种深层问题(资源竞争、泄漏、协议异常等)引起,需要分析海量信息寻找线索。

分析过程

  1. 测试同事,好不容易协调好设备上线,远程取了日志后,立马呈给开发大佬排查。
  2. 像这种问题,首先过滤socket状态日志。从日志上看,似乎是远程服务器断开了客户端的连接:

recv error: Connection reset by peer

  3. 于是,压力给到服务器侧研发。服务器侧同事拿到问题后,给出结论:是因为存在相同的终端信息登入,导致设备被踢。
  4. 问题甩到了终端侧研发,看到此结论,首先怀疑是否有不同的终端误刷了相同的设备ID。
  5. 测试同事坚决否认:货设备ID管控,不可能误刷相同信息。
  6. 终端研发无奈只能结合日志,逐行排查控制业务相关的代码。
  7. 星光不负赶路人,经过苦思冥想和 "地中海" 加成,终于在代码实现上,找到一些蛛丝马迹。

代码定位

问题代码大致如下:

void DeviceConnection::reconnect() {
...
    if (m_worker.joinable()) {
        ... // 省略 触发socket关闭
        m_worker.detach(); 
    }
    
    m_worker = std::thread([this] {
        ... // 省略 socket连接
    });
}

最终定位到此处,从代码上分析:

  • 本意应该是先关闭socket,然后回收线程。
  • 问题就在于detach是非阻塞的,并不会等待socket完全关闭。这就导致了在新线程创建新的socket连接时,上一个socket可能还没关闭。
  • 这也就与实际现象吻合看,如果socket关闭的够快,就不会存在这种问题。而本地复现时,CPU处理的业务往往没有实际场景复杂,故难以复现。

解决方案

  找到问题了,修复起来也就快了。主要有两种方式:

  • detach()替换成join(),可快速解决问题。
  • 将重连逻辑优化至一个线程处理,避免重复创建线程,但改动较大,实施复杂。

  鉴于当前需快速、稳妥地修复问题,选择了第一种方案。虽然从长远看第二种方案更优,但受限于项目进度,第一种方案是当下最优解。后续类似功能的开发,可参考第二种方案。

总结

  回头再看这个紧急问题,根源在于误用了线程detach()。后续开发中,回收线程尽量不要使用detach()

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容