glibc getaddrinfo BUG导致Python脚本卡死

上一篇博客还是2014年的,2015年赶紧写一篇

遇到一个Python脚本卡死,是运行了大约9小时的。几乎每隔一个星期就会卡死一次,加上sigalarm handler也无法kill掉自身,sigalarm handler没有触发。

gdb上卡死的进程,发觉线程卡在sem_wait,查看所有线程

(gdb) info threads
 Id Target Id Frame
 11 Thread 0x7f734fd5b700 (LWP 13356) "python" 0x00007f7351c2cd8d in recvmsg () at ../sysdeps/unix/syscall-template.S:82
 10 Thread 0x7f734f55a700 (LWP 13357) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 9 Thread 0x7f734ed59700 (LWP 13358) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 8 Thread 0x7f734e558700 (LWP 13359) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 7 Thread 0x7f734dd57700 (LWP 13360) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 6 Thread 0x7f734d556700 (LWP 13361) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 5 Thread 0x7f734cd55700 (LWP 13362) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 4 Thread 0x7f734c554700 (LWP 13363) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 3 Thread 0x7f734bd53700 (LWP 13364) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 2 Thread 0x7f734b552700 (LWP 13365) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
 * 1 Thread 0x7f7352ba7700 (LWP 13349) "python" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86

看来线程11拿住sem,其他线程都在等待。看下线程11的栈

(gdb) bt
 #0 0x00007f7351c2cd8d in recvmsg () at ../sysdeps/unix/syscall-template.S:82
 #1 0x00007f7351c4d58c in make_request (fd=8, pid=13349, seen_ipv4=, seen_ipv6=, in6ai=, in6ailen=) at ../sysdeps/unix/sysv/linux/check_pf.c:119
 #2 0x00007f7351c4da0a in __check_pf (seen_ipv4=0x7f734fd5768f, seen_ipv6=0x7f734fd5768e, in6ai=0x7f734fd57670, in6ailen=0x7f734fd57668) at ../sysdeps/unix/sysv/linux/check_pf.c:271
 #3 0x00007f7351c0a4d7 in *__GI_getaddrinfo (name=0xda0490b4 "reg.163.com", service=0x7f734fd57730 "80", hints=0x7f734fd57750, pai=0x7f734fd57700) at ../sysdeps/posix/getaddrinfo.c:2386
 #4 0x0000000000527b94 in PyEval_SetProfile (func=0xb00000000, arg=) at ../Python/ceval.c:3752

recvmsg卡住。strace/lsof这个进程,发现recvmsg的fd是一个netlink socket,对应ROUTE

Google下,发现是glibc的一个BUG:https://sourceware.org/bugzilla/show_bug.cgi?id=12926,要到2.23才修复。

而生产机是debian 7,glibc还是2.13,暂时不想折腾升级glibc,弱弱地写了个监控脚本监控卡死进程,发现就kill掉……


广州路况监控地图

广州交警出了个广州出行易App,可以看市内多个监控的实时画面。
周末写了下在百度地图上显示这些监控的位置和图片

链接:http://www.lzhaohao.info/xxt


放个《高级Bash脚本编程指南》的HTML版

HTML版链接:http://www.lzhaohao.info/abs/


使用cffi和re2实现部分Python内置re模块功能(兼容pypy)

