php5.2的curl-bug 服务器被php进程卡死问题排查
前几天东政同学反馈说Linode服务器快卡死了,今天有时间排查了一下具体原因,最终原因稍微有点悲壮file_get_contents没有设置超时时间,加上我用的php5.2关于curl的代码有个bug,于是导致PHP进程进入死循环。
今天下午又发现系统负载很高,于是上去看了一下,发现一大坨PHP进程没有退出,占用了很多CPU,如图
问题进程
后面运行的脚本是我的RSS定时更新任务,看来PHP代码什么地方有问题,于是strace -p 14043看了一下
select(5, [4], [4], [], {15, 0}) = 1 (out [4], left {14, 999996}) poll([{fd=4, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout) clock_gettime(CLOCK_MONOTONIC, {4582888, 760370017}) = 0 clock_gettime(CLOCK_MONOTONIC, {4582888, 760468615}) = 0 clock_gettime(CLOCK_MONOTONIC, {4582888, 760565053}) = 0 select(5, [4], [4], [], {15, 0}) = 1 (out [4], left {14, 999997})
在4号fd上面死循环了,于是看看FD是什么ll /proc/14043/fd
lrwx—— 1 wuhaiwen wuhaiwen 64 7月 21 11:00 4 -> socket:[53176380]
再看了一下原来是在请求CSDN的一个网页的时候死循环了,但不知道什么地方请求的,想到GDB一下php进程看看,bt显示
(gdb) bt
#0 0x00007f6721f8f013 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:82
#1 0×0000000000481952 in php_curl_stream_read (stream=0×2280650,
buf=0x22ea5d0 “2F.laruence.%2Ftag%2F%25e6%25ad%25a3%25e5%2588%2599%27+class%3D%27tag-link-191%27+title%3D%273+ics%27+style%3D%27font-size%3A+9.0243902439pt%3B%27%3E%E6%AD%A3%E5%88%99%3C%2Fa%3E%3C%2Ftags%3E\”"…, count=8192) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/ext/curl/streams.c:169
#2 0x00000000006738f9 in php_stream_fill_read_buffer (stream=0×2280650, size=4283) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/main/streams/streams.c:554
#3 0x0000000000673c39 in _php_stream_read (stream=0×2280650,
buf=0x2301fd5 “f='http://.laruence./tag/json' class='tag-link-79′ title='3 ics' style='font-size: 9.0243902439pt;'>json</a>\n<a href='http://.laruence./tag/module' class='tag-link-43′ title='2 ics' “…, size=4283) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/main/streams/streams.c:600
#4 0x0000000000674c51 in _php_stream_copy_to_mem (src=0×2280650, buf=0x7fff376ed898, maxlen=<optimized out>, persistent=0)
at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/main/streams/streams.c:1267
#5 0x00000000005fdb85 in zif_file_get_contents (ht=<optimized out>, return_value=0x2223da0, return_value_ptr=<optimized out>, this_ptr=<optimized out>, return_value_used=<optimized out>)
at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/ext/standard/file.c:565
#6 0x00000000006c2a59 in zend_do_fcall_mon_helper_SPEC (execute_data=0x7fff376edc60) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/Zend/zend_vm_execute.h:200
#7 0x00000000006c239f in execute (op_array=0x1f26730) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/Zend/zend_vm_execute.h:92
·············
#16 0x0000000000730d8e in main (argc=4, argv=0x7fff376f2468) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/sapi/cli/php_cli.c:1133
看一下当前PHP执行的脚步是什么
(gdb) p op_array $4 = {type = 2 '\002', function_name = 0x1e54278 "getContent", scope = 0x1f8e850, fn_flags = 257, prototype = 0x0, num_args = 2, required_num_args = 1, arg_info = 0x1fd5e20, pass_rest_by_reference = 0 '\000', return_reference = 0 '\000', refcount = 0x1fd3ab8, opcodes = 0x1fdd8, last = 28, size = 28, vars = 0x1fd30, last_var = 6, size_var = 16, T = 15, brk_cont_array = 0x0, last_brk_cont = 0, current_brk_cont = 4294967295, try_catch_array = 0x0, last_try_catch = 0, static_variables = 0x0, start_op = 0x0, backpatch_count = 0, done_pass_two = 1 '\001', uses_this = 0 '\000', filename = 0x1fd3b58 "/home/wuhaiwen/webroot/kulvrss/libs/Myrss/Model/UrlContenter.php", line_start = 9, line_end = 30, doc_ment = 0x0, doc_ment_len = 0, reserved = {0x0, 0x0, 0x0, 0x0}}
找到了问题代码位置,原来是一个file_get_contents($url)调用,没有设置超时时间,于是PHP卡死在网络请求了。于是用stream_context_create 设置超时时间搞定。
到这里 似乎问题解决了,,为什么没有设置超时时间就导致php进程占用CPU,系统负载那么高?按理说应该等待I/O才是呀?看上面CPU情况,完全是进入了死循环的节奏。
根据上面的bt堆栈,看倒数第二个函数的调用
#1 0×0000000000481952 in php_curl_stream_read (stream=0×2280650,
buf=0x22ea5d0 “2F.laruence.%2Ftag%2F%25e6%25ad%25a3%25e5%2588%2599%27+class%3D%27tag-link-191%27+title%3D%273+ics%27+style%3D%27font-size%3A+9.0243902439pt%3B%27%3E%E6%AD%A3%E5%88%99%3C%2Fa%3E%3C%2Ftags%3E\”"…, count=8192) at /home/wuhaiwen/install/php-env/src/php/php-5.2.8/ext/curl/streams.c:169
看一下代码,我用的事5.2.8版本的PHP,比较老。代码如下
static size_t php_curl_stream_read(php_stream stream, char buf, size_t count TSRMLS_DC) { php_curl_stream curlstream = (php_curl_stream ) stream->abstract; size_t didread = 0; if (curlstream->readbuffer.readpos >= curlstream->readbuffer.writepos && curlstream->pending) { //········ do { / get the descriptors from curl / curl_multi_fdset(curlstream->multi, &curlstream->readfds, &curlstream->writefds, &curlstream->excfds, &curlstream->maxfd); / if we are in blocking mode, set a timeout / tv.tv_usec = 0; tv.tv_sec = 15; / TODO: allow this to be configured from the script / / wait for data / switch (select(curlstream->maxfd + 1, &curlstream->readfds, &curlstream->writefds, &curlstream->excfds, &tv)) { case -1: / error / return 0; case 0: / no data yet: timed-out / return 0; default: / fetch the data / do { curlstream->mcode = curl_multi_perform(curlstream->multi, &curlstream->pending); } while (curlstream->mcode == CURLM_CALL_MULTI_PERFORM); } } while (curlstream->readbuffer.readpos >= curlstream->readbuffer.writepos && curlstream->pending > 0); } //·········· return didread; }
GDB进去发现,代码一直在里面的do-while里面循环了!心想curl_multi_fdset怎么不用先FD_ZERO 清空FD呢?一般做法都是会先清空的。
莫非是PHP的bug, 于是网上找了一下发现了这个,确实是一个bug, 其实curl_multi_fdset 的文档开头写了的
This function extracts file descriptor information from a given multi_handle. libcurl returns its fd_set sets. The application can use these to select() on, but be sure to FD_ZERO them before calling this function as curl_multi_fdset(3) only adds its own descriptors,
好吧,用GDB验证一下,我在上面的do狼蚁网站SEO优化,curl_multi_fdset调用之前,手动将fd清空,看看能否退出循环
(gdb) print FD_ZERO(&curlstream->readfds)
No symbol “FD_ZERO” in current context.
FD_ZERO竟然没有,不管了,其本来是个宏定义,展开就行#define FD_ZERO(p) bzero((char )(p), sizeof((p)))
直接用call修改curl_muti_fdset的三个参数数组如下
(gdb) call bzero((char )(&curlstream->readfds), sizeof((&curlstream->readfds)))
$5 = 17055392
(gdb) call bzero((char )(&curlstream->writefds),sizeof((&curlstream->writefds)))
$6 = 17055520
(gdb) call bzero((char )(&curlstream->excfds), sizeof((&curlstream->excfds)))
$7 = 17055648
然后GDB单步执行,如期的由于curlstream->pending变为0,从而退出了循环,回到php_stream_fill_read_buffer的大函数了
到此基本结束。有问题的PHP版本应该是5.2. 具体没有细看,读者可以参考下上面的这个提交改动或者直接看自己的版本代码是否有问题。
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程