IT技术博客大学习 共学习 共进步

Curl的毫秒超时的一个”Bug”

风雪之隅 2014-11-23 21:42:10 浏览 2,405 次

 最近我们的服务在升级php使用的libcurl, 期望新版本的libcurl支持毫秒级的超时, 从而可以更加精细的控制后端的接口超时, 从而提高整体响应时间.

  但是, 我们却发现, 在我们的CentOS服务器上, 当你设置了小于1000ms的超时以后, curl不会发起任何请求, 而直接返回超时错误(Timeout reached 28).

  原来, 这里面有一个坑,  CURL默认的, 在Linux系统上, 使用SIGALARM来提供控制域名解析超时的功能, 但是SIGALARM不支持小于1s的超时, 于是在libcurl 7.28.1的代码中(注意中文注释行):

int Curl_resolv_timeout(struct connectdata *conn,
                        const char *hostname,
                        int port,
                        struct Curl_dns_entry **entry,
                        long timeoutms)
{
.......
.......
#ifdef USE_ALARM_TIMEOUT
  if(data->set.no_signal)
    /* Ignore the timeout when signals are disabled */
    timeout = 0;
  else
    timeout = timeoutms;

  if(!timeout)
    /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
    return Curl_resolv(conn, hostname, port, entry);

  if(timeout < 1000) //如果小于1000, 直接超时返回
    /* The alarm() function only provides integer second resolution, so if
       we want to wait less than one second we must bail out already now. */
    return CURLRESOLV_TIMEDOUT;  

  ....
  ....

  可见, 当你的超时时间小于1000ms的时候, name解析会直接返回CURLRESOLV_TIMEOUT, 最后会导致CURLE_OPERATION_TIMEDOUT, 然后就Error, Timeout reached了…

  这….太坑爹了吧? 难道说, 我们就不能使用毫秒超时么? 那你提供这功能干啥?

  还是看代码, 还是刚才那段代码, 注意这个(中文注释行):

#ifdef USE_ALARM_TIMEOUT
  if(data->set.no_signal)  //注意这行
    /* Ignore the timeout when signals are disabled */
    timeout = 0;
  else
    timeout = timeoutms;

  if(!timeout)
    /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
    return Curl_resolv(conn, hostname, port, entry);

  if(timeout < 1000)
    /* The alarm() function only provides integer second resolution, so if
       we want to wait less than one second we must bail out already now. */
    return CURLRESOLV_TIMEDOUT;

  看起来, 只要set.no_signal 这个东西为1, 就可以绕过了… 那这个玩意是啥呢?

这就简单了, grep一下代码, 发现:

  case CURLOPT_NOSIGNAL:
    /*
     * The application asks not to set any signal() or alarm() handlers,
     * even when using a timeout.
     */
    data->set.no_signal = (0 != va_arg(param, long))?TRUE:FALSE;
    break;

  哈哈, 原来是这货:

<?php
   curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
?>

 加上这个OPT以后, 一切终于正常了!

 最后, 这个我想是Curl的一个bug吧, 只不过我们没想明白他们为啥不用setitimer…

 参考:  http://stackoverflow.com/questions/7987584/curl-timeout-less-than-1000ms-always-fails

建议继续学习

  1. Rolling cURL: PHP并发最佳实践 (阅读 11,364)
  2. curl 命令使用cookie (阅读 9,846)
  3. curl检查访问网页返回的状态码 (阅读 7,703)
  4. ssh连接超时解决办法 (阅读 5,507)
  5. PHP用CURL伪造IP和来源 (阅读 5,307)
  6. curl测试下载速度 (阅读 5,184)
  7. cURL基础教程 (阅读 4,925)
  8. php socket为什么这么慢,直到超时 (阅读 4,926)
  9. curl快速实现网速测试 (阅读 4,704)
  10. javascript 在各个浏览器中的超时时间 (阅读 4,663)