web-dev-qa-db-fra.com

Attraper en toute sécurité une erreur «Taille de mémoire autorisée épuisée» dans PHP

J'ai un script de passerelle qui renvoie JSON au client. Dans le script, j'utilise set_error_handler pour détecter les erreurs et avoir toujours un retour formaté.

Il est sujet à des erreurs de "taille de mémoire autorisée épuisée", mais plutôt que d'augmenter la limite de mémoire avec quelque chose comme ini_set ('memory_limit', '19T') , je veux juste retourner que l'utilisateur doit essayer quelque chose d'autre parce qu'il utilisait beaucoup de mémoire.

Existe-t-il de bons moyens de détecter les erreurs fatales?

62
Matt R. Wilson

Comme cette réponse le suggère, vous pouvez utiliser register_shutdown_function() pour enregistrer un rappel qui vérifiera error_get_last().

Vous devrez toujours gérer la sortie générée à partir du code incriminé, que ce soit par l'opérateur @ ( shut up ), ou 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';
}

Lorsqu'il est exécuté, cela génère Caught at shutdown. Malheureusement, l'objet d'exception ErrorException n'est pas levé car l'erreur fatale déclenche la terminaison du script, interceptée par la suite uniquement dans la fonction d'arrêt.

Vous pouvez vérifier le tableau $error Dans la fonction d'arrêt pour plus de détails sur la cause et répondre en conséquence. Une suggestion pourrait être de réémettre la demande sur votre application Web ( à une adresse différente, ou bien sûr avec différents paramètres ) et renvoyer la réponse capturée.

Je recommande de garder error_reporting() high ( une valeur de -1) cependant, et d'utiliser ( comme d'autres l'ont suggéré ) gestion des erreurs pour tout le reste avec set_error_handler() et ErrorException.

46
Dan Lugg

Si vous devez exécuter du code métier lorsque cette erreur se produit (journalisation, sauvegarde du contexte pour de futurs débogages, emailing ou autre), l'enregistrement d'une fonction d'arrêt ne suffit pas: vous devez libérer de la mémoire en quelque sorte.

Une solution consiste à allouer de la mémoire d'urgence quelque part:

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;
}
34
Alain Tiemblo

vous pouvez obtenir la taille de la mémoire déjà consommée par le processus en utilisant cette fonction memory_get_peak_usage les documentations sont à http://www.php.net/manual/en/function.memory-get-peak-usage.php Je pense que ce serait plus facile si vous pouviez ajouter une condition pour rediriger ou arrêter le processus avant que la limite de mémoire soit presque atteinte par le processus. :)

7
Christopher Pelayo

Alors que la solution @ alain-tiemblo fonctionne parfaitement, j'ai mis ce script pour montrer comment vous pouvez réserver de la mémoire dans un script php, hors de la portée de l'objet.

Version courte

// memory is an object and it is passed by reference
function shutdown($memory) {
    // unsetting $memory does not free up memory
    // I also tried unsetting a global variable which did not free up the memory
    unset($memory->reserve);
}

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

register_shutdown_function('shutdown', $memory);

Exemple de script complet

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

Et la sortie serait:

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