【PHP】 タイムアウト発生時のエラー処理を仕込む(未完成)

PHPでは、1回のリクエストに対して時間制限(デフォルトで30秒)があり、処理がこの制限時間内に終了しなかった場合にタイムアウトが発生して強制的に終了してしまう。

php.ini での設定や、 set_time_limit() などで制限時間を変更することもできるが、やっていいときと望ましくないときがあって、望ましくないときが大半だ。

タイムアウトが発生すると、Fatal Error (致命的なエラー) という機械的なエラーメッセージを出力して強制終了されるが、この動作を変更することはできないものか。

ということで、PHPでタイムアウト発生時のエラーハンドラを登録する方法を調べてみた。

  • ※注意:結果として、十分な結論には至れませんでした。調べる途中で試したことを記録したのみです。

タイムアウトを発生させる

まず、タイムアウトのテストをするために、タイムアウトを発生させる処理を書いておく。

<?php
set_time_limit(1);//制限時間を1秒に設定
sleep(2);//2秒間処理を止めてタイムアウトを起こさせる
?>

set_error_handler() でエラーハンドラを登録してみる(失敗)

最初に試したのがこれ。set_error_handler() に関数名をセットすると、エラーが発生したときにいちいちコールしてもらえるようになる。

<?php
function errorhandler( $errno , $errstr , $errfile , $errline , $errcontext ){
    print 'ERROR!!';
}
set_error_handler('errorhandler');

set_time_limit(1);//制限時間を1秒に設定
sleep(2);//2秒間処理を止めてタイムアウトを起こさせる
?>

この関数は、軽微なレベルのエラーまで全部反応してしまうので、第1引数に渡される $errno から、どのようなレベルのエラーか判断して実装する必要がある。

公式のマニュアルページに下記のエラーハンドラの実装例があったので参考までに貼っておく。

function myErrorHandler($errno, $errstr, $errfile, $errline)
{
    if (!(error_reporting() & $errno)) {
        // error_reporting 設定に含まれていないエラーコードです
        return;
    }

    switch ($errno) {
    case E_USER_ERROR:
        echo "My ERROR [$errno] $errstr<br />\n";
        echo "  Fatal error on line $errline in file $errfile";
        echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
        echo "Aborting...<br />\n";
        exit(1);
        break;

    case E_USER_WARNING:
        echo "My WARNING [$errno] $errstr<br />\n";
        break;

    case E_USER_NOTICE:
        echo "My NOTICE [$errno] $errstr<br />\n";
        break;

    default:
        echo "Unknown error type: [$errno] $errstr<br />\n";
        break;
    }

    /* PHP の内部エラーハンドラを実行しません */
    return true;
}

しかし、Fatal Error はこの関数を通らないようだ。 タイムアウトは Fatal Error として処理されるため、残念ながらこの方法では拾えなかった。

register_shutdown_function()で、スクリプト終了時に実行する関数を登録

スクリプトの終了時に実行される関数を登録することができるらしいので、試してみた。

<?php
function shutdown_func(){
    print 'Timeout!!';
}
register_shutdown_function('shutdown_func');

set_time_limit(1);//制限時間を1秒に設定
sleep(2);//2秒間処理を止めてタイムアウトを起こさせる
?>

しかしこれは「スクリプトの終了時」に実行される関数なので、正常終了時にも実行されてしまう。

ヘッダーが送信されてなかったら、タイムアウトしたことにしちゃえ

本質的な解決ではないが、今回はとりあえず、ヘッダーがすでに送信されたかどうかを調べる headers_sent() を使って、ヘッダーが送信されていなければタイムアウトになったのだろう、という推測を信じて解決することにした。

<?php
function shutdown_func(){
    if( !headers_sent() ){
        print 'Timeout!!';
    }
}
register_shutdown_function('shutdown_func');

set_time_limit(1);//制限時間を1秒に設定
sleep(2);//2秒間処理を止めてタイムアウトを起こさせる
?>

しかし当然だが、この実装は正しくない。実際には、ヘッダーを送信した後でタイムアウトが発生するケースもある。フレームワークなどにもよるかも知れない。

まとめ

ちょっと、PHPのタイムアウト処理を実行というにはすっきりしない非本質的な解決になってしまった。方法がないわけないと思うので、いずれまたよく調べてみたい。

今回使った register_shutdown_function() と、debug_backtrace()error_get_last() などのエラー処理系関数を組み合わせるとできそうな予感はするが・・・。

コメント (1件)

2013年2月12日(Tue) 15時01分18秒 通りすがりんぐ

私も同じ問題で考え込んでいましたが、
どうもsleep系の処理は、Timeoutまでの秒数にカウントされない様子です。



プロフィール

ときにはデザイナ、ときにはディレクタ、ときにはプログラマ、ときには何でも屋と、ウェブの世界で未熟ながらもいろいろやっている、コヤナギトモヤです。

ツイッター上ではこのヒト。
@tomk79

RSSフィード

ページの先頭へ戻る