从一些benchmark可以看出,Python内置的re模块比起re2是慢很多的。而Google的re2(https://code.google.com/p/re2/)是一个速度快的C++正则库。是否可以在Python内利用re2呢?有一些人已经做了相关的工作:facebook/pyre2 axiak/pyre2,但这些都是采用CPython扩展/Cython的方式编写,在pypy下不能正常使用。如何既能得到pypy的速度,也能利用re2?首先想到了CPython和pypy都内置的ctypes模块,可以用来访问C编写的so库。

但从实际测试结果来看,在pypy下使用ctypes是比较慢的,有人也报了BUG(https://bugs.pypy.org/issue1237),但仍未解决。测试数据如下:

还有另外一个第三方库:cffi(http://cffi.readthedocs.org/),可以实现类似ctypes的访问C库并调用其函数的功能。跟ctypes一样,cffi也只能访问C的so,无法直接访问C++的so,因此我们要为re2包装一层,暴露C接口。具体代码在github。从上面的测试可以看出,cffi_re2在某些正则下是原生re模块的速度的50倍,但有些正则却会比原生的慢……

使用了我们生产的真实数据和真实正则测试,测试代码:https://gist.github.com/vls/7539716(涉及公司数据,这里无法给出测试数据),在CPython和pypy下都能取得一些性能上的提升:)


Python循环中的local变量绑定问题

考虑以下代码

func_list = []

def foo(d):
    print 'num', num
    _num = num
    return d['level'] > _num

def get_f(num):
    _num = num
    def bar(d):
        print 'num', _num
        return d['level'] > _num
    return bar

for num in (30, 60):
    level_func = foo
    #level_func = get_f(num)
    func_list.append(level_func)


d = {'level': 40}

for f in func_list:
    print f(d)

当level_func是foo时,两次调用都会输出num=60,而level_func = get_f(num),才会输出期望的num=30, num=60。

原因是循环中level_func都绑定了同一个local变量num,而循环结束后,变量num的值为60,所以输出都是60。而get_f里面定义了_num=num,bar函数的_num是指向get_f里面所定义的_num,这是因为Python里def定义函数创造了一个新的variable scope;循环两次,get_f里形成闭包,保存了循环当时的值。

但每次都要定义一个辅助函数(上例的get_f)总觉得麻烦,如何解决?其实可以利用Python的一个“坑”
代码:

level_func = lambda d, num=num: return d['level'] > num

原理就是利用Python函数默认参数是在定义时绑定。

具体的差别可以用过dis模块来打印函数的bytecode查看。


pypy 2.0在CentOS 5.x 编译问题小结

pypy 2.0发布了,又要编译了。环境仍然是CentOS 5.x 64bit。

问题:tmp目录空间不够
解决:调用的是python里面的tempfile来获取临时目录的。所以定义环境变量TMP或者TEMP或者TMPDIR都可以。

问题:default value for ‘w_ignored1′ can only be None, got ”; (详细信息:http://bpaste.net/show/OUN1VScbqnuujKEkGHJ8/
解决:irc上说用新版本pypy或cpython来执行。我用的是pypy 1.9也不行,就用了cpython 2.6.7,通过。

问题:CentOS自带SQLite版本过低
解决:安装更新版本的SQLite再编译。


MySQL 5.6新特性测试

在微博上看到隔壁部门的同事发布了一个介绍MySQL 5.6新特性的slide。遂对其中几个对我们业务有用的特性做了测试。

Found a slide which introducing new features in MySQL 5.6, which is made by a colleague in other department. For our business, I ran some tests on some features.

  • InnoDB Compression
//压缩前(before compress)
Name: workticket
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 1471830
Avg_row_length: 328
Data_length: 483295232
Max_data_length: 0
Index_length: 260276224
Data_free: 5242880
Auto_increment: 3473533
Create_time: 2013-04-15 16:01:01
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options: 
Comment: 工单表
//压缩后(after compress)
Name: workticket
Engine: InnoDB
Version: 10
Row_format: Compressed
Rows: 1553544
Avg_row_length: 143
Data_length: 222281728
Max_data_length: 0
Index_length: 121192448
Data_free: 2621440
Auto_increment: 3473533
Create_time: 2013-04-15 16:06:33
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options: row_format=COMPRESSED
Comment: 工单表

但还是不够myisampack后的myisam省空间

But InnoDB Compression is less storage efficient than MyISAM after myisampack.

-rw-rw---- 1 mysql mysql 9.5K Apr 16 09:35 workticket.frm
-rw-rw---- 1 mysql mysql 9.5K Apr 16 09:45 workticket_innodb.frm
-rw-rw---- 1 mysql mysql 336M Apr 16 09:58 workticket_innodb.ibd
-rw-rw---- 1 mysql mysql 199M Apr 16 09:41 workticket.MYD
-rw-rw---- 1 mysql mysql  86M Apr 16 09:44 workticket.MYI
           Name: workticket
         Engine: MyISAM
        Version: 10
     Row_format: Dynamic
           Rows: 1593851
 Avg_row_length: 201
    Data_length: 321325744
Max_data_length: 281474976710655
   Index_length: 130242560
      Data_free: 0
 Auto_increment: 3473533
    Create_time: 2013-04-16 09:35:53
    Update_time: 2013-04-16 09:41:09
     Check_time: 2013-04-16 09:37:03
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: 
        Comment: 工单表
  • InnoDB Online DDL
mysql> ALTER TABLE `workticket` ADD COLUMN `test_col` INT NOT NULL AFTER sid, LOCK=NONE;
Query OK, 0 rows affected (5 min 25.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> set old_alter_table=1;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE `workticket` ADD COLUMN `test_col` INT NOT NULL AFTER sid, LOCK=NONE; 
ERROR 1846 (0A000): LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED.
mysql> ALTER TABLE `workticket` ADD COLUMN `test_col` INT NOT NULL AFTER sid, LOCK=SHARED;
Query OK, 1593851 rows affected (4 min 11.89 sec)
Records: 1593851  Duplicates: 0  Warnings: 0

虽然总体耗时更长,但DDL过程中不锁表(可query and/or 可DML)还是很吸引的(详细的DDL操作是否可query and/or 可DML可参见文档)。

Although costing more time, that the table can be query and/or execute DML query is very amazing. (Detail can be seen in documentation)

  • InnoDB-Transportable Tablespaces
    没什么可测试的,就是操作成功。用途?建slave?备份?
    Nothing interesting to test. It just works. What can I do with this feature? Maybe building up a slave server? Or backup up the database?

pypy 1.9+mysql_ctypes 0.5+libmysqlclient15 got Segmentation fault on CentOS 5.8 64bit

在一台CentOS 5.8 64bit的机器上使用pypy 1.9, mysql_ctypes 0.5查询数据库出现Segmentation fault。gdb了一下,堆栈都被破坏了。只好另觅他法。

When I use pypy 1.9, mysql_ctypes 0.5, I got a Segmentation fault querying MySQL database. First I try to gdb it, the stack is messed up. Have to find another way out.

想起有另一台机器使用同样程序与库一直正常,一番查找发现差别只在于libmysqlclient.so的版本。正常的机器有多个版本,最高版本是16;而出错的机器最高的是15。将libmysqlclient.so.16复制到出错机器,发现查询正常了。为了库的完整,找了一个libmysqlclient16的rpm安装。

Then I remember that there is another machine running the same version of program and library. Finally I found that the only difference between these two machines is the version of libmysqlclient.so. In the normal machine, there are multiple versions, the largest version is 16. And in the problem machine the largest version is 15. Then I try to copy the libmysqlclient.so.16 to the problem machine, everything is OK. For competence, I found a libmysqlclient16 rpm and install it.

问题解决。

Problem solved.


《游戏反垃圾聊天系统》分享

这是近日做的一个内部分享

html5 slide:

游戏反垃圾聊天系统

slideshare:

游戏反垃圾聊天系统


Nginx中HttpLimitReqModule的burst参数略解

Nginx有个HttpLimitReqModule,可以限制请求的频率,其中有个参数burst在文档http://wiki.nginx.org/HttpLimitReqModule不甚详尽,故读其代码了解之。

源码在src/http/modules/ngx\_http\_limit\_req\_module.c,关于burst的核心代码在这。

//src/http/modules/ngx_http_limit_req_module.c:396
ms = (ngx_msec_int_t) (now - lr->last);

excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;

if (excess < 0) {
    excess = 0;
}

*ep = excess;

if ((ngx_uint_t) excess > limit->burst) {
    return NGX_BUSY;
}

if (account) {
    lr->excess = excess;
    lr->last = now;
    return NGX_OK;
}

excess初始值是0,假设现在ctx->rate是2000(即2 request/s),这次请求距离上次请求是400ms。

那么excess = 0 – 2000 * 400 / 1000 + 1000 = 200。如果limit->burst是0,那么200 > 0,会返回NGX_BUSY即是503了。

假如burst是1,limit->burst即是1000,那么如果请求是每隔400ms来一个,共需5个才会填满limit->burst(每个请求将会增加200 excess),到第6个才会返回503。

推导出公式,假设设置频率是r request/s,每次请求距离上次请求t ms,设置burst为b,那么返回503的临界请求个数x是
\begin{equation}
x = floor(b * \frac {( 1000 / r )} {( 1000 / r – t )})
\end{equation}