msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

21 Jan 14 Curl的毫秒超时的一个”Bug”

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

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

原来, 这里面有一个坑, CURL默认的, 在Linux系统上, 如果使用了系统标准的DNS解析, 则会使用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以后, 一切终于正常了!

后记:

这样一来, 就会有一个隐患, 那就是DNS解析将不受超时限制了, 这在于公司内部来说, 一般没什么问题, 但是万一DNS服务器hang住了, 那就可能会造成应用超时.

那么还有其他办法么?

有, 那就是Mike提醒的, 我们可以让libcurl使用c-ares(C library for asynchronous DNS requests)来做名字解析. 具体的可以在config curl的时候:

./configure --enable-ares[=PATH]

这样就可以不用设置NOSIGNAL了 :)

PS, 为什么冠以”Bug”, 我只是好奇, 他们为什么不用setitimer?

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


分享到:



Related Posts:

Tags: , , ,

35 Responses to “Curl的毫秒超时的一个”Bug””

  1. 张蒙 |

    嘿嘿之前也发现这个问题了,同样的解决方法

  2. Anglea |

    I read a lot of interesting content here. Probably you
    spend a lot of time writing, i know how to save you a lot of work,
    there is an online tool that creates readable, google friendly posts in minutes, just search
    in google – laranitas free content source

  3. Erna |

    I see a lot of interesting articles on your page. You have to spend a lot
    of time writing, i know how to save you a lot of time, there is a
    tool that creates unique, SEO friendly articles in couple
    of minutes, just type in google – laranita’s free content source

  4. marker |

    前几天刚遇到这个问题,忽略掉了,几天看到,获益良多

  5. 儿童玩具 |

    先前一直没发现有这个问题。今天才明白原来是这样。

  6. 天气预报 |

    膜拜

  7. 小小吴 |

    支持一下….

  8. maque |

    博客勤更新哦

  9. shenguanpu |

    设置了CURLOPT_NOSIGNAL 也没有效果啊 curl版本 7.31.0
    超时设置到0.5 结果就没有超时限制了
    鸟哥使用的是curl_multi_exec去处理并发吗? 是否会有性能问题呢?

  10. 都市网达 |

    这个问题,还不能捕获异常,烦死人了,后来换java了。

  11. 雪候鸟 |

    @tang 用github主干的代码, 这个在windows上的bug已经fixed了

  12. tang |

    忘记说了,刚才说的那个错误是指用鸟哥的yar时,客户端的方法调用出现的。

  13. tang |

    鸟哥,你好,我的是IIS上运行的,发现curl exec failed ‘Timeout was reached’这样的错误也较为常见.
    已经调用SetOpt(YAR_OPT_CONNECT_TIMEOUT, 5000);
    SetOpt(YAR_OPT_TIMEOUT, 1000*60*5);
    按理说我们是服务器直连,速度还可以,服务器的远程方法5分钟足够了的。请鸟哥指点?

  14. 无敌WEB |

    学习了,研究得真深入啊

  15. luffy |

    按照鸟哥的做法,经过多轮测试,似乎毫秒的依然无法生效,求解

  16. 一个堕落的程序员 |

    If you want cURL to timeout in less than one second, you can use CURLOPT_TIMEOUT_MS, although there is a bug/”feature” on “Unix-like systems” that causes libcurl to timeout immediately if the value is < 1000 ms with the error "cURL Error (28): Timeout was reached". The explanation for this behavior is:

    "If libcurl is built to use the standard system name resolver, that portion of the transfer will still use full-second resolution for timeouts with a minimum timeout allowed of one second."

    What this means to PHP developers is "You can use this function without testing it first, because you can't tell if libcurl is using the standard system name resolver (but you can be pretty sure it is)"

    The problem is that on (Li|U)nix, when libcurl uses the standard name resolver, a SIGALRM is raised during name resolution which libcurl thinks is the timeout alarm.

    The solution is to disable signals using CURLOPT_NOSIGNAL. Here's an example script that requests itself causing a 10-second delay so you can test timeouts:

    0) {
    echo “cURL Error ($curl_errno): $curl_error\n”;
    } else {
    echo “Data received: $data\n”;
    }
    } else {
    // Server
    sleep(10);
    echo “Done.”;
    }
    ?>

  17. James Tang |

    我们现在调用外链是采用的这种方式支持毫秒级超时,遇到的问题是(极少)部分请求时间远大于设置的超时时间:如设置100ms但有时请求时间能高达5000ms.我说的“请求时间”是指开始调用curl到curl执行完成的时间,所以这种>100ms的时间会不会是DNS解析消耗的时间?

  18. 雪候鸟 |

    @Mike thanks, i will update my post about it :)

  19. wiwi |

    准确的说,这样就没有域名解析超时了吧?
    这样设的毫秒超时还有用吗?

    PS:在c语言等多线程中使用CURL时,也必须设CURLOPT_NOSIGNAL为1,不然会报错

  20. wiwi |

    但这样就没用到超时了吧?

  21. 九明 |

    前排占座学习~

  22. xuanskyer |

    好久没看到鸟哥更新了啊!

  23. 傲雪星枫 |

    學習,CURLOPT_NOSIGNAL是启用时忽略所有的curl传递给php进行的信号。

  24. 23lalala |

    学习了 还以为高版本的curl毫秒超时就没问题了。

  25. Lynn |

    真心是个坑

  26. Mike |

    Sub-second timeout only works with c-ares support:
    http://m6w6.blogspot.co.at/2008/12/peclhttp-and-sub-second-timeouts_05.html

  27. rest |

    前排占座

  28. sky |

    这个仅仅是忽略timeout,鸟哥就是为了普及这个bug呗

  29. andyhua |

    if(true) 哈哈 ~~~

  30. 起司猫 |

    学习了!

  31. 惊奇 |

    前排占座 点我名字有惊奇

  32. sssskccc |

    前排广告位出租

  33. 莱登堡 |

    CURLOPT_NOSIGNAL这个参数原来这么有用,以前还没关注过!

  34. 花生2 |

    占位了

  35. 花生 |

    前排占座

Leave a Reply

*