PHP中curl设置毫秒级超时的问题

 

TL;DR

PHP5/7加上7.19的libcurl,设置低于1s的超时时间时,curl_exec仍会执行超过1s以上。原因在于此版本的libcurl实现逻辑上以1000ms作为curl_execpoll系统调用的超时值。

问题

某些HTTP接口响应时间可能因为种种原因会引起响应时间延长的问题,在这种情况下,从性能、体验角度考虑,设置调用方的超时时间成为必要。

手册上得知:

选项 设置value为 备注
CURLOPT_TIMEOUT_MS 设置cURL允许执行的最长毫秒数。 如果 libcurl 编译时使用系统标准的名称解析器( standard system name resolver),那部分的连接仍旧使用以秒计的超时解决方案,最小超时时间还是一秒钟。 在 cURL 7.16.2 中被加入。从 PHP 5.2.3 起可使用。

在符合版本条件的服务器上用PHP内置的HTTP服务器模拟一个时长超过1s的接口:

通过如下代码访问这一接口:

通过如下命令,设定了200ms的超时时间,同时还有2s的接口延迟响应时间。

预料中,客户端应该会在200ms左右的时间结束程序,然而实际执行输出让人感到困惑:

客户端运行了1000ms+的时间。

分析

查询手册

从PHP手册中查询到的结果,字面意思并没有太多的歧义,使用方法上也并没有明显错误,下面可以从运行时的状态开始分析。

Strace跟踪运行过程

通过strace查看运行过程中的系统调用:

为了方便分析,将输出记录到curl_strace.log文件之中,并记录各个系统调用产生时的时间戳。

关键的网络请求部分系统调用记录如下:

fd=3的文件描述符即用于处理网络请求。在上述步骤的第10行发出请求之后,调用了poll系统调用来检验fd是否可以读写。查阅手册得知,poll系统调用的声明为:

第三个参数即以毫秒为单位的等待超时时间。

请注意16行的系统poll调用,超时时间足足设定了1000ms,之后程序执行结束退出,那么这个1000ms的超时设定,应该就是程序没有在200ms左右的时间退出的主要原因。

PHP curl扩展

PHP中的curl函数在curl的扩展中定义,如果怀疑对运行的方式产生了怀疑,可以查看扩展源码。

在PHP源码的ext/curl目录下的interface.c文件中,可以看到curl_exec方法的定义:

可以看到,实际上PHP中的curl_exec调用的是libcurl中的curl_easy_perform这一函数执行的网络请求。

对于超时的问题,需要深入到libcurl之中了。

libcurl 7.19

通过curl --version命令可以获取cUrl的版本,之后在GitHub是哪个clone源码repo,checkout对应版本(7.19.x)即可。

可以看到curl_easy_perform在libcurl源码目录中lib/easy.c中被定义了。主要执行步骤如下:

1.执行Curl_perform函数

2.在Curl_perform中完成连接(connect_host函数)与传输数据操作(Transfer函数)

3.在Transfer中对第一次poll操作设定0ms的超时,之后均设定为1000ms的超时,参见代码:

4.Curl_socket_ready方法中会根据传入的超时时间即变量timeout_ms的值设定poll系统调用的超时值,结合strace日志,即1116行的poll操作。

5.第一次超时为0ms时,poll会立刻返回,显然没有socket可以读写,会走入switch语句的case 0的分支,调用Curl_readwrite函数;在第二次以及之后的执行中,超时会恒定在1000ms,在可读写之后,会走入switch语句的default的分支,也会调用Curl_readwrite函数。

6.在Curl_readwrite函数中会再次调用Curl_socket_ready方法,但是这次传入的超时时间则固定为0ms了:

这里的Curl_socket_ready就是strace日志中1217行两次poll操作记录的来源。

7.在Curl_readwrite中会判断当前已执行时间是否已经超过了设定的超时时间:

如果已超过则,返回已超时。

小结

综上,老版本curl设定了ms级别超时但是仍然会执行s级别的问题在于curl的poll超时设定被固定在1000ms之上,造成了这一问题。

解决

可以考虑升级libcurl,或者在编译的PHP中使用高版本的libcurl。

以上。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">