Recently, our service is upgrading the libcurl used by PHP. We expect that the new version of libcurl will support millisecond timeouts, so that the back-end interface timeout can be controlled more finely, thereby improving the overall response time.
However, we found that on our CentOS server, when you set a timeout less than 1000ms, curl will not initiate any request, but will directly return a timeout error (Timeout reached 28).
It turns out that there is a pit in this. CURL defaults. On Linux systems, if the system standard DNS resolution is used, SIGALARM will be used to provide the function of controlling the domain name resolution timeout, but SIGALARM does not support a timeout less than 1s, so in In the code of libcurl 7.28.1 (note the Chinese comment line):
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) // If it is less than 1000, return directly after timeout /* 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; .... ....
It can be seen that when your timeout time is less than 1000ms, the name resolution will directly return CURLRESOLV_TIMEOUT, which will eventually lead to CURLE_OPERATION_TIMEDOUT, and then Error, Timeout reached…
This… is too cheating? Could it be that we can’t use millisecond timeout? Then why do you provide this function?
Still look at the code, or the code just now, pay attention to this:
#ifdef USE_ALARM_TIMEOUT if(data->set.no_signal) // Pay attention to this line /* 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;
It seems that as long as set.no_signal is 1, it can be bypassed… So what is this thing?
This is simple, grep the code and found:
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;
Haha, it turned out to be this product:
<?php curl_setopt($ch, CURLOPT_NOSIGNAL, 1); ?>
After adding this OPT, everything is finally normal!
postscript:
In this way, there will be a hidden danger, that is, DNS resolution will not be restricted by timeout, which is internal to the company, generally there is no problem, but if the DNS server hangs, it may cause application timeout.
So is there any other way?
Yes, that’s what Mike reminded, we can let libcurl use c-ares (C library for asynchronous DNS requests) for name resolution. The specifics can be in config curl:
./configure --enable-ares[=PATH]
This way you don’t need to set NOSIGNAL ?
PS, why is it called “Bug”, I’m just curious, why don’t they use setitimer?
Reference: http://stackoverflow.com/questions/7987584/curl-timeout-less-than-1000ms-always-fails