web-dev-qa-db-ja.com

PHPで 'Allowed memory size枯渇'エラーを安全にキャッチします

JSONをクライアントに返すゲートウェイスクリプトがあります。スクリプトでは set_error_handler を使用してエラーをキャッチし、まだフォーマットされた戻り値を保持しています。

「Allowed memory size exhausted」エラーの影響を受けますが、 ini_set( 'memory_limit'、 '19T') などのようにメモリ制限を増やすのではなく、ユーザーが試してみてくださいそれは多くのメモリを使用していたためです。

致命的なエラーをキャッチする良い方法はありますか?

62
Matt R. Wilson

この回答 が示唆するように、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配列で原因の詳細を確認し、それに応じて対応できます。 1つの提案として、Webアプリケーションに対してリクエストを再発行し(、異なるアドレスで、またはもちろん異なるパラメーター)、キャプチャした応答を返すことができます。

ただし、error_reporting()を高く(-1の値)にして、(他の人が示唆しているようにset_error_handler()およびErrorExceptionを使用して他のすべてのエラー処理。

46
Dan Lugg

このエラーが発生したときにビジネスコードを実行する必要がある場合(ロギング、将来のデバッグのためのコンテキストのバックアップ、電子メールなど)、シャットダウン関数の登録だけでは十分ではありません。何らかの方法でメモリを解放する必要があります。

1つの解決策は、緊急メモリをどこかに割り当てることです。

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

この関数を使用して、プロセスがすでに消費しているメモリのサイズを取得できますmemory_get_peak_usageドキュメントは http://www.php.net/manual/en/function.memory-get-peak-usage.phpにあります プロセスがメモリ制限にほぼ達する前に、プロセスをリダイレクトまたは停止する条件を追加できれば簡単だと思います。 :)

7

@ alain-tiembloソリューションは完全に機能しますが、このスクリプトを使用して、オブジェクトの範囲外のphpスクリプトでメモリを予約する方法を示します。

短縮版

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

完全なサンプルスクリプト

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