ChatGPT解决这个技术问题 Extra ChatGPT

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

我一直在玩我正在开发的系统并设法让它导致这个:

致命错误:超过 30 秒的最大执行时间

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

有谁知道是否有办法捕捉这个异常?我已经阅读过,但似乎每个人都建议增加允许的时间。

我相信一旦超过执行时间,脚本就会终止。在这种情况下,可能已经捕获异常的脚本已经被杀死了。
无法捕获或处理致命错误(它们不是异常)。您只能通过 registering a shutdown function 优雅地处理脚本终止,但脚本将在之后结束。
不,可能的重复不是“精确”重复。它不要求脚本超时引发的致命错误,而只要求通常如何捕获致命错误。但是由于在这种情况下一般也回答了具体问题,因此它有资格成为可能的重复项。如果问题是如何解决脚本超时错误,那么它可能是其中大多数 stackoverflow.com/search?q=maximum+execution+time+php 的重复;)
一般来说,我无法回答这个问题,但在某些情况下,您可以将某些事情的时间限制为小于 max_execution_time,然后抓住它。例如 curl_setopt($client, CURLOPT_TIMEOUT, 27);将导致 curl 在 27 秒后放弃,这样您就不会遇到致命错误。

J
John

试试 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);
?>

看看以下链接:

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


这可能是我从未见过的最好的 php 秘密。惊人的。
我测试了它。但仍然显示致命错误消息。能不能避免。我使用的任何方式 ini_set('display_errors', '0');
sleep() 实际上并不影响时间限制计数器,这很奇怪,但确实如此。所以你应该做一些其他的事情来测试你的 shutdown() 函数——几个嵌套的 for 循环,每个循环迭代一百万次就可以了。
@BobbyJack虽然很奇怪,但这是有道理的,因为休眠线程会停止执行一段时间。因为它没有执行,所以那个时间不被认为是经过的执行时间。
确实,这是一个不错的选择。就我而言,通过使用此功能,我可以使用 javascript 作为最终资源将用户重定向到另一个页面/服务器。
c
cwallenpoole

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

无法捕获的原因是它并没有真正被抛出。没有一行代码实际上触发了错误,而是 PHP 说:“不,抱歉,这太长了。现在该关闭了。”这是有道理的。想象一下,在一个设计不佳的程序中,有一个最长执行时间为 30 秒的脚本捕获该错误并再花费 30 秒……这会带来一些相当讨厌的利用机会。至少,它会为 DOS 攻击创造机会。


将异常时间设置为 0 然后我自己超时怎么办?
这在某些情况下可能有效,但不是全部。如果它是某种形式的循环可能是最好的,这样你就可以定期测试。
唔。我注意到它的原因是在接收数据包块时。我将数据包大小设置为非常小的东西,并且发送需要很长时间。我的想法是,如果要发送大量数据,并且数据包大小约为 1024 字节,我可以在该循环上设置自己的超时,并在几秒钟内做出反应以警告用户。
我反对被 PHP 告知编码缺陷是否允许无限循环或 DOS 漏洞利用等。我认为应该由程序员来处理错误。也许超时发生在 cURL 期间,我希望页面无限刷新,直到它重新连接到 url(因为 T1 线路已关闭,或者可能是远程服务器,谁知道?)。我不希望我的预处理器(无论是 php、asp、cgi 等)为我做出决定。我不认为预处理器,甚至像 javascript 这样的客户端语言不应该代表我做出道德或道德决定。
您还可以选择与超时相关的其他选项,但本问题未涵盖这些选项。最常见的做法是简单地确保请求不超过 60 秒。到那时,无论如何您都不会面对公众。
G
GordonM

这不是一个例外,这是一个错误。异常和错误之间有重要的区别,首先是错误不能用 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 限制。

编辑添加:PHP 7 的错误处理进行了大修。我相信错误和异常现在都是 Throwable 的子类。这可能会使上述内容不再与 PHP7+ 相关,尽管我必须更仔细地研究错误处理的具体细节才能确定。


p
pinkal vansia

您对此无能为力。但是您可以使用 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
} 

?>

如果 other_function() 在无限循环之前声明,会在关机期间调用它吗?
@Douglas.Sesar other_function() 将以任何方式调用。但是不能保证它会完成并且函数调用下面的任何代码。如果 other_function() 花费很少的时间来做很少的事情,它可能会完成并且函数调用下面的任何代码。但是如果完成任务花费的时间太多,那么,并不能保证它会完全完成!
在 OP 问题的最严格意义上,这个解决方案是有效的。我想感谢您在春季登机时根据您的回答进行了稍微详细的回答。
W
Walf

是的,我测试了 TheJanOnline 的解决方案。 sleep() 不计入 php 执行时间,所以这里是无限循环的 WORKING 版本:

<?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
?>

u
user3901681

在某些情况下,将“致命错误:超过 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
}

如果您的工作被很好地分割以便可以迭代,那么这将起作用,但是如果单个大操作或操作序列一直持续到时间限制,那么您在很大程度上仍然是 SOL。
T
TARKUS

我根据@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 秒并不算长,这比从我正在抓取的页面中提取数据的时间要少一点。有时,仅由于连接不规则,它只会持续几秒钟。知道有时失败的是连接时间,而不是脚本处理,最好不要增加超时,而只是自动刷新页面并重试。


A
Antônio Medeiros

我遇到了类似的问题,这就是我解决它的方法:

<?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 - 手册

PHP: set_time_limit - 手动

PHP:error_get_last - 手动