Python multi thread "error: longjmp causes uninitialized stack frame"

Python 使用pycurl 在多线程下crash的问题

问题背景:

在工作中需要使用pycurl 在多线程情况下对server发送请求。发现py程序跑一会就回出现程序crash,console报错如下:

longjmp causes uninitialized stack frame
#0  0x00ad1424 in __kernel_vsyscall ()
#1  0x008a7d31 in raise () from /lib/libc.so.6
#2  0x008a960a in abort () from /lib/libc.so.6
#3  0x008e5d5d in __libc_message () from /lib/libc.so.6
#4  0x009752ed in __fortify_fail () from /lib/libc.so.6
#5  0x0097525a in ____longjmp_chk () from /lib/libc.so.6
#6  0x009751c9 in __longjmp_chk () from /lib/libc.so.6
#7  0x0027e0b9 in ?? () from /usr/lib/libcurl.so.4
#8  <signal handler called>
#9  0x00ad1424 in __kernel_vsyscall ()
#10 0x0094aa5b in read () from /lib/libc.so.6
#11 0x008e7aab in _IO_new_file_underflow () from /lib/libc.so.6
#12 0x008e97cb in _IO_default_uflow_internal () from /lib/libc.so.6
#13 0x008eadda in __uflow () from /lib/libc.so.6
#14 0x008dd83c in _IO_getline_info_internal () from /lib/libc.so.6
#15 0x008dd781 in _IO_getline_internal () from /lib/libc.so.6
#16 0x008dc6ba in fgets () from /lib/libc.so.6
#17 0x00430401 in fgets (buf=0x9c4a168 "8T\240", len=100, fp=0xa04440) at /usr/include/bits/stdio2.h:255
#18 my_fgets (buf=0x9c4a168 "8T\240", len=100, fp=0xa04440) at Parser/myreadline.c:47
..........

解决步骤

google 相关crash 信息

首先Google了一下 “longjmp causes uninitialized stack frame”, lucky 从stackoverflow上面找到了如下信息:

There is a problem with the way libcurl currently handles the SIGALRM signal. It installs a handler for SIGALRM to force a synchronous DNS resolve to time out after a specified time, which is the only way to abort such a resolve in some cases. Just before the the DNS resolve takes place it initializes a longjmp pointer so when the signal comes in the signal handler just does a siglongjmp, control continues from that saved location and the function returns an error code.
The problem is that all the following control flow executes effectively inside the signal handler. Not only is there a risk that libcurl could call an async handler unsafe function (see signal(7)) during this time, but it could call a user callback function that could call absolutely anything. In fact, siglongjmp() itself is not on the POSIX list of async-safe functions, and that’s all the libcurl signal handler calls!

从描述来看是libcurl 对于时钟信号(SIGALRM)处理有问题。在处理DNS解析请求的时候,在某些情况下只有一种方法来对请求做超时,就是在DNS请求开始之前初始化一个longjmp 指针,当信号量来了之后程序继续从调用之前的位置继续执行。
问题在于这个控制流在信号处理函数里work没有问题。问题在于如果libcurl有可能会在这段时间内部调用非线程安全的异步处理函数,或者用户回掉函数(可以调用anything)。其实siglongjmp() 本身就不是线程安全函数。

上面说了一大堆,简而言之就是libcurl非线程安全,多线程情况下会有crash危险。

解决步骤

根据stackoverflow 方法

老外还是很靠谱的,说了不行之后还给出了解决方法:
1, 重编libcurl,使用异步的c-area来做域名解析
2,在调用libcurl 之前将signal关掉:curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1)
看了看,我用pycurl额,咋关?
所以义无反顾的走第一条路,下载libcurl/c-area 包,然后再编译替换现有版本。
吭哧吭哧忙半天之后,发现还是crash。

使用GDB调试该python程序

尝试生成coredump文件。 因为问题能稳定重现,所以首先通过命令:ulimit -c 如果输出为 0 ,则代表没有打开; 则执行命令: ulimit -c unlimited。如果为unlimited则已经打开了,就没必要再做打开。

然后再编辑vi /etc/abrt/abrt.conf 将 ProcessUnpackaged = no -> yes

这样的话就可以在/var/spool/abrt 目录下面生成coredump 文件。

但是在程序crash之后,使用gdb python coredump
执行bt后发现没有什么重要的信息。

使用py-bt 调试Python程序

随后我想是否可以直接对Python程序进行调试?

发现了下面的文章,DebuggingWithGDB,需要安装python-debuginfo 这个rpm包。需要注意的是这个包需要连小版本号都要相同。

安装完之后,通过gdb python < PID >的方式在线调试Python程序,但是发现py-bt 命令仍然没有用。随后我就参考这个文章,注意自己Python的版本号,执行python -V 可知。
需要在/usr/lib/debug/usr/bin 目录下面建立一个python2.6-gdb.py文件用于gdb的python调试。
故执行:

 find / -name *gdb.py
 ls -l /usr/lib/debug/usr/bin
 ln -s /usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py /usr/lib/debug/usr/bin/python2.6-gdb.py

再次执行发现成功。

当程序再次crash时。可以看到很细节的crash信息。
但是从bt的信息来看,六个线程所谓的DNS超时之类的行为。

最后通过禁用python的信号量解决问题

没辙,只能直接禁用python的信号量,确定是在libcurl里面的问题,所以我在每次发起libcurl请求之前,做一次curl.setopt(pycurl.NOSIGNAL, 1)
多线程再也没有crash现象。

文章仅仅作为这次debug的纪录。重要的是在这个过程中学到的方法以便以后更深入的学习

你可能感兴趣的:(Python,Linux)