问题描述
在生产环境上,因为网络原因,导致初始化失败,在失败退出时有崩溃产生。
问题分析
在生产环境,linux
环境的dump
生成文件名为默认形式,实际dump
文件名为core.4055
的格式。通过原始elf
文件来载入分析,发现调用栈入口都是具体的函数地址,而不是具体的函数名,生产环境上的这个dump
,配合经过优化的elf
文件,没有给解决这个问题提供有效帮助。下一个阶段,转入日志分析。
由于后台服务有监控脚本,当进程崩溃时,会再重新拉起进程,每一个进程会生产对应的日志文件。从dump
文件为core.4055
上,可以得知崩溃进程的进程Id为4055,据此,在现有的众多日志文件中,通过
grep pid *.log
找出崩溃环境发生时涉及到的日志文件,一般找到多个日志文件,分别对应该服务内部各个模块的日志输出,然后再根据崩溃发生时间,通过
ls --full-time core.4055
查看精确到毫秒的崩溃时间,根据此时间,此处以 13:15:57
为说明,在前面的日志文件中,抽取该时间点之前的记录,进行日志聚合分析。
grep -B 20 --no-filename "13:15:57" *.log | sort -k1 -o merge_core_4055.log
-B:表示输出匹配行前20行的内容
--no-filename:不输出多文件匹配时的文件名
-k1:表示按日志中的时间排序
通过上述指令,获得聚合文件后,然后再对照源码,逐行分析崩溃点前的日志打印,寻找蛛丝马迹。经过分析发现,怀疑是初始化失败触发的。至于为什么初始化失败,可以稍微探究下原因,不过在这个场景下,初始化失败是崩溃的触发点,而不是崩溃的产生点。
问题复现:既然是初始化失败导致的,那么可以构造初始化失败场景,例如将连接地址设置无效,或者在调用完初始化后,始终返回初始化失败。通过这样的构造,可以稳定触发崩溃。最终发现,框架在初始化时,有一处单例服务的注入,在框架卸载时,会调用每个服务的UnInit后,再delete掉它。问题就出现在这里,delete一个单例指针,这个地址是由单例函数内部的静态局部变量提供的,释放不恰当的内存地址从而触发崩溃。
解决方案以及小结
此崩溃的问题原因是当程序退出时,释放了单例的局部静态变量地址,触发崩溃。修改方案有两个:
- 是不用单例服务,而是用new的方式将服务注入框架。
- 单例内部使用静态成员指针的形式来对外提供服务。
在以后的开发实践中,可手动触发后台服务的正常退出,验证退出逻辑是否正常。
在服务运行过程中,将自身进程Id以及线程Id输出是很有必要的,可以输出到日志文件,也可以单独输出到独立文件,例如mysql.pid文件。这样,方便后续在多种类型的日志文件中定位以及排错。