最近我們的服務在升級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
作者: Laruence
本文地址: http://www.laruence.com/2014/01/21/2939.html