BUG FIX:进程退出时hang住

问题描述

进程kill后迟迟不退出,pstack看到一直在等锁:

Thread 1 (Thread 0x2b3db1e9d900 (LWP 83917)):
#0  0x00002b3db6193f4d in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00002b3db61915ae in _L_lock_39 () from /lib64/libpthread.so.0
#2  0x00002b3db61914e8 in pthread_cond_destroy@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#3  0x00002b3dbd7a9736 in CSequenceRuntime::~CSequenceRuntime (this=0x2b3dbd9af340 <CSequenceRuntime::m_cInstance>, __in_chrg=<optimized out>) at mf_util.cpp:95
#4  0x00002b3db7aaae69 in __run_exit_handlers () from /lib64/libc.so.6
#5  0x00002b3db7aaaeb5 in exit () from /lib64/libc.so.6
#6  0x00002b3db7a93b1c in __libc_start_main () from /lib64/libc.so.6
#7  0x00000000004022b9 in _start ()

检查

很明显,在执行exit时调用__run_exit_handlers执行全局变量/静态变量的析构函数。全局变量的析构函数中destroy一个条件变量。条件变量释放时一直在等锁,按理来说没有其他线程拿到条件变量的锁,而且条件变量的几个接口pthread_cond_signalpthread_cond_wait, pthread_cond_broast等都不会长时间拿着条件变量内部的锁。
用gdb查看一下pthread_cond_t的信息(这里只贴出来了条件变量的数据):

m_signalCond = {__data = {__lock = 2, __futex = 2, __total_seq = 18446744073709551615, __wakeup_seq = 1, __woken_seq = 1, 
        __mutex = 0x2b65fb6eb360 <CSequenceRuntime::m_cInstance+32>, __nwaiters = 0, __broadcast_seq = 1}, 
      __size = "\002\000\000\000\002\000\000\000\377\377\377\377\377\377\377\377\001\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000`\263n\373e+\000\000\000\000\000\000\001\000\000", __align = 8589934594}, 

注意看,条件变量的__total_seq的值非常大,是uint64_t(-1)。BUG主机环境上的glibc版本是2.17,通过代码查看只有在执行过pthread_cond_destroy之后才会设置为-1。这样就有一个猜想,是不是这个条件变量已经释放过一次,或者包含这个条件变量的对象已经释放过一次,再进一步,就是全局变量两次释放,或者有多次释放的问题。
因为这个场景很容易重现,就用gdb跟踪了一下,确实存在两次释放,而且在第二次释放的时候会hang在这里。
全局变量正常情况下只会释放一次,只有在不同的链接库中存在相同名字的全局对象时,才会释放两次。
用一个脚本把所有的链接库都检查一遍,看是否有两个链接库包含这个全局变量即可。pstack信息中展示调用pthread_cond_destroy的全局对象是CSequenceRuntime::m_cInstance,用个脚本检查一下:

for file in `ls *.so`
do 
    res=`nm $file | grep _ZN16CSequenceRuntime11m_cInstanceE`
    if [ "${res}x" != "x" ] 
    then 
        echo `ls -l $file` $res
    fi
done

_ZN16CSequenceRuntime11m_cInstanceE这个名字是我知道在哪个链接库中包含这个符号,先用nm查出来的:

lnbillkj%nm libxxxxD.so | grep CSequenceRuntime | grep m_cInstance
000000000020f300 B _ZN16CSequenceRuntime11m_cInstanceE

通过上面的脚本遍历检查,发现有个链接库本来应该是软链接,结果变成了真实文件。应该是操作人员把ln命令写成了cp。
输出结果:

-rwxr-xr-x 1 bill aigrp 472728 Sep 26 09:26 libmdb_frame_baseD.so 000000000020f340 B _ZN16CSequenceRuntime11m_cInstanceE
-rwxr-xr-x 1 bill aigrp 3789018 Sep 26 09:26 libmdb_in_baseD.so U _ZN16CSequenceRuntime11m_cInstanceE
-rwxr-xr-x 1 bill aigrp 3789018 Sep 26 09:25 libMdbInD.so U _ZN16CSequenceRuntime11m_cInstanceE
-rwxr-xr-x 1 bill aigrp 472728 Sep 26 09:24 libMFBaseD.so 000000000020f340 B _ZN16CSequenceRuntime11m_cInstanceE

链接库名字后面的字母U表示符号未定义,只是在链接库中引用了,B表示符号在未初始化的数据区中(BSS)。更多信息请看考man nm

网上也看到两个文章介绍全局变量多次析构的问题,不过与本例稍有不同,可以参考涨姿势:

  1. 排错经历:全局变量被多次析构
  2. 技巧:多共享动态库中同名对象重复析构问题的解决方法
    这两篇文章对全局变量多次析构的原理进行了阐述,阅读非常有益。第1篇文章中介绍说如果同一个实现文件(cpp文件)被多个链接库编译进去,而程序同时引用这两个链接库,就会出现两个重名的全局变量。第2篇介绍在头文件中定义了全局变量(其实大型程序中绝对不会允许这么做),多个链接库引用了这个头文件,而可执行程序同时引用了这些链接库,就会出现多个同名链接库。

另外,除了上面两篇文章介绍的造成一个全局符号在两个链接库中定义,最近还发现一个情况,就是extern Class::static_member
比如:

/// test.h
class A
{
public:
    int a;
    static A sa; 
};

cpp实现:

test.cpp
#include "test.h"
extern A A::sa;  // 按照常规理解 extern只是变量声明,不带实现,然而事实并非如此

编译:

g++ test.cpp -o libtest.so -shared -fPIC -std=c++11

检查符号:

nm libtest.so | grep sa

输出结果:

[~]$ nm libtest.so | grep sa
0000000000201054 B _ZN1A2saE    # B表示该符号已经定义

c++filt _ZN1A2saEA::sa
呆若木鸡。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值