首页 > 编程 > PHP > 正文

php脚本运行时的超时机制详解

2020-03-22 19:09:41
字体:
来源:转载
供稿:网友
在做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对应的函数:html' target='_blank'>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的超时。同样也是采取调用zend_set_timeout,并传入max_execution_time。特别注意一下,windows下面的需要显式调用zend_unset_timeout关闭原来的定时器,而linux下不需要。这是由于两个平台的定时器实现原理不同导致的,下文也会详细展开叙述。最后用一张图表示超时控制的流程,左侧的case表明用户既配置了max_input_time,又配置了max_execution_time。而右侧的区别在于用户仅仅配置了max_execution_time:zend_set_timeout
前文提到,zend_set_timeout函数用来设置定时器。具体来看下实现:void zend_set_timeout(long seconds, int reset_signals) /* {{{ */ TSRMLS_FETCH(); // 赋值 EG(timeout_seconds) = seconds;#ifdef ZEND_WIN32 if(!seconds) { return; // 启动定时器线程 if (timeout_thread_initialized == 0 && InterlockedIncrement(&timeout_thread_initialized) == 1) { /* We start up this process-wide thread here and not in zend_startup(), because if Zend * is initialized inside a DllMain(), you're not supposed to start threads from it. zend_init_timeout_thread(); // 向线程发送WM_REGISTER_ZEND_TIMEOUT消息 PostThreadMessage(timeout_thread_id, WM_REGISTER_ZEND_TIMEOUT, (WPARAM) GetCurrentThreadId(), (LPARAM) seconds);#else // linux平台下 struct itimerval t_r; /* timeout requested */ int signo; if (seconds) { t_r.it_value.tv_sec = seconds; t_r.it_value.tv_usec = t_r.it_interval.tv_sec = t_r.it_interval.tv_usec = 0; // 设置定时器,seconds秒后会发送SIGPROF信号 setitimer(ITIMER_PROF, &t_r, NULL); signo = SIGPROF; if (reset_signals) { sigset_t sigset; // 设置SIGPROF信号对应的处理函数为zend_timeout signal(signo, zend_timeout); // 防屏蔽 sigemptyset(&sigset); sigaddset(&sigset, signo); sigprocmask(SIG_UNBLOCK, &sigset, NULL);#endif

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表