标签归档:timeout

PHP脚本运行超时管理机制

PHP脚本运行超时管理机制

在我们平常的开发中,也许曾经都遇到过PHP脚本运行超时的情况,此时PHP会显示错误说: “Fatal error: Maximum execution time of XXX seconds exceeded in XXX”,并终止脚本的运行。当遇到这种情况我们经常会通过使用 set_time_limit(非安全模式),或修改配置文件并重启服务器,或者修改程序减少程序的执行时间,使其在允许的范围之内,以解决此问题。但是,这些都是在应用层上我们可以看到的的表象,在PHP内核中有一套这样的机制支撑这样一个表象。

这是PHP为防止某些业务脚本长时间执行而阻塞其它脚本的处理或耗尽服务器资源,从而实现的脚本运行的超时管理机制。其本质上是PHP通过针对不同的平台实现定时器,依赖运行时的超时全局变量(EG(timeout_seconds))管理并控制定时器的运行。所有对脚本运行时长的管理,包括接口函数和配置文件对于最大运行时长的配置,最终都是通过管理超时全局变量并重启定时器来实现的。

初始化和超时配置项

在PHP内核的核心层文件/main/main.c文件中,定义了PHP的核心配置项以及每个配置项对应的on_modify方法。在模块初始化(php_module_startup)时,PHP内核会调用ini配置的注册函数,将定义的核心配置项添加到ini配置的指令集中,并且会调用每个配置项对应的on_modify方法。

用于定义脚本运行最长时间的max_execution_time配置项也是这些核心配置项的一员,它的默认值为30秒,对应的on_modify方法是OnUpdateTimeout。当注册这些核心配置项时,max_execution_time的on_modify方法将被调用,此时配置项的值将传递给超时全局变量:EG(timeout_seconds),并通过zend_set_timeout方法启动定时器。

针对WIN平台和类unix平台,PHP内核实现了不同的定时器。 Win32平台的定时器是在WM_TIME的基础上封装了一个计时器。通过创建一个独立线程控制计时器,并创建一个消息环,WaitForSingleObject用来阻塞zend_init_timeout_thread 返回。当接收到WM_REGISTER_ZEND_TIMEOUT时开始计时,实际上此时计时的任务是SetTimer(timeout_window, wParam, lParam*1000, NULL); 系统会在 seconds * 1000 后发个 WM_TIMER,这个时候就结束计时,中间可以被 WM_UNREGISTER_ZEND_TIMEOUT 打断。

类unix平台使用Linux的API函数setitimer,指定SIGPROF信号为超时处理信号,对应超时处理函数zend_timeout,当发生超时时,会发送此信号并触发函数zend_timeout显示错误信息并中止程序。

如果需要取消定时器,Win平台通过PostThreadMessage发送WM_UNREGISTER_ZEND_TIMEOUT给线程即可,类unix平台会重置定时器的时长为0。

超时管理

超时机制的管理非常灵活,有三种修改运行时长的方法。

1、 修改配置项。默认情况下PHP脚本的最长运行时长为30s。如果需要调整此项,可以通过修改php.ini文件中的max_execution_time项并重启动服务器达到修改最长运行时长的目的。此种方法适用于最开始的默认配置修改,或在其它方法无效的情况下使用。

2、 使用set_time_limit接口函数。此函数的作用是设置脚本最大执行时间。当此函数被调用时,set_time_limit()会从零开始重新启动超时计数器。比如,每一次设置是5秒,待脚本运行4秒后,脚本中又设置了5秒,那么,脚本在超时之前可运行总共时间为10秒。如下脚本示例:

    <?php
    set_time_limit(5);
    for ($i = 0; $i < 4; $i++) {
        sleep(1);
        echo $i, "<br />";
    }
 
    set_time_limit(5);
 
    for ($i = 0; $i < 4; $i++) {
        sleep(1);
        echo $i, "<br />";
    }

如上的代码,程序会执行完两个循环,都输出0,1,2,3。如果我们注释掉中间的set_time_limit(5),程序再运行一次,此时就会在第二个循环输出0后报错。

在安全模式下,无法通过set_time_limit和ini_set重新设置max_execution_time,只有关闭安全模式或改变php.ini中的时间限制才能达到修改此参数的目的。

3、 通过ini_set修改max_execution_time参数。

以上的三种方法,其实现过程基本类似,前一种是在初始化时调用on_modify指针函数。后两种在处理了参数后,调用zend_alter_ini_entry_ex函数,触发on_modify函数。于是,管理超时机制的所有操作最终都汇集到OnUpdateTimeout函数。在此函数中,通过zend_set_timeout重新设置脚本的超时时间。