register_shutdown_function()で登録したコールバック関数が、実行時にFatalになった時でも動くのか試してみた。
メモリを使い切るまで、配列を足し続けるコードを書いた。メモリオーバのエラーで落ちても、コールバック関数は呼ばれた。
<?php
error_reporting(-1);
ini_set('display_errors', 1);
register_shutdown_function('unexpected_shutdown');
function unexpected_shutdown()
{
echo '予期しないシャットダウンです。';
}
while ( true ) // メモリを使い切るまでループ
{
$hugeData[] = array();
}
// 結果:
// Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 40 bytes) in /Applications/MAMP/htdocs/php/register_shutdown_function/case1.php on line 15
// 予期しないシャットダウンです。
ただ、これもコールバック関数を実行するのに十分なメモリがあるときだけ成功するみたい。コールバック関数内でメモリを大量消費すると、当然こけちゃう。コールバック関数で、メモリを沢山必要とする場合は、unset()できるものはしてメモリ領域を確保しないとダメっぽい。
<?php
error_reporting(-1);
ini_set('display_errors', 1);
register_shutdown_function('unexpected_shutdown');
function unexpected_shutdown()
{
$dummy = range(0, 1000); // ここでメモリオーバ
echo '予期しないシャットダウンです。';
}
while ( true )
{
$hugeData[] = array();
}
// 結果:
// Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 40 bytes) in /Applications/MAMP/htdocs/php/register_shutdown_function/case1.1.php on line 16
この場合もコールバック関数が動いた。
<?php
error_reporting(-1);
ini_set('display_errors', 1);
register_shutdown_function('unexpected_shutdown');
function unexpected_shutdown()
{
echo '予期しないシャットダウンです。';
}
require 'parse_error.php'; // syntax error のファイル
echo 'OK'; // これを表示する前に落ちる
// 結果:
// Parse error: syntax error, unexpected T_STRING in /Applications/MAMP/htdocs/php/register_shutdown_function/parse_error.php on line 3
// 予期しないシャットダウンです。
register_shutdown_function()を使えば、せめてHTTP 503エラーくらい出せるかも。
メモリの使用量を調べるに当たって、検査用関数を作ってみた。初期メモリ使用量は無視して、純粋に変数で使われた容量を検出するもの。$initialMemoryUseですでに20バイトくらい使っている、というのは誤差の範囲で^^
function dumpMemory()
{
static $initialMemoryUse = null;
if ( $initialMemoryUse === null )
{
$initialMemoryUse = memory_get_usage();
}
var_dump(number_format(memory_get_usage() - $initialMemoryUse));
}
dumpMemory(); // string(1) "0"
$hoge = array();
dumpMemory(); // string(3) "184"
for ( $i = 1; $i < 10000; $i++ )
{
$hoge[$i] = null;
}
dumpMemory(); // string(7) "785,720"
for ( $i = 1; $i < 10000; $i++ )
{
unset($hoge[$i]);
}
dumpMemory(); // string(6) "65,764"
んー、なんかものすごいオーバヘッドがあるんだけど。
dumpMemory(); // string(1) "0"
$hoge = array();
dumpMemory(); // string(3) "184"
for ( $i = 1; $i < 10000; $i++ )
{
$hoge[$i] = null;
unset($hoge[$i]);
}
dumpMemory(); // string(3) "260"
ケース1の変形版。unsetのタイミングをforの中にしてみたら、かなりオーバヘッドが減った。理由はわかんない。
dumpMemory(); // string(1) "0"
$hoge = array();
dumpMemory(); // string(3) "184"
for ( $i = 1; $i < 10000; $i++ )
{
$hoge[$i] = null;
}
dumpMemory(); // string(7) "785,732"
unset($hoge);
dumpMemory(); // string(2) "96"
これもまたケース1の変形版。forでunset()しないで、配列まるごとunset()するようにしてみた。ケース2よりもオーバヘッドはひどくない。
function scopeTest()
{
$array = range(1, 10000);
dumpMemory();
// ここで$arrayはスコープを失う
}
dumpMemory(); // string(1) "0"
scopeTest(); // string(7) "785,700"
dumpMemory(); // string(2) "20"
今度はスコープでの実験。スコープが切れた変数はメモリから解放されると聞いていたけど、実際そのとおりみたい。
class TestClass
{
public static function test()
{
$array = range(1, 10000);
dumpMemory();
// ここでスコープを失う
}
}
dumpMemory(); // string(1) "0"
TestClass::test(); // string(7) "785,680"
dumpMemory(); // string(2) "20"
ケース4をクラスにしてみただけ。静的関数でもスコープが切れた段階で、メモリが開放されていた。
ApacheからPHPを実行するくらいなら、プロセス自体の寿命も短いからメモリなんてそうそう気にならないけど、PHPでバッチ処理をしようとするとメモリ管理も大変になるんだよなあ。扱うデータ量が全然違ってくるし、プロセスの寿命も長いし。メモリを気にするならPHPなんてやめちまえ、ってこと?
try
{
ob_start();
echo "これは見えちゃだめ!";
throw new Exception();
}
catch ( Exception $e )
{
}
echo "こっちが先。";
これは見えちゃだめ!こっちが先。
try
{
ob_start();
echo "これは見えちゃだめ!";
ob_start();
echo "これは見えちゃだめだってば!";
throw new Exception();
}
catch ( Exception $e )
{
}
echo "こっちが先。";
これは見えちゃだめ!これは見えちゃだめだってば!こっちが先。
class MyException extends Exception
{
public function __construct()
{
echo "例外のコンストラクタだよ。";
}
}
try
{
ob_start();
echo "これは見えちゃだめ!";
throw new MyException();
}
catch ( MyException $e )
{
}
echo "こっちが先。";
これは見えちゃだめ!例外のコンストラクタだよ。こっちが先。
try
{
ob_start();
echo "これは見えちゃだめ!";
throw new Exception();
}
catch ( Exception $e )
{
$content = ob_get_clean();
}
echo "こっちが先。";
echo $content;
こっちが先。これは見えちゃだめ!
register_shutdown_function()関数は、PHPのプロセス終了直前にコールバックする関数を登録するものです。用途によっては便利な機能なのです。この関数についてふと疑問が。「register_shutdown_function()はいりこ状にできるのか」というものです。気になったのでやってみました。
<?php
error_reporting(-1);
ini_set('display_errors', 1);
register_shutdown_function('shutdown');
function shutdown()
{
var_dump(__FUNCTION__);
register_shutdown_function('shutdown_recursive');
}
function shutdown_recursive()
{
var_dump(__FUNCTION__);
register_shutdown_function('shutdown_recursive_recursive');
}
function shutdown_recursive_recursive()
{
var_dump(__FUNCTION__);
}
string(8) "shutdown" string(18) "shutdown_recursive" string(28) "shutdown_recursive_recursive"
エラーも出ずに、あっさりと出来てしまいました。まずこんな実装はないと思いますが、「register_shutdown_function()はいりこ状にできる」というのは覚えておくといいかもしれません。
<?php
error_reporting(-1);
ini_set('display_errors', 1);
$null = null;
$null += 10;
var_dump($null); // int(10)
$null = null;
$null .= 'hoge';
var_dump($null); // string(4) "hoge"
$null = null;
$null['hoge'] = 1;
var_dump($null);
/*
array(1) {
["hoge"]=>
int(1)
}
*/
$null = null;
$null->hoge = 1;
var_dump($null);
/*
Strict Standards: Creating default object from empty value
object(stdClass)#1 (1) {
["hoge"]=>
int(1)
}
*/
NULLは数値や文字列はもちろん、配列にもオブジェクト(stdClass)にもなんだってなれます。さすが、型がゆるい言語。
氷川 XOOPS Module 開発室