Home > fundamental > 多线程python的cx_Oracle模块segment fault问题解决一例

多线程python的cx_Oracle模块segment fault问题解决一例

之前用python写了一个自动failover的工具,主要的功能是在主库不可用(如网络 down、主机 down、db down等)时(1分钟内),能够自动触发推送连接池相关的配置,让应用能够切到failover的一个db上去,从而降低系统的不可用时间。一些流水性质的系统特别适合自动failover的设计,比如交易、付款、消息等,特别是在对可用率要求比较高的情况下。

跑题了。。主要讲下这么多天一路走来,解决这个问题的跌宕起伏的心路历程:

先废话下这个工具的大体实现原理:一个主线程autofo.py,里面包含了多个主库环境(通过配置文件配置),这些库的共同点是他们的failover库都指向同一个db(没有必要给每个主库配置各不相同的db做failover库)。autofo.py对每个主库配置起了3个线程,分别做主库的心跳检测、failover库的心跳检测、线程hung住的资源回收,所以配置2套主库的话,就有6个线程,其中2个检测failover库的线程会定期去更新同一个failover库的心跳表。

上线后问题就来了,autofo.py会不定期的crash(1day~5day),系统日志报segment fault,并且内存增长比较快。
因为不是一般的exception,而且又是不定期的,没法debug,就尝试开启linux的dump crash core功能,然后用gdb什么的分析下,但是发现程序crash后仍不会生产core文件,跟c程序不一样啊你妹。

只好开启strace跟踪进程,看crash的瞬间是什么情况。
strace.log:

3739  07:29:04 <... write resumed> )    = 10 <0.000086>
3744  07:29:04 <... read resumed> "", 4096) = 0 <0.000023>
3740  07:29:04 <... setsockopt resumed> ) = 0 <0.000076>
3744  07:29:04 close(13 
3739  07:29:04 close(7 
3744  07:29:04 <... close resumed> )    = 0 <0.000021>
3740  07:29:04 fcntl(14, F_SETFD, FD_CLOEXEC 
3744  07:29:04 munmap(0x2aaaaab5b000, 4096 
3739  07:29:04 <... close resumed> )    = 0 <0.000073>
3744  07:29:04 <... munmap resumed> )   = 0 <0.000023>
3740  07:29:04 <... fcntl resumed> )    = 0 <0.000152>
3744  07:29:04 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP 
3739  07:29:04 futex(0x3bde3529e0, FUTEX_WAKE_PRIVATE, 1 
3422  07:29:04 <... select resumed> )   = 0 (Timeout) <0.004059>
3744  07:29:04 <... socket resumed> )   = 7 <0.000051>
3740  07:29:04 --- SIGSEGV (Segmentation fault) @ 0 (0) ---
3739  07:29:04 <... futex resumed> )    = 0 <0.022031>
3744  07:29:04 +++ killed by SIGSEGV +++
3739  07:29:04 +++ killed by SIGSEGV +++
3511  07:29:04 +++ killed by SIGSEGV +++

都是系统调用级的,完全没法跟代码行关联起来,最靠近的几个调用就是fcntl,socket,futex,select,怀疑跟文件句柄的获取有关(fcntl,futex),程序里是多个线程打印同一个logfile,难道logging模块不是线程安全的?但是翻翻doc,人家说的很清楚:

The logging module is intended to be thread-safe without any special work needing to be done by its clients. It achieves this though using threading locks; there is one lock to serialize access to the module’s shared data, and each handler also creates a lock to serialize access to its underlying I/O.

http://docs.python.org/3/library/logging.html?highlight=logging#logging

重新review代码后无果。
很不爽了已经,打算讲线程拆开一个个测试,因为线下环境有限,就将主库跟fail库配置成同一个物理库,程序起来一会就报了如下错误:

*** glibc detected ***corrupted double-linked list: 0x0000000000a0ab90 ***

以及:

ORA-24550: signal received: [si_signo=11] [si_errno=0] [si_code=2] [si_addr=0000000000000000]

然后crash,终于有迹可寻了,心里一阵窃喜一阵紧张,难道这就是全文的高潮了??
是的,google到了一篇类似案例:

http://comments.gmane.org/gmane.comp.python.db.cx-oracle/2678

原来cx_Oracle.connect()默认不是线程安全的!加上threaded参数后才是。

cx_Oracle.connect(,threaded=True)

这一点doc上也说的比较明确:

The threaded argument is expected to be a boolean expression which indicates whether or not Oracle should use the mode OCI_THREADED to wrap accesses to connections with a mutex. Doing so in single threaded applications imposes a performance penalty of about 10-15% which is why the default is False.

http://cx-oracle.sourceforge.net/html/module.html

本质上,cx_Oracle调用的还是OCI的接口,而OCI的多线程问题可以参考这里:

http://oracle.chinaitlab.com/backup/3052.html

调整完后,程序很稳定。
至此,1月份终于稍微可以好过点了。

Categories: fundamental Tags:
  1. No comments yet.