Безопасно поймать ошибку "допустимый размер памяти исчерпан" в PHP


У меня есть скрипт шлюза, который возвращает JSON обратно клиенту. В скрипте я использую set_error_handler чтобы поймать ошибки и все еще иметь форматированный возврат.

он подвержен ошибкам "разрешенный размер памяти исчерпан", но вместо того, чтобы увеличить лимит памяти с чем-то вроде ini_set ('memory_limit','19T'), Я просто хочу вернуть, что пользователь должен попробовать что-то еще, потому что он использовал много памяти.

есть ли хорошие способы чтобы поймать фатальные ошибки?

4 54

4 ответа:

как ответ предполагает, что вы можете использовать register_shutdown_function() чтобы зарегистрировать обратный вызов, который будет проверять error_get_last().

вам все равно придется управлять выводом, сгенерированным из оскорбительного кода, будь то @ (заткнись оператор), или ini_set('display_errors', false)

ini_set('display_errors', false);

error_reporting(-1);

set_error_handler(function($code, $string, $file, $line){
        throw new ErrorException($string, null, $code, $file, $line);
    });

register_shutdown_function(function(){
        $error = error_get_last();
        if(null !== $error)
        {
            echo 'Caught at shutdown';
        }
    });

try
{
    while(true)
    {
        $data .= str_repeat('#', PHP_INT_MAX);
    }
}
catch(\Exception $exception)
{
    echo 'Caught in try/catch';
}

при запуске выводит Caught at shutdown. К сожалению,ErrorException объект исключения не выбрасывается, потому что неустранимая ошибка вызывает завершение сценария, впоследствии пойман только в функции выключения.

вы можете проверить $error массив в функции выключения для деталей на причине, и отвечает соответственно. Одним из предложений может быть переиздание запроса обратно в ваше веб-приложение (по другому адресу, или с другими параметрами конечно) и вернуть захваченный ответ.

я рекомендую держать error_reporting() высокий (значение -1) хотя, и используя (как и другие предложил) обработка ошибок для всего остального с set_error_handler() и ErrorException.

Если вам нужно выполнить бизнес-код, когда происходит эта ошибка (ведение журнала, резервное копирование контекста для будущих отладок, отправка по электронной почте или т. д.), регистрации функции выключения недостаточно: вы должны освободить память в некотором роде.

одним из решений является выделение некоторой аварийной памяти где-то:

public function initErrorHandler()
{
    // This storage is freed on error (case of allowed memory exhausted)
    $this->memory = str_repeat('*', 1024 * 1024);

    register_shutdown_function(function()
    {
        $this->memory = null;
        if ((!is_null($err = error_get_last())) && (!in_array($err['type'], array (E_NOTICE, E_WARNING))))
        {
           // $this->emergencyMethod($err);
        }
    });
    return $this;
}

вы можете получить размер памяти, уже потребленной процессом, используя эту функцию memory_get_peak_usage documentations are at http://www.php.net/manual/en/function.memory-get-peak-usage.php я думаю, что было бы проще, если бы вы могли добавить условие для перенаправления или остановки процесса до того, как предел памяти будет почти достигнут процессом. :)

в то время как решение @alain-tiemblo работает отлично, я поставил этот скрипт, чтобы показать, как вы можете зарезервировать некоторую память в PHP-скрипте, вне области объекта:

<?php

function getMemory(){
    return ((int) (memory_get_usage() / 1024)) . 'KB';
}

// memory is an object and it is passed by reference
function shutdown($memory) {
    echo 'Start Shut Down: ' . getMemory() . PHP_EOL;

    // unsetting $memory does not free up memory
    // I also tried unsetting a global variable which did not free up the memory
    unset($memory->reserve);

    echo 'End Shut Down: ' . getMemory() . PHP_EOL;
}

echo 'Start: ' . getMemory() . PHP_EOL;

$memory = new stdClass();
// reserve 3 mega bytes
$memory->reserve = str_repeat('❤', 1024 * 1024);

echo 'After Reserving: ' . getMemory() . PHP_EOL;

unset($memory);

echo 'After Unsetting: ' . getMemory() . PHP_EOL;

$memory = new stdClass();
// reserve 3 mega bytes
$memory->reserve = str_repeat('❤', 1024 * 1024);

echo 'After Reserving again: ' . getMemory() . PHP_EOL;

// passing $memory object to shut down function
register_shutdown_function('shutdown', $memory);

и выход будет такой:

Start: 349KB
After Reserving: 3426KB
After Unsetting: 349KB
After Reserving again: 3426KB
Start Shut Down: 3420KB
End Shut Down: 344KB