protobuf 异常死锁分析

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库。
  • 对于开源库的使用,要关注输出的错误日志,分析错误日志为啥产生、及时修改。
欢迎关注公众号,一起学习、一起进步。

发表评论

邮箱地址不会被公开。 必填项已用*标注