Безопасно поймать ошибку «Разрешенный объем памяти исчерпан» в PHP - PullRequest
61 голосов
/ 09 декабря 2011

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

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

Есть ли хорошие способы отловить фатальные ошибки?

Ответы [ 4 ]

46 голосов
/ 09 декабря 2011

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

Вам все равно придется управлять выводом, сгенерированным изнарушающий код, будь то оператор @ ( shut up ) или 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.

32 голосов
/ 20 декабря 2014

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

Одно из решений состоит в том, чтобы выделить где-нибудь аварийную память:

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;
}
7 голосов
/ 09 декабря 2011

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

4 голосов
/ 07 июня 2018

Хотя решение @ 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...