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

作者: Laruence( ) 本文地址: http://www.laruence.com/2014/01/21/2939.html 转载请注明出处

最近我们的服务在升级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

Comments

2014/01/21, 花生 writes: 前排占座2014/01/21, 花生2 writes: 占位了2014/01/21, 莱登堡 writes: CURLOPT_NOSIGNAL这个参数原来这么有用,以前还没关注过!2014/01/21, sssskccc writes: 前排广告位出租2014/01/21, 惊奇 writes: 前排占座 点我名字有惊奇2014/01/21, 起司猫 writes: 学习了!2014/01/21, andyhua writes: if(true) 哈哈 ~~~2014/01/21, sky writes: 这个仅仅是忽略timeout,鸟哥就是为了普及这个bug呗2014/01/21, rest writes: 前排占座2014/01/21, Mike writes: Sub-second timeout only works with c-ares support: http://m6w6.blogspot.co.at/2008/12/peclhttp-and-sub-second-timeouts_05.html2014/01/21, Lynn writes: 真心是个坑2014/01/21, 23lalala writes: 学习了 还以为高版本的curl毫秒超时就没问题了。2014/01/21, 傲雪星枫 writes: 學習,CURLOPT_NOSIGNAL是启用时忽略所有的curl传递给php进行的信号。2014/01/21, xuanskyer writes: 好久没看到鸟哥更新了啊!2014/01/21, 九明 writes: 前排占座学习~2014/01/22, wiwi writes: 但这样就没用到超时了吧?2014/01/22, wiwi writes: 准确的说,这样就没有域名解析超时了吧? 这样设的毫秒超时还有用吗? PS:在c语言等多线程中使用CURL时,也必须设CURLOPT_NOSIGNAL为1,不然会报错2014/01/22, 雪候鸟 writes: @Mike thanks, i will update my post about it :)2014/01/23, James Tang writes: 我们现在调用外链是采用的这种方式支持毫秒级超时,遇到的问题是(极少)部分请求时间远大于设置的超时时间:如设置100ms但有时请求时间能高达5000ms.我说的“请求时间”是指开始调用curl到curl执行完成的时间,所以这种>100ms的时间会不会是DNS解析消耗的时间?2014/01/30, 一个堕落的程序员 writes: 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."; } ?>2014/02/04, luffy writes: 按照鸟哥的做法,经过多轮测试,似乎毫秒的依然无法生效,求解2014/02/13, 无敌WEB writes: 学习了,研究得真深入啊2014/03/02, tang writes: 鸟哥,你好,我的是IIS上运行的,发现curl exec failed 'Timeout was reached'这样的错误也较为常见. 已经调用SetOpt(YAR_OPT_CONNECT_TIMEOUT, 5000); SetOpt(YAR_OPT_TIMEOUT, 1000*60*5); 按理说我们是服务器直连,速度还可以,服务器的远程方法5分钟足够了的。请鸟哥指点?2014/03/02, tang writes: 忘记说了,刚才说的那个错误是指用鸟哥的yar时,客户端的方法调用出现的。2014/03/04, 雪候鸟 writes: @tang 用github主干的代码, 这个在windows上的bug已经fixed了2014/03/11, 都市网达 writes: 这个问题,还不能捕获异常,烦死人了,后来换java了。2014/04/10, shenguanpu writes: 设置了CURLOPT_NOSIGNAL 也没有效果啊 curl版本 7.31.0 超时设置到0.5 结果就没有超时限制了 鸟哥使用的是curl_multi_exec去处理并发吗? 是否会有性能问题呢?2014/04/18, maque writes: 博客勤更新哦2014/04/29, 小小吴 writes: 支持一下....2014/05/07, 天气预报 writes: 膜拜2014/05/17, 儿童玩具 writes: 先前一直没发现有这个问题。今天才明白原来是这样。2014/06/17, marker writes: 前几天刚遇到这个问题,忽略掉了,几天看到,获益良多2014/07/04, Erna writes: 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 source2014/08/28, Anglea writes: 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 source2014/09/06, 张蒙 writes: 嘿嘿之前也发现这个问题了,同样的解决方法2015/07/22, wusuopubupt writes: 我也遇到这个问题, centos php curl2015/08/19, Curl的毫秒超时的一个”Bug” | Andy Luo's Blog writes: [...] :http://www.laruence.com/2014/01/21/2939.html [...]2016/07/14, rednife writes: 是的,这个bug,在项目中,我也遇到过!2016/09/01, Joleen writes: If you are interested in topic: can you make money creating apps - you should read about Bucksflooder first2017/01/14, php实现异步处理 – CzRzChao writes: [...] 其中CURLOPT_NOSIGNAL这个设置是为了消除curl毫秒级的bug。这个bug的详细信息在鸟哥的博客里也有提到:Curl的毫秒超时的一个”Bug” [...]2017/08/08, jscklulu writes: 使用yar ,windows环境,客户端调用出现 curl exec failed 'Timeout was reached',鸟哥,请赐教2017/09/07, 无名 writes: 真心是个坑,今天踩到了,搞了半天都没发现它是哪里的问题,最后到鸟哥这里找到答案了,感谢2017/12/12, 三月荒川 writes: 17年底遇到这个问题月余,今天静心搜寻答案,原来14年laruence就发现了并撰写了原委,感慨自己真是个懒惰的程序员啊。

Related posts:

memory_limit的一个bugPHP Reflection Extension的一个bugPHP类型转换相关的一个BugPHP5.2.x + APC的一个bug的定位shell下发推脚本Copyright © 2010 风雪之隅 版权所有, 转载务必注明. 该Feed只供个人使用, 禁止未注明的转载或商业应用. 非法应用的, 一切法律后果自负. 如有问题, 可发E-mail至my at laruence.com.(Digital Fingerprint: 73540ba0a1738d7d07d4b6038d5615e2)

Related Posts:

Expect:100-continueHTTPOXY漏洞说明让你的PHP7更快(GCC PGO)在PHP中使用协程实现多任务调度GCC优化引起的一个”问题”Weibo LAMP演变 – 6月在上海分享的PPT一个小玩意PHP-Valgrind的介绍PHP浮点数的一个常见问题的解答Yaf and Phalcon, which is faster?Yar – 并行的RPC框架(Concurrent RPC framework)

文章来源:

Author:Laruence
link:http://www.laruence.com/2014/01/21/2939.html