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 “2Fwww.laruence.com%2Ftag%2F%25e6%25ad%25a3%25e5%2588%2599%27+class%3D%27tag-link-191%27+title%3D%273+topics%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://www.laruence.com/tag/json' class='tag-link-79′ title='3 topics' style='font-size: 9.0243902439pt;'>json</a>\n<a href='http://www.laruence.com/tag/module' class='tag-link-43′ title='2 topics' “…, 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_common_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 = 0x1fddcc8, last = 28, size = 28, vars = 0x1fd3cc0, 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_comment = 0x0,
doc_comment_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 “2Fwww.laruence.com%2Ftag%2F%25e6%25ad%25a3%25e5%2588%2599%27+class%3D%27tag-link-191%27+title%3D%273+topics%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, 于是网上找了一下发现了这个Pierrick-Charron的commit,确实是一个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下面,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. 具体没有细看,读者可以参考下上面的这个提交改动或者直接看自己的版本代码是否有问题。

(0)

相关推荐

  • 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

  • nginx服务器异常502 bad gateway原因排查

    服务器进行公众号粉丝数据同步以及批量推送报错502 根据错误信息可以判定是后端的问题,502错误的原因有很多种,但总的来说就是服务器处理不过来了 1.首先查看服务器日志 1)先查nginx日志,不熟悉的可以从nginx.conf中获取error_log的路径,找到错误如下: 发现nginx进程处理的连接数不够用,单个进程处理的连接数超过了nginx.conf配置的worker_connections值 通常worker_connections的值可以参考单个进程打开的最大连接数,命令为:ulim

  • PHP5.4内置web服务器

    PHP是一种脚本语言,它需要PHP解释器来分析运行PHP文件.当把PHP做为CGI服务Web请求时,它需要被嵌入到某种Web服务器里,最常见的是集成到Apache或IIS里,这就是说,在使用PHP前,你需要安装Apache或IIS,并且正确的配置它们和PHP集成的参数.虽然这种配置已经很规范,文档非常丰富,但我们还是经常在安装Apache和PHP集成时遇到问题,而且,有时候我们只想测试一个简单的PHP特征,不想就为此安装.启动Apache服务. 但据官方文档上说,这个内置的Web服务器只是提供开

  • 单台服务器的PHP进程之间实现共享内存的方法

    开发人员要想使php进程实现共享内存的读写,首先就要支持IPC函数,即php编译安装时指定:--enable-shmop  与--enable-sysvsem 两个选项. IPC (Inter-process communication) 是一个Unix标准机制,它提供了使得在同一台主机不同进程之间可以互相的方法.基本的IPC处理机制有3种:它们分别是共享内存.信号量和消息队列.本文中我们主要讨论共享内存和信号量的使用. 在不同的处理进程之间使用共享内存是一个实现不同进程之间相互的好方法.如果你

  • php使用curl详细解析及问题汇总

    七夕啦,作为开发,妹子没得撩就"撩"下服务器吧,妹子有得撩的同学那就左拥妹子右抱服务器吧,况且妹子是要礼物的,服务器又不用.好啦,长话短说再长说,祭出今天的工具--CURL(Client URL Library),当然今天以PHP的方式来使用这件工具. 0. curl是个什么东西 复制代码 代码如下: PHP supports libcurl, a library created by Daniel Stenberg, that allows you to connect and co

  • 使IIS支持PHP,ISAPI或CGI,FastCGI完全配置教程(最新php5.2.13配置方法)

    在Windows Server 2003的IIS6下配置ISAPI方式的PHP,配置方法是,在IIS的"WEB服务扩展"中,添加一个新的WEB服务扩展,程序后缀为PHP,ISAPI程序为php5isapi.dll,然后再我的电脑->属性->高级->"环境变量"-"系统变量"中增加变量名PHPRC,数值为php.ini的路径,在Internet信息服务管理器中,选择网站或应用程序的根目录,打开目录属性页(右键选择"属性&

  • PHP7基于curl实现的上传图片功能

    本文实例讲述了PHP7基于curl实现的上传图片功能.分享给大家供大家参考,具体如下: 根据php版本不同,curl模拟表单上传的方法不同 php5.5之前 $curl = curl_init(); if (defined('CURLOPT_SAFE_UPLOAD')) { curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false); } $data = array('file' => '@' . realpath($path));//'@' 符号告诉服务器为上

  • 安装apache2.2.22配置php5.4(具体操作步骤)

    修改 apache2.2/httpd.conf 配置文件,让apache能够解析php文件 #修改监听端口Listen 8011 #在LoadModule的最后一段后面添加下面一句话LoadModule php5_module "E:/LAMP/php5.4/php5apache2_2.dll" #修改服务器网站目录为 E:/LAMP/wwwDocumentRoot "E:/LAMP/www"#同时还有一个地方要改<Directory "E:/LAM

  • win2003 iis6服务器设置排错集锦[比较全]

    问题1:未启用父路径 症状举例:Server.MapPath() 错误 'ASP 0175 : 80004005'不允许的 Path 字符 /0709/dqyllhsub/news/OpenDatabase.asp,行 4在 MapPath 的 Path 参数中不允许字符 '..'. 原因分析:许多Web页面里要用到诸如../格式的语句(即回到上一层的页面,也就是父路径),而IIS6.0出于安全考虑,这一选项默认是关闭的. 解决方法:在IIS中属性->主目录->配置->选项中.把&quo

  • 服务器大量php-cgi.exe进程导致CPU占用100%的解决方法

    windows 2003+IIS6中优化fastcgi配置文件fcgiext.ini,减少php-cgi.exe进程数量和所占内存大小 本来听说fastcgi比isapi好就在服务器中装上了,配置环境为windows 2003+IIS6+fastcgi(FCGI)+PHP5.2.17,经过与很长一段时间观察,发现工作在FastCGI模式下的PHP会占用越来越多的内存,访问量稍微多点php-cgi进程就多了N个,同样情况下能比原来用isapi模式的时候多出几百M,我的服务器内存只有2G伤不起啊.

随机推荐