如何捕获致命错误:PHP的最大执行时间超过了30秒


How to catch the fatal error: Maximum execution time of 30 seconds exceeded in PHP

我一直在摆弄我正在开发的一个系统,并设法使它导致以下情况:

致命错误:Maximum execution time of 30 seconds exceeded

它发生在我做一些不现实的事情时,但它可能发生在用户身上。

有没有人知道,如果有一种方法来捕获这个异常?我四处阅读了一下,但似乎每个人都建议提高允许的时间。

如何尝试作为PHP文档(嗯…至少有一个读者)说:

<?php
function shutdown()
{
 $a = error_get_last();
 if ($a == null) {echo "No errors";}
 else {print_r($a);}
}
register_shutdown_function('shutdown');
ini_set('max_execution_time', 1);
sleep(3);
?>

请看以下链接:

  1. http://www.php.net/manual/en/function.set-error-handler.php # 106061
  2. http://www.php.net/manual/en/function.register-shutdown-function.php

您唯一的选择是增加脚本的允许执行时间(将其设置为0使其无限,但不建议这样做)或生成一个新线程并希望最好的

不可捕获的原因是它没有真正被抛出。实际上没有任何一行代码触发错误,而是PHP说,"不,对不起,这太长了。"现在该关闭了。"这是有道理的。想象一下,如果一个脚本的最大执行时间为30秒,那么它捕获这个错误并花费另外30秒的时间……在一个设计糟糕的程序中,这为开发提供了一些相当恶劣的机会。至少,它将为DOS攻击创造机会。

这不是异常,这是一个错误。异常和错误之间有重要的区别,首先,错误不能用try/catch语义捕获。

PHP脚本是围绕短执行时间的范例构建的,因此PHP在默认情况下配置为假设如果脚本运行超过30秒,它必须被捕获在一个无限循环中,因此应该终止。这是为了防止错误的PHP脚本导致拒绝服务,无论是意外的还是恶意的。

然而,脚本有时确实需要比默认分配的运行时间更多的时间。

您可以尝试更改最大执行时间,通过使用 set_time_limit() 或通过更改php.ini文件中的 max_execution_time 的值来提高限制。您也可以通过将执行时间设置为0来完全删除该限制,但不建议这样做。

set_time_limit() 可能被disable_functions等机制禁用,因此您可能无法使用它,同样您可能无法访问php.ini。如果这两种情况都存在,那么你应该联系你的房东寻求帮助。

一个例外是从命令行运行的PHP脚本。在这些运行条件下,PHP脚本可能是交互式的,需要花费很长时间处理数据或等待输入。由于这个原因,默认情况下从命令行运行的脚本没有max_execution_time限制。

EDIT TO ADD: PHP 7的错误处理进行了重大修改。我认为错误和异常现在都是Throwable的子类。这可能使上述内容不再适用于PHP7+,尽管我现在必须更仔细地研究错误处理的具体工作方式才能确定。

是的,我测试了TheJanOnline的解决方案。sleep()不计入php的执行时间,所以下面是带有无限循环的工作版本:

<?php 
function shutdown() 
 { 
     $a=error_get_last(); 
     if($a==null)   
         echo "No errors"; 
     else 
          print_r($a); 
 } 
register_shutdown_function('shutdown'); 
ini_set('max_execution_time',1 ); 
while(1) {/*nothing*/}
// will die after 1 sec and print error
?>

你对此无能为力。但是你可以使用register_shutdown_function

实现优雅关机
<?php 
ini_set('display_errors', '0');         
ini_set("max_execution_time",15 ); //you can use this if you know your script should not take longer than 15 seconds to finish
register_shutdown_function('shutdown');

function shutdown() 
{ 
       $error = error_get_last();
       if ($error['type'] === E_ERROR) {
      //do your shutdown stuff here
      //be care full do not call any other function from within shutdown function
      //as php may not wait until that function finishes
      //its a strange behavior. During testing I realized that if function is called 
      //from here that function may or may not finish and code below that function
      //call may or may not get executed. every time I had a different result. 
      // e.g.
      other_function();
      //code below this function may not get executed
       } 
} 
while(true)
{
}
function other_function()
{
  //code in this function may not get executed if this function
  //called from shutdown function
} 
?>

有一个小棘手的方法来处理"致命错误:最大执行时间超过30秒"作为例外在某些情况下:

function time_sublimit($k = 0.8) {
    $limit = ini_get('max_execution_time'); // changes even when you set_time_limit()
    $sub_limit = round($limit * $k);
    if($sub_limit === 0) {
        $sub_limit = INF;
    }
    return $sub_limit;
}

在你的代码中,你必须测量执行时间,并在超时之前抛出异常,致命错误可能被触发。$k = 0.8是允许执行时间的80%,所以你有20%的时间来处理异常。

try{
    $t1 = time(); // start to mesure time.
    while (true) { // put your long-time loop condition here
        time_spent = time() - $t1;
        if(time_spent >= time_sublimit()) {
            throw new Exception('Time sublimit reached');
        }
        // do work here
    }
} catch(Exception $e) {
    // catch exception here
}

我根据@pinkal-vansia给出的答案想出了这个。所以我不是在说一个原创的答案,而是一个有实际应用的答案。我需要一种方法让页面在超时时自行刷新。因为我已经观察了足够的超时我的cURL脚本知道代码是工作的,但有时无论出于什么原因,它无法连接到远程服务器,或读取所提供的html完全,并在刷新问题就消失了,我可以用脚本刷新自己"治愈"一个最大的执行超时错误。

<?php //script name: scrape_script.php
ini_set('max_execution_time', 300);
register_shutdown_function('shutdown');
function shutdown() 
{ 
    ?><meta http-equiv="refresh" content="0; url=scrape_script.php"><?php
    // just do a meta refresh. Haven't tested with header location, but
    // this works fine.
}

仅供参考,300秒对于我正在运行的抓取脚本来说并不太长,它只需要比这少一点,就可以从我正在抓取的各种页面中提取数据。有时由于连接不规范,它只超过几秒钟。知道有时失败的是连接时间,而不是脚本处理,所以最好不要增加超时,而只是自动刷新页面并再次尝试。

我遇到过一个类似的问题,我是这样解决的:

<?php
function shutdown() {
    if (!is_null($error = error_get_last())) {
        if (strpos($error['message'], 'Maximum execution time') === false) {
            echo 'Other error: ' . print_r($error, true);
        } else {
            echo "Timeout!'n";
        }
    }
}
ini_set('display_errors', 0);
register_shutdown_function('shutdown');
set_time_limit(1);
echo "Starting...'n";
$i = 0;
while (++$i < 100000001) {
    if ($i % 100000 == 0) {
        echo ($i / 100000), "'n";
    }
}
echo "done.'n";
?>

这个脚本将在最后打印Timeout!

你可以将$i = 0;行修改为$i = 1 / 0;,它将打印:

Other error: Array
(
    [type] => 2
    [message] => Division by zero
    [file] => /home/user/test.php
    [line] => 17
)

引用:

    PHP: register_shutdown_function - ManualPHP: set_time_limit - ManualPHP: error_get_last - Manual