pthread_key泄露问题检测
问题描述
进程启动后业务处理异常,经过调试发现是pthread_key_create
返回失败。pthread_key_create
创建一个TLS变量,因为用的少,不会占用其它系统资源,并且创建失败后,进程肯定不能正常运行,就像内存申请失败,也无需多做处理,所以就没有捕获异常,只是调试的时候才发现。
在Linux系统上,TLS默认支持1024个,这个数量已经相当大,我们在Linux上面也没有失败,因此最初的测试也没有发现这个问题。
但是在IBM的AIX系统上面,这个值默认是450,而且不是系统参数,不能调整大小。这样我们就只能查出来到底是哪里用了这么多的pthread_key
。
查找原因
pthread_key_create实现方法
众所周知,TLS在Linux系统上的实现,是用类似于数组的方法,用户申请时,找到一个空闲位置,记录用户指定的析构函数,并将此位置标记。AIX系统的做法是类似的,这可以通过反汇编pthread_key_create
验证。
了解了pthread_key_create
的实现方式,那就很容易查问题了。
找出使用pthread_key_create
的模块
找到一个TLS变量析构函数的函数地址
对于我们自己的模块,可以通过dbx(AIX上类似于gdb的调试工具)attach到进程,找到我们自己的析构函数(我们自己调用的pthread_key_create
并不是全部失败的),记录下函数地址。查找TLS
AIX下可以使用gencore pid core-file
来生成一个core文件。用16进制工具打开core文件,根据记录的函数地址查找,找到TLS变量存放的区域。在这里可以找到很多其他使用线程变量的析构函数。幸运的是,确实有很多变量使用了相同的析构函数。
查找泄露的模块
很幸运的,我们找到了泄露TLS变量的析构函数,准确的说应该是存放析构函数地址的地址值。拿到了这个地址值,就可以用dbx找到析构函数的地址。
(dbx) p *(long long *)0x09001000a8114840
648518347166419584
转换成16进制就是0x09000000312D8A80,抱歉太傻,当时没有直接打印16进制数据。不过我们还是拿到了函数的地址,现在只需要找到这个地址是哪个模块的就可以了。dbx提供了一个很强大的命令map
,他可以根据指定的地址,找出来所属的模块,代码段信息和数据段信息。
(dbx) map verbose 0x9000000312D8A80
Entry 66:
Object name: /data01/usergrp/billmdb/lib/libpublic_common_baseD.so
Text origin: 0x900000031004000
Text end: 0x90000003136ea69
Text length: 0x36aa69
Data origin: 0x9001000a80deb38
Data end: 0x9001000a8199300
Data length: 0xba7c8
File descriptor: 0x84
这样就一目了然了,我们在public_common_base
模块的代码中搜索了一下pthread_key_create
,找到了下面的代码:
template<typename T>
class CPThreadKeyHolder {
public:
//default construtor
CPThreadKeyHolder() : m_pthread_key_t()
{
pthread_key_create(&m_pthread_key_t,DestructThreadObject);
};
// ...
};
罪魁祸首原来就在这里。在模板类中使用pthread_key_create
,那么每个使用这个模板的类都会创建一个甚至多个(内联会自动展开)TLS变量。我们在core文件中打印了一下DestructThreadObject
函数的地址,也确实对应了上面在core中找到的地址。
问题已经找到了,至于解决方案,方法很多,最简单的就是让所有的模板类共用同一个TLS变量。
TIPS
AIX默认使用partialcore模式,仅仅dump进程的出现问题的线程堆栈信息。但是查问题时很多时候需要查看全局变量或者TLS变量,因此需要开启fullcore模式。AIX还支持fullcore时不dump共享内存,具体的方法可以man core
。
- 查看是否fullcore开启
root%lsattr -El sys0 | grep fullcore
fullcore true Enable full CORE dump True
- 开启fullcore模式
chdev -l sys0 -a fullcore=true
- 手动coredump进程
gencore ProcessID FileName