Отладка утечки памяти, загрузка потокового файла PHP и MySQL - PullRequest
3 голосов
/ 31 октября 2011

с использованием MAMP v2.0 на Mac _ _ Apache / 2.0.64 (Unix) - PHP / 5.3.5 - DAV / 2 mod_ssl / 2.0.64 - OpenSSL / 0.9.7l- MySQL 5.5.9

У меня есть скрипт, который я пытаюсь запустить, и, похоже, он дает мне серьезные утечки памяти, которые я пытался отладить и не могу решить, как исправить.

По сути, скрипт является частью модуля файлового менеджера.Он обрабатывает загрузку файла, когда ему присваивается идентификатор.

Весь файл хранится в таблице базы данных в виде большого двоичного объекта в кусках по 64 КБ (на запись) и передается клиенту по запросу.

База данных: 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 - дата и время
FileUploadBy - int (5)

file_details :
FileDataID - int (10) AUTO_INCREMENT
FileID - int (10)
FileData - BLOB

На самом деле я получаю ошибку (из журнала ошибок php):

[31 октября2011 09:47:39] Неустранимая ошибка PHP: допустимый объем памяти 134217728 байт исчерпан (попытался выделить 63326173 байт) в / root / htdocs / file_manager / file_manager_download.php в строке 150

Теперь фактическая функция загрузки работает, если файл достаточно мал, в данном случае менее 40 МБ, однако, если он превышает этот размер, например, файл 60 МБ вошибка выше, это не удается.Все, что он делает, это загружает файл размером 0 КБ.

Очевидно, что 134217728 байт превышает 63326173 байта (128 МБ против 60 МБ).

Допустимый объем памяти 134217728 байт - этодиректива в php.ini: «memory_limit = 128M; максимальный объем памяти, который скрипт может потреблять»

Если я установлю это значение 256M, это позволит мнезагрузить этот файл 60 МБ, а также примерно до 80 МБ файла.

Кроме того, если я установлю это значение на 1024 МБ, это позволит мне загрузить файл 260 МБ и, возможно, больше.

Таким образом, вы можетевидите, что проблема заключается в утечке где-то в скрипте, который поглощает всю память.

Вот скрипт загрузки:



    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.<br>Your Query: " . $sql_GetFileDetails . "<br> Error: (" . mysql_errno() . ") " . mysql_error());

    if (mysql_num_rows($result_GetFileDetails) != 1) { die ("A MySQL error has occurred.<br>Your Query: " . $sql_GetFileDetails . "<br> 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<br />Your Query: " . $sql_GetFileDataNodeIDs . "<br /> 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<br />Your Query: " . $sql_GetFileDataBlobs . "<br /> 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 и выводил результаты для пикаиспользование памяти, но, похоже, ничто не приближается к пределам, в целом пиковое использование памяти для страницы было около 900 КБ.

Так что я думаю, что это объединяет куски файлов в память и не позволяет имидти или что-топохоже, но куски файлов - это единственное, что может достичь такого объема памяти, что приведет к сбою сценария.

Я могу предоставить сценарий для загрузки файла в базу данных, чтобы вы могли проверить мой сценарий, если выкак, просто дайте мне знать

Ура за любую помощь!

Мик


* ///////// решено ///////// *

Я просто хочу сказать спасибо hafichuk, отличный ответ и решил всю мою проблему.

Проблема была в два раза.

1 - я не использовал ob_flush () внутри цикла while.Я добавил это, и это, казалось, высвободило много памяти, позволяя загружать больше, но не без ограничений.

Например, с memory_limit = 128M я мог теперь загрузить более 40 Мб, фактически я мог получитьдо около 200 МБ.Но здесь это снова не удалось.Первая проблема с памятью решена.

УРОК 1: Очистите ваши объекты!

2 - Я использовал mysql_query для получения результатов для моего SQL-запроса.Проблема в том, что он буферизует эти результаты, и это добавило к моей проблеме с ограничением памяти.

Я вместо этого использовал mysql_unbuffered_query, и теперь это работает безупречно.

Это, однако, имеет некоторые ограничения, что он блокирует вашу таблицу при чтении результатов.

УРОК 2: Не буферизируйте результаты mysql, если не требуется!(в рамках программных ограничений)

ЗАКЛЮЧИТЕЛЬНЫЙ УРОК:

Все эти исправления работают, однако требуется дополнительное тестирование, чтобы убедиться в отсутствии проблем с их комбинацией.

Кроме того, я узнал намного больше об объектах и ​​распределении памяти php, я просто хотел бы, чтобы был способ визуальной отладки процесса немного лучше, чем предлагает xdebug.Если у кого-то есть идеи о том, как xdebug мог бы пролить свет на этот процесс, пожалуйста, дайте мне знать в комментариях.

Надеюсь, что это поможет кому-то еще в будущем.

Приветствия

Мик

1 Ответ

1 голос
/ 31 октября 2011

Вам просто нужно сделать ob_flush () в вашем цикле while. Это очистит буфер для страницы. Ваш последний заголовок с указанием длины контента необходимо будет удалить, поскольку вы не можете отправить заголовок после запуска данных. Это не должно быть проблемой при загрузке файла, только обновление индикатора выполнения загрузки.

...