- 本文地址: https://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
bug
This is amazing.
我现在在最新版的guzzle中发起请求时,至少5s;
将guzzle请求的option中增加
‘curl’ => [
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4
]
反应就非常快了。和此文中的问题是2种不同的情况吗?
这一句要添加到所有文件里面么
我用的wordpress还是添加到head就可以了?
我说我设置总超时时间是3s,结果DNS解析耗了10几秒,都没超时,我还以为dns的耗时需要另外设置,原来是之前的代码设置了CURLOPT_NOSIGNAL这个玩意,导致dns解析不受超时限制了。。这个太蛋疼了
搜答案搜到鸟哥这里来了,我就说CURL怎么老是不支持1S以下超时,并发量一大了有地方卡住超蛋疼
[…] 本文地址: http://www.laruence.com/2014/01/21/2939.html […]
[…] PS:如果想要設定超時時間少於1秒,需要用到CURLOPT_TIMEOUT_MS,但此配置據鳥哥說有bug,未測試,留個心眼:《Curl的毫秒超時的一個”Bug”》 目錄 […]
[…] 問題是,我們這些介面的域名都是配置在hosts檔案裡的,沒有道理會解析超時,然後有同事貼出鳥哥的一篇文章,Curl的毫秒超時的一個”Bug”,懷疑是libcurl庫的bug,然後下載libcurl的原始碼看了半天,並沒有找到什麼奇怪的地方。 […]
setitimer也是alarm机制
我就在想一个问题,有没有谁能开发个系统,是中文做的代码,让我们不懂英文的也能进入这一行业http://www.zwdeng.cn/news.html/
易语言了解下
来了来了
17年底遇到这个问题月余,今天静心搜寻答案,原来14年laruence就发现了并撰写了原委,感慨自己真是个懒惰的程序员啊。
真心是个坑,今天踩到了,搞了半天都没发现它是哪里的问题,最后到鸟哥这里找到答案了,感谢
使用yar ,windows环境,客户端调用出现 curl exec failed ‘Timeout was reached’,鸟哥,请赐教
[…] 其中CURLOPT_NOSIGNAL这个设置是为了消除curl毫秒级的bug。这个bug的详细信息在鸟哥的博客里也有提到:Curl的毫秒超时的一个”Bug” […]
If you are interested in topic: can you make money creating apps
– you should read about Bucksflooder first
是的,这个bug,在项目中,我也遇到过!
[…] :http://www.laruence.com/2014/01/21/2939.html […]
我也遇到这个问题, centos php curl
嘿嘿之前也发现这个问题了,同样的解决方法
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
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
前几天刚遇到这个问题,忽略掉了,几天看到,获益良多
先前一直没发现有这个问题。今天才明白原来是这样。
膜拜
支持一下….
博客勤更新哦
设置了CURLOPT_NOSIGNAL 也没有效果啊 curl版本 7.31.0
超时设置到0.5 结果就没有超时限制了
鸟哥使用的是curl_multi_exec去处理并发吗? 是否会有性能问题呢?
这个问题,还不能捕获异常,烦死人了,后来换java了。
@tang 用github主干的代码, 这个在windows上的bug已经fixed了
忘记说了,刚才说的那个错误是指用鸟哥的yar时,客户端的方法调用出现的。
鸟哥,你好,我的是IIS上运行的,发现curl exec failed ‘Timeout was reached’这样的错误也较为常见.
已经调用SetOpt(YAR_OPT_CONNECT_TIMEOUT, 5000);
SetOpt(YAR_OPT_TIMEOUT, 1000*60*5);
按理说我们是服务器直连,速度还可以,服务器的远程方法5分钟足够了的。请鸟哥指点?
学习了,研究得真深入啊
按照鸟哥的做法,经过多轮测试,似乎毫秒的依然无法生效,求解
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.”;
}
?>
我们现在调用外链是采用的这种方式支持毫秒级超时,遇到的问题是(极少)部分请求时间远大于设置的超时时间:如设置100ms但有时请求时间能高达5000ms.我说的“请求时间”是指开始调用curl到curl执行完成的时间,所以这种>100ms的时间会不会是DNS解析消耗的时间?
@Mike thanks, i will update my post about it 🙂
准确的说,这样就没有域名解析超时了吧?
这样设的毫秒超时还有用吗?
PS:在c语言等多线程中使用CURL时,也必须设CURLOPT_NOSIGNAL为1,不然会报错
但这样就没用到超时了吧?
前排占座学习~
好久没看到鸟哥更新了啊!
學習,CURLOPT_NOSIGNAL是启用时忽略所有的curl传递给php进行的信号。
学习了 还以为高版本的curl毫秒超时就没问题了。
真心是个坑
Sub-second timeout only works with c-ares support:
http://m6w6.blogspot.co.at/2008/12/peclhttp-and-sub-second-timeouts_05.html
前排占座
这个仅仅是忽略timeout,鸟哥就是为了普及这个bug呗
if(true) 哈哈 ~~~
学习了!
前排占座 点我名字有惊奇
前排广告位出租
CURLOPT_NOSIGNAL这个参数原来这么有用,以前还没关注过!
占位了
前排占座