公開日: 2010年10月02日(Sat)
PHPでは、1回のリクエストに対して時間制限(デフォルトで30秒)があり、処理がこの制限時間内に終了しなかった場合にタイムアウトが発生して強制的に終了してしまう。
php.ini での設定や、 set_time_limit() などで制限時間を変更することもできるが、やっていいときと望ましくないときがあって、望ましくないときが大半だ。
タイムアウトが発生すると、Fatal Error (致命的なエラー) という機械的なエラーメッセージを出力して強制終了されるが、この動作を変更することはできないものか。
ということで、PHPでタイムアウト発生時のエラーハンドラを登録する方法を調べてみた。
まず、タイムアウトのテストをするために、タイムアウトを発生させる処理を書いておく。
<?php
set_time_limit(1);//制限時間を1秒に設定
sleep(2);//2秒間処理を止めてタイムアウトを起こさせる
?>
最初に試したのがこれ。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 として処理されるため、残念ながらこの方法では拾えなかった。
スクリプトの終了時に実行される関数を登録することができるらしいので、試してみた。
<?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() などのエラー処理系関数を組み合わせるとできそうな予感はするが・・・。
私も同じ問題で考え込んでいましたが、
どうもsleep系の処理は、Timeoutまでの秒数にカウントされない様子です。
公開日: 2010年10月02日(Sat)