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

最近咱们的服务在升级php使用的libcurl, 指望新版本的libcurl支持毫秒级的超时, 从而能够更加精细的控制后端的接口超时, 从而提升总体响应时间.后端

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

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

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了…less

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

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

#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, 就能够绕过了… 那这个玩意是啥呢?ide

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

 
  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的时候: