IoTHub半个月崩了五次?都是裸rethrow惹的祸

线上服务频繁崩溃,每次栈帧都不一样,看起来像玄学问题?其实很多时候都是同一类低级错误堆出来的。


问题现象

IoTHub进程半个月内连续崩溃了五次,每次崩溃的coredump栈帧都不一样:

  • 一次崩在 getTopicPrx
  • 一次崩在 getPublisher
  • 一次崩在IceStorm内部的rethrow
  • 一次崩在LOG_RECORD宏里
  • 还有一次栈帧直接乱了,根本看不出来在干嘛

每次重启完好好的,跑个一两天就崩,特别玄学。


第一反应:是不是Ice中间件的bug?

一开始以为是Ice 3.7的bug,毕竟每次崩溃栈里都有Ice的函数。 后来把所有coredump翻出来对比,发现一个共同点:所有崩溃的栈帧,最上面一层都是 __cxa_rethrow。

也就是异常被重新抛出之后,没人接住,直接把进程干崩了。


找问题:五处裸rethrow

把整个IoTHub代码搜了一遍,找出来五处写得非常"潇洒"的catch块:

cpp 复制代码
catch(...) {
    throw; // 直接裸rethrow,啥也不干
}

还有更离谱的:

cpp 复制代码
catch(const Ice::Exception& e) {
    throw e; // 重新拷贝抛出
}

这种代码写的时候爽,觉得"我不在这儿处理,交给上层处理",但问题是------上层根本就没有处理。 异常一路往上抛,最后跑到框架的事件循环里,框架看到没接住的异常,直接abort。


第二个问题:getTopicPrx里的catch不完整

还有一个坑:getTopicPrx 函数内部虽然写了try-catch,但是catch不全:

cpp 复制代码
try {
    tpcPrx = topicManager->retrieve(topic);
} catch(const Ice::NoTopic&) {
    // 处理NoTopic异常
}
// 没 catch Ice::ConnectionLost、SocketException 这些连接类异常

当IceStorm服务端重启、网络闪断的时候,抛出的连接异常根本没被catch住,直接就漏出去了,然后一路裸rethrow到进程退出。


修复:把所有漏出去的异常都接住

修复其实特别简单,就是把所有裸rethrow的地方,要么catch住打日志,要么在外面加一层兜底catch:

1. 五处裸rethrow,全部改成catch住打日志

原来的代码:

cpp 复制代码
try {
    // 一堆Ice操作
} catch(...) {
    throw; // 直接抛出去
}

改成:

cpp 复制代码
try {
    // 一堆Ice操作
} catch(const Ice::Exception& e) {
    LOG_ERROR("操作失败: " << e.what());
} catch(...) {
    LOG_ERROR("未知操作失败");
}

业务逻辑上不需要处理这个异常,那就接住打个日志就行,别让它把进程干崩了。 分布式系统里,网络闪断、服务端重启都是常态,不能因为一次连接失败就把整个进程搞挂。

2. getTopicPrx外面包一层兜底

cpp 复制代码
Ice::ObjectPrxPtr getTopicPrx(const string& topic) {
    try {
        // 原来的获取逻辑
    } catch(...) {
        LOG_ERROR("getTopicPrx failed: " << topic);
        return nullptr; // 返回空指针,调用处自己判断
    }
}

3. LOG_RECORD宏里的异常也接住

之前还有个坑:打日志的LOG_RECORD宏内部,打日志的时候抛了异常,结果日志没打成,反而把进程崩了。 这种地方更要兜底------日志系统是最后一道防线,不能因为打日志把进程搞挂。直接在宏内部catch住所有异常,啥也不做就行。


效果

改完之后部署上去,到现在跑了快一周,一次都没崩过。 之前平均两三天崩一次,现在稳得很。


总结

这种问题真的很常见:

  1. 写代码的时候觉得"这个异常我不在这处理,抛给上层",结果上层忘了处理
  2. try-catch写一半,只catch了想得到的异常,没想到的连接类、超时类异常直接漏出去
  3. 核心组件的初始化函数里没兜底,一次网络抖动就整个进程崩了

线上服务的稳定性,很多时候不是靠什么高深的架构,就是靠这些不起眼的细节:

  • 所有对外调用的地方,异常都接住,别让它炸穿进程
  • 核心组件失败了返回空指针,让业务层自己判断要不要重试
  • 宁可多打两行日志,也别图省事写个裸rethrow

毕竟,线上崩一次,半夜爬起来排障的痛苦,谁经历谁知道。

相关推荐
fpcc20 小时前
c++编程实践—堆和栈越界调试
c++
_upupup20 小时前
Linux的调试工具——gdb/cgdb
linux·服务器
weiabc21 小时前
MSYS2 UCRT64 + g++ C++ 中文输出乱码 完整全过程
c++
刚子编程1 天前
我用 ASP.NET Core 做了个水稻病虫害检查系统
后端·asp.net
谢亮_vipxieliang1 天前
用 PHP 构建轻量级 REST API:从路由到鉴权的完整实践
开发语言·后端·php
灯澜忆梦1 天前
【面向对象编程C++】| 基础语法
java·c++·算法
aqiu1111111 天前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode
Android系统攻城狮1 天前
Linux Gstreamer深度解析之gst_audio_sink_get_type调用流程与实战(六十五)
linux·运维·服务器·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
chenbingjie_c1 天前
C++ STL 适配器:stack & queue,从 vector/list 到 deque 深度解析
数据结构·c++