现象

在系统更新以后,相应的php也升级到了新的版本.在运行了自己的代码之后,发现程序直接卡死不动了.在经过一阵排查之后,发现其卡在了非常诡异的地方

 //use select to get response
 //proceed select until all handle response
 //can refer php.net page, curl_multi_select() api
 while ($this->active && $mrc == CURLM_OK) 
 {   
     if (curl_multi_select($this->mh) != -1) 
     {   
         do {
             $mrc = curl_multi_exec($this->mh, $this->active);
             if ($mrc == CURLM_OK)
             {   
                 while($info = curl_multi_info_read($this->mh))
                 {   
                     $this->process($info);
                 }        
             }   
         } while ($mrc == CURLM_CALL_MULTI_PERFORM);
     }   
 }

另外可以注意到,CPU的使用率一直100%,说明我们的程序卡死了.

而经过一阵debug,发现程序运行到curl_multi_select之后,一直返回-1,然后就不断进入循环,卡死了.

原因探究

原来在10.8.5的时候,代码是能运行的,而且在linux中运行也很正常,因此,结果之后可能是php或者是相应依赖随着系统变化引起的问题.观察命名也能知道curl_multi_select其背后是基于libcurl进行实现的.而两个系统的libcurl版本也确实不同.

通过查看curl_multi_exec,有个用户的回复引起了注意.

Alex Palmer 10 months ago
On php 5.3.18+ be aware that curl_multi_select() may return -1 forever until you call curl_multi_exec(). See https://bugs.php.net/bug.php?id=63411 for more information.

查看bug列表,发现了原作者的回答:

[2012-11-03 03:42 UTC] pierrick@php.net
I’m not sure we really want to wait 1 second for nothing in this specific case. Furthermore, as mentioned in my commit message, when libcurl returns -1 in max_fd after calling curl_multi_fdset, it is because libcurl currently does something that isn’t possible for your application to monitor with a socket and unfortunately you can then not know exactly when the current action is completed using select().
I would personally keep the current behaviour.

所以显然是curl_multi_select一直返回了-1,导致了程序进入了死循环,卡死掉了.而这个是进入了php5.3.8以后,做出的改变.

正确的curl_multi_select写法

那在这样的behaviour下,该如何编写curl_multi_select呢.

经过一番研究,终于找到了合适的写法.关键在于在while循环中要执行curl_multi_exec,直到处理完毕再进入select.

实现代码:

    while ($this->active && $mrc == CURLM_OK) 
    {   
        while (curl_multi_exec($this->mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
       if (curl_multi_select($this->mh) != -1) 
       {   
           do {
               $mrc = curl_multi_exec($this->mh, $this->active);
               if ($mrc == CURLM_OK)
               {   
                   while($info = curl_multi_info_read($this->mh))
                   {   
                       $this->process($info);
                   }        
               }   
           } while ($mrc == CURLM_CALL_MULTI_PERFORM);
       }   
    } 

影响的php版本

这个我没有深入探究,理论上这个是和curl以及php版本有关系.

从其他使用者反馈的有:

  • 5.4.7
  • 5.4.8
  • 5.3.17
  • 5.4.7