剖析PHP脚本的超时机制

在做php开发的时候,经常会设置max_input_time、max_execution_time,用来控制脚本的超时时间。但却从来没有思考过背后的原理。

趁着这两天有空,研究一下这个问题。

超时配置

php的ini配置如何起作用,这是一个老生常谈的话题了。

首先,我们在php.ini里进行配置。当php启动的时候(php_module_startup阶段),会尝试读取ini文件并解析。解析过程简单来说,是分析ini文件,提取出其中合法的键值对,并保存到configuration_hash表。

OK,然后php会进一步调用zend_startup_extensions来启动各个模块(包含php Core模块,以及所有需要加载的扩展)。各个模块的启动函数中,会完成REGISTER_INI_ENTRIES动作。REGISTER_INI_ENTRIES负责将模块对应的一些配置从configuration_hash表取出,然后调用处理函数,最终将处理完的值存入模块的globals变量。

max_input_time、max_execution_time这两个配置属于php Core模块。对于php Core来说,REGISTER_INI_ENTRIES依然发生在php_module_startup中。同样属于php Core模块的配置还有expose_php、display_errors、memory_limit等等...

示意图如下:

---->php_module_startup----------->php_request_startup---->
        |
        |
        |-->REGISTER_INI_ENTRIES
        |
        |
        |-->zend_startup_extensions
        |          |
        |          |-->zm_startup_date
        |          |        |-->REGISTER_INI_ENTRIES
        |          |
        |          |-->zm_startup_json
        |          |        |-->REGISTER_INI_ENTRIES
        |
        |
        |-->do otherthings

上面说到对于不同的配置,REGISTER_INI_ENTRIES会调用不同的函数来处理。我们直接来看max_execution_time对应的函数:

static PHP_INI_MH(OnUpdateTimeout)

{

// php启动阶段走这里

if (stage == PHP_INI_STAGE_STARTUP) {

// 将超时设置保存到EG(timeout_seconds)中

EG(timeout_seconds) = atoi(new_value);

return SUCCESS;

}

// php执行过程中的ini set则走这里

zend_unset_timeout(TSRMLS_C);

EG(timeout_seconds) = atoi(new_value);

zend_set_timeout(EG(timeout_seconds), 0);

return SUCCESS;

}


暂时只看上半截,因为我们目前只需关注php的启动阶段,该函数行为很简单,将max_execution_time存入了EG(timeout_seconds)。

至于max_input_time,并没有特殊的处理函数,默认是会将max_input_time存入存入PG(max_input_time)。

因此,当REGISTER_INI_ENTRIES完成,发生的是:

max_execution_time ----> 存入EG(timeout_seconds)

max_input_time      ----> 存入PG(max_input_time)

请求超时控制

现在我们搞清楚php的启动阶段发生了什么,继续来看php在实际处理请求的时候,如何管理超时。

在php_request_startup函数中有如下代码:

if (PG(max_input_time) == -1) {

zend_set_timeout(EG(timeout_seconds), 1);

} else {

zend_set_timeout(PG(max_input_time), 1);

}

php_request_startup的时机很讲究。

以cgi为例,只有当php已经从CGI拿到了原始请求以及一些CGI的环境变量之后,php_request_startup才会被调用。上面这段代码实际执行的时候,由于请求已经拿到,所以SG(request_info)处于准备就绪状态,但是php中的$_GET,$_POST,$_FILE等超全局变量尚未生成。

从代码上理解:

1、如果用户将max_input_time配做-1,或没有配置,那么脚本的生命周期就只受EG(timeout_seconds)约束。

2、否则,请求启动阶段的超时控制,受PG(max_input_time)约束。

3、zend_set_timeout函数负责设置定时器。一旦指定时间过去,定时器会通知php进程。zend_set_timeout下文会具体分析。

php_request_startup完成,则进入php的实际执行阶段,即php_execute_script。在php_execute_script中可以看到:

// 设定执行超时

if (PG(max_input_time) != -1) {

#ifdef PHP_WIN32

zend_unset_timeout(TSRMLS_C); // 关闭之前的定时器

#endif

zend_set_timeout(INI_INT("max_execution_time"), 0);

}

// 进入执行

retval = (zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, NULL, 3, prepend_file_p, primary_file, append_file_p) == SUCCESS);


OK,假如代码执行到这里,尚未发生max_input_time超时,则会重新指定max_execution_time的超时。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/d96cc7904de123940760e51fb4c3a79a.html