PHP

时间:2019-03-05 10:01:04

标签: php apache exception crash php-7

我正在研究资源流,尤其是在fopen()上。 除了返回false而不是资源之外,此函数在失败时还会引发警告。我决定抑制不必要的警告,这是一个问题。

我想到了两种可能性:使用错误抑制运算符@或使用set_error_handler()。并被告知@性能不佳,并且所提出的问题往往比解决的问题多,我运行了一个快速基准测试,以查看set_error_handler()与之相违背。

下面是有问题的代码:

<?php 

error_reporting(E_ALL);

function errorHandler(int $errorNumber, string $errorMessage)
{
    throw new \Exception();
}

$previousHandler = set_error_handler("errorHandler");

$operations = 10000;

for($i = 0; $i < $operations; $i++) {
    try{
        $inexistant[0];
    } catch (\Exception $e) {}
}

set_error_handler($previousHandler);

echo 'ok';

运行以下简单代码将使Apache服务器崩溃,并显示以下消息:

[mpm_winnt:notice] [pid 6000:tid 244] AH00428: Parent: child process 3904 exited with status 3221225725 -- Restarting.

搜索后,此消息表示服务器出现访问冲突错误,主要是在达到堆栈大小限制的情况下。但是,这不是事实,因为此代码不应增加堆栈大小(实际上,它不会增加PHP堆栈框架)。

我还测试了时间是否重要,但是即使每次迭代之间有3ms的睡眠时间,崩溃也会在大约相同数量的迭代之后发生。这个数字约为700,但波动很小,有时在704处运行良好,有时则没有。

此外,在php bug跟踪器上进行搜索并没有显示任何相关内容,除了可能bug entry之外,该内容讨论了对处理函数调用的处理。这可能意味着异常可能会绕过函数退出时的某些处理,但是由于我对PHP源代码一无所知,所以这纯粹是猜测。

由于我想正确传播错误消息,因此使用set_error_handler()的方法将是最清晰的方法,但是我知道我可以使用error_get_last()和@运算符来实现相同的目标更多的代码(因为像fopen()这样的多个函数在实际的projet中一个接一个地调用)。

因此,这里有个问题:这是PHP的错误吗?有没有办法在保持清晰代码的同时规避这个问题?

谢谢。

PS:我知道基准测试的理由充其量是可疑的,只要性能可行,我应该采用最清晰的代码,但这仍然让我发现了代码的这一有趣之处。 / p>

编辑:我忘了将我对此进行测试的版本放在上面:

  • 通过XAMPP的Windows 7,Apache 2.4.38,PHP 7.3.2
  • Windows 7,Apache 2.4.29,PHP 7.2.2通过XAMPP
  • Ubuntu Server 18.04,Apache / 2.4.29(Ubuntu),PHP 7.2.15-0ubuntu0.18.04.1

2 个答案:

答案 0 :(得分:0)

为什么要经历所有这些麻烦?仅处理false的返回值会好得多,因为docs声明:Returns a file pointer resource on success, or FALSE on error.因此,仅检查fopen的返回值是否为false以及然后继续基于此操作(或在必要时抛出您自己的错误)。

答案 1 :(得分:0)

由于已确认这是一个PHP错误,并且已找到确切原因,因此我将同时发布答案和解决方法,直到错误被解决为止。

首先,这里是错误报告:https://bugs.php.net/bug.php?id=77693

这是导致捕获异常上下文的堆栈溢出,在这种情况下,该异常包括调用错误处理程序的函数,因为它的行为是捕获父上下文。此父上下文包括previoulsy捕获的异常(已添加到要捕获的新异常的上下文中),并重复ad vitam aeternam直到崩溃。

由于已明确确定原因,解决方案很简单:只需在catch块的末尾添加unset()即可,

$operations = 10000;

for($i = 0; $i < $operations; $i++) {
    try{
        $inexistant[0];
    } catch (\Exception $e) {
        unset($e);
    }
}

然后就没有问题了。

要添加到第二个问题中,要求在使用fopen的情况下使用干净的替代品以及类似的其他方法,这是一个解决方案:

function throwLastError() {
    $context = error_get_last();
    error_clear_last();
    throw new ErrorException($context["message"],
                             0,
                             $context["type"],
                             $context["file"],
                             $context["line"]);
}

// Wrong call to fopen
if (!@fopen("", "a"))
    throwLastError();

当两个错误参数都使用时,两个答案的性能大致相同,@方法要慢10%。