Mac で MAMP v2.0 を使用_ _ Apache/2.0.64 (Unix) -- PHP/5.3.5 -- DAV/2 mod_ssl/2.0.64 -- OpenSSL/0.9.7l -- MySQL 5.5.9
実行しようとしているスクリプトがあり、大きなメモリ リークが発生しているようです。これをデバッグしようとしましたが、修正方法がわかりません。
基本的に、スクリプトはファイル マネージャー モジュールの一部です。ID を指定すると、ファイルのダウンロードを処理します。
ファイル全体がデータベース テーブルに BLOB として 64kb のチャンク (レコードごと) で格納され、要求に応じてクライアントにストリーミングされます。
データベース: file_management
テーブル: file_details、file_data
file_details :
FileID - int(10) AUTO_INCREMENT
FileTypeID - int(10)
FileType - varchar(60)
FileName - varchar(255)
FileDescription - varchar(255)
FileSize - bigint(20)
FileUploadDate - datetime
FileUploadBy - int(5)
file_details :
FileDataID - int(10) AUTO_INCREMENT
FileID - int(10)
FileData - BLOB
私が実際に得ているエラーはこれです(phpエラーログから):
[31-Oct-2011 09:47:39] PHP 致命的なエラー: 行 150 の /root/htdocs/file_manager/file_manager_download.php で 134217728 バイトの許容メモリ サイズが使い果たされました (63326173 バイトを割り当てようとしました)
現在、ファイルが十分に小さい場合 (この場合は 40 MB 未満)、ダウンロードの実際の機能は機能しますが、上記のエラーの 60 MB ファイルのように、それを超えると失敗します。0kb ファイルをダウンロードするだけです。
明らかに、134217728 バイトは 63326173 バイトを超えています (128mb 対 60mb)。
134217728 バイトの許容メモリ サイズは、php.ini のディレクティブです: "memory_limit = 128M ; スクリプトが消費するメモリの最大量"
これを 256M に設定すると、その 60MB のファイルだけでなく、最大約 80MB のファイルをダウンロードできます。
また、これを 1024M に設定すると、260MB 以上のファイルをダウンロードできるようになります。
したがって、問題はスクリプトのどこかでリークが発生し、すべてのメモリが消費されていることがわかります。
ダウンロードスクリプトは次のとおりです。
ini_set('display_errors',1);
error_reporting(E_ALL & ~E_NOTICE);
$strDB=mysql_connect("localhost","username","password")or die ("Error connecting to mysql.. Error: (" . mysql_errno() . ") " . mysql_error());
$database=mysql_select_db("file_management",$strDB);
if (isset($_GET["id"])) {
// List of nodes representing each 64kb chunk
$nodelist = array();
// Pull file meta-data
$sql_GetFileDetails = "
SELECT
FileID,
FileTypeID,
FileType,
FileName,
FileDescription,
FileSize,
FileUploadDate,
FileUploadBy
FROM file_details WHERE FileID = '".$_GET["id"]."';";
$result_GetFileDetails = mysql_query($sql_GetFileDetails) or die ("No results for this FileID.
Your Query: " . $sql_GetFileDetails . "
Error: (" . mysql_errno() . ") " . mysql_error());
if (mysql_num_rows($result_GetFileDetails) != 1) { die ("A MySQL error has occurred.
Your Query: " . $sql_GetFileDetails . "
Error: (" . mysql_errno() . ") " . mysql_error()); }
// Set the file object to get details from
$FileDetailsArray = mysql_fetch_assoc($result_GetFileDetails);
// Pull the list of file inodes
$sql_GetFileDataNodeIDs = "SELECT FileDataID FROM file_data WHERE FileID = ".$_GET["id"]." order by FileDataID";
if (!$result_GetFileDataNodeIDs = mysql_query($sql_GetFileDataNodeIDs)) { die("Failure to retrive list of file inodes
Your Query: " . $sql_GetFileDataNodeIDs . "
Error: (" . mysql_errno() . ") " . mysql_error()); }
while ($row_GetFileDataNodeIDs = mysql_fetch_assoc($result_GetFileDataNodeIDs)) {
$nodelist[] = $row_GetFileDataNodeIDs["FileDataID"];
}
$FileExtension = explode(".",$FileDetailsArray["FileName"]);
$FileExtension = strtolower($FileExtension[1]);
// Determine Content Type
switch ($FileExtension) {
case "mp3": $ctype="audio/mp3"; break;
case "wav": $ctype="audio/wav"; break;
case "pdf": $ctype="application/pdf"; break;
//case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser.
case "png": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser.
case "jpeg": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser.
case "jpg": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser.
default: $ctype="application/force-download"; // This forces download, instead of viewing in browser.
}
// Send down the header to the client
header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
//header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
//header("Etag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
//header("Cache-Control: Expires ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=\"".$FileDetailsArray["FileName"]."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Type: ".$FileDetailsArray["FileSize"]);
ob_end_clean();
ob_start();
ob_start("ob_gzhandler");
$sql_GetFileDataBlobs = "SELECT FileData FROM file_data WHERE FileID = ".$_GET["id"]." ORDER BY FileDataID ASC;";
if (!$result_GetFileDataBlobs = mysql_query($sql_GetFileDataBlobs)) { die("Failure to retrive list of file inodes
Your Query: " . $sql_GetFileDataBlobs . "
Error: (" . mysql_errno() . ") " . mysql_error()); }
while ($row_GetFileDataBlobs = mysql_fetch_array($result_GetFileDataBlobs)) {
echo $row_GetFileDataBlobs["FileData"];
}
ob_end_flush();
header('Content-Length: '.ob_get_length());
ob_end_flush();
}
Xdebug を使用して、ピーク メモリ使用量の結果を出力しましたが、制限に近づくようなものは何もないように見えます。合計で、ページのピーク メモリ使用量は 900kb 程度でした。
したがって、ファイルのチャンクをメモリに集約して解放しない、または同様のことを考えていますが、その量のメモリに到達するのはファイルのチャンクだけであり、スクリプトが失敗します。
ファイルをデータベースにアップロードするためのスクリプトを提供できますので、必要に応じてスクリプトをテストしてください。
どんな助けにも乾杯!
ミック
* ///////// 解決済み ///////// *
hafichuk に感謝したいと思います。すばらしい反応で、問題全体が解決しました。
問題は 2 つありました。
1 - while ループ内で ob_flush() を使用していませんでした。それを追加すると、大量のメモリが解放され、より大きなダウンロードが可能になるように見えましたが、無制限ではありません.
たとえば、memory_limit = 128M の場合、40 MB 以上をダウンロードできるようになりました。実際には、約 200 MB まで取得できました。しかし、ここでまた失敗。ただし、最初のメモリの問題は解決しました。
レッスン 1: オブジェクトをフラッシュします。
2 - SQL クエリの結果を取得するために mysql_query を使用していました。問題は、これらの結果をバッファリングすることであり、これがメモリ制限の問題に追加されていました。
代わりに mysql_unbuffered_query を使用することになりましたが、これは問題なく動作するようになりました。
ただし、これにはいくつかの制限があり、結果の読み取り中にテーブルがロックされます。
レッスン 2: 不要な場合は、mysql の結果をバッファリングしないでください。(プログラムの制限内)
最終レッスン:
これらの修正はすべて機能しますが、それらの組み合わせに問題がないことを確認するには、さらにテストが必要です。
また、オブジェクトと PHP のメモリ割り当てについて多くのことを学びました。xdebug が提供するものよりも少しだけプロセスを視覚的にデバッグする方法があればいいのにと思います。xdebug が実際にこのプロセスにどのように光を当てることができたのかについて誰かがアイデアを持っている場合は、コメントでお知らせください。
これが将来他の誰かに役立つことを願っています。
乾杯
ミック