protobuf 异常死锁分析
最近在开发中,有个模块在测试时偶尔会出现卡死现象,进程状态处于futex状态。网络搜索发现这种状态下,进程大概率发生了死锁。最近的代码修改不涉及到锁的使用,可能是其它方面引入的问题。
在日常开发中,死锁并不陌生。只要能有堆栈就很容易解决。使用stace 命令发现进程处于FUTEX_WAIT_PRIVATE 状态,明确发生了死锁。
stace -p + 进程ID
接下来要需要gdb分析进程的堆栈,明确出现死锁的原因。可以使用gdb attach + 命令。
中间有个插曲,一开始用gdb分析时,提示找不到符号,原因时编译的时候没有带上符号把进程对应的代码重新编译加上-g就可以。利用gdb 输出堆栈发现最上层堆栈在protobuf库。
定位到具体行是pb库在输出错误日志 “Can’t parse message of type because it is missing required fields” 时发生了产生了死锁。问题就出在输出这条日志里面。
官方给出的解释是如果在pb协议里面有些required 字段没有赋值,pb库就会输出这个提示日志默认情况下这个日志会输出到stderr,也就是屏幕。如果设置了自定义日志输出函数,就不会调用默认日志输出函数。
经过排查代码,把部分字段从required改成optional之后,程序就没有出现死锁。怀疑是由于protobuf版本过老 以及公司操作系统内核版本问题引起,没有进一步分析。
如果业务进程在使用protobuf时是单线程操作,编译protobuf时可添加如下参数,DGOOGLE_PROTOBUF_NO_THREAD_SAFETY 开启非线程安全,可进一步避免出现死锁。非线程安全protobuf库编译以及使用方法如下。
编译方法:
./configure --prefix=/root/c++/protobuf_bin CPPFLAGS="-DGOOGLE_PROTOBUF_NO_THREAD_SAFETY -DGOOGLE_PROTOBUF_ONCE_INIT -DNDEBUG" && make -j28 && make check -j28 && make install --prefix 后面指定编译出来的protobuf静态库文件输出位置。
protobuf生成协议文件
./protobuf_bin/bin/protoc helloworld.proto --cpp_out=./
protobuf 使用方法
g++ -o helloworld helloworld.cc helloworld.pb.cc -DGOOGLE_PROTOBUF_NO_THREAD_SAFETY -DGOOGLE_PROTOBUF_ONCE_INIT -I./protobuf_bin_2.5/include -L./protobuf_bin_2.5/lib/ -lprotobuf -lpthread helloworld.cc 为测试文件,可以结合实际使用进行替换。
- 实际在开发中,对于protobuf协议中的字段要想清楚是否把字段设置为required,如果不是必须要填写的字段,建议统一设置为optional。
- 如果是单线程使用protobuf库进行封包、解包,建议使用非线程安全的protobuf库。
- 对于开源库的使用,要关注输出的错误日志,分析错误日志为啥产生、及时修改。