8791 sujets

Développement web côté serveur, CMS

Bonjour,

Voilà quand j'exécute mon script je rencontre le message ci-dessous:

[i]Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 4294967296 bytes) in /home/******/public_html/*****/ma_page.php on line 295[/b]

Donc à part le fait de me douter qu'il s'agit d'un problème de mémoire php allouée insuffisante, je voudrais savoir comment faire pour pallier à ce problème:
1 - comment augmenter la taille de la mémoire allouée ? ==> ini_set('memory_limit', nnn) en début de traitement c'est ça ?

2 - faut-il que je suive les recommandations du message en allouant 4096 Mo !! J'vais faire sauter la planète avec ça non ?!

A savoir que mon script a pour but (à terme) de traiter des fichiers ZIP assez balèses contenant images et data.
Là le test que j'ai fait (et où j'ai le message d'erreur) est avec un fichier:
- de 10Mo environ
- contenant 366 images de 2 à 50ko pour la majorité (exceptionnellement 150ko max)
- contenant un fichier de data de 40ko

La ligne 295 (là où j'ai l'erreur fatale) correspond au moment où j'extrais chaque fichier du ZIP à destination d'un répertoire de traitement.

Merci par avance pour votre aide.

Claw
Modérateur
Alors ton code explose la quantité de mémoire, souvent pour deux raisons:

- Une boucle qui n'en finit pas (bug de code).
- Utilisation de fonctionnalités gourmandes ( gd, décodage/encodage, etc. ) sur des fichiers volumineux.

Dans ton cas ça peut être les deux.

Les options:

- Optimiser ton code, peut-être que tu pourrais à certains moments libérer de la mémoire. (Le dev php a généralement la mauvaise habitude de ne pas libérer la mémoire, unset et les différents closes sont tes amis )
- Augmenter la mémoire (4096Mo, c'est trop Smiley eek ).
- Laisser faire de grosses opérations par des scripts adaptés ( shell, perl, imagemagick, etc. )
Modifié par kustolovic (24 May 2012 - 16:20)
Merci Kustolovic !

En fait pas de bug à signaler car j'avais testé avec succès mon script avec un petit fichier test (quelques images et données).

Et pour ce qui est des traitements, en gros c'est une grande boucle qui traite chaque data lue, et qui fait pas mal de petites opérations pour chacune ... la + gourmande sans doute est en effet l'utilisation de GD pour créer 2 fichiers images (formats différents) à partir de l'image lue. Et là j'en ai 366 d'images !

Par contre c'est vrai que je n'ai fait aucun "close" pour mes requêtes préparées PDO.
Je suppose qu'il faut faire un "$ma_requete->closeCursor();" après chaque requête c'est ça ?
Pour unset(), faut voir mais dans mon cas je ne sais pas si je vais pouvoir en "unseter" bcp.

J'connais pas shell, perl, et cie ! Smiley decu


Dans quelle mesure je peux augmenter ma mémoire (paramètre "memory_limit" je suppose)?
Et est-ce qu'il vaut mieux passer par un "ini_set('memory_limit', nnn)" (je pense) ou de carrément modifier le paramètre dans le .htaccess (sachant que mon script en question est un script qui s'exécute en parallèle de mon site, donc pour mon site en lui-même les 256Mo que j'ai devraient suffire au "quotidien" )?

Merci par avance.

Claw69
Modérateur
Claw69 a écrit :
Et pour ce qui est des traitements, en gros c'est une grande boucle qui traite chaque data lue, et qui fait pas mal de petites opérations pour chacune ... la + gourmande sans doute est en effet l'utilisation de GD pour créer 2 fichiers images (formats différents) à partir de l'image lue. Et là j'en ai 366 d'images !


Il faudrait voir le code pour se rendre compte, mais si il y a bien du GD, je sens l'embrouille.
Pour les close je pensais aux fichiers ouverts. Pour les images imagedestroy est ton ami, sinon tu as à la fin toutes les images chargées en mémoire.

Claw69 a écrit :
J'connais pas shell, perl, et cie ! Smiley decu

Je ne peux pas t'aider, d'autres font ça pour moi.

Claw69 a écrit :
Et est-ce qu'il vaut mieux passer par un "ini_set('memory_limit', nnn)" (je pense) ou de carrément modifier le paramètre dans le .htaccess (sachant que mon script en question est un script qui s'exécute en parallèle de mon site, donc pour mon site en lui-même les 256Mo que j'ai devraient suffire au "quotidien" )?

Si ce n'est que pour ce script, mieux vaut le modifier en effet au besoin. Pour l'histoire, «carrément», c'est dans la configuration d'apache. htaccess est déjà une solution locale.
Modifié par kustolovic (24 May 2012 - 17:02)
En fait pour les close, pour chaque fichier lu contenu ds mon ZIP, je fais bien un
fclose(); et un zip_entry_close().

Quand tu dis par rapport à GD que tu sens l'embrouille, tu vx dire que mes problèmes de mémoire ne sont pas illogiques c'est ça ?

OK j'vais faire des tests avec init_set() mais j'ai peur de mettre une trop grande valeur. Tu aurais un avis sur la valeur à préciser ?

Pour la "solution" via .htaccess, c'est ce que j'ai lu chez mon hébergeur qui conseille d'augmenter le paramètre via ce fichier MAIS je pense que c'est + pour une problématique de site inacessible et pas que pour un problème sur un seul script comme moi.

EDIT : 17h44:
par contre c'est pas bête ton "imagedestroy()" ...
Je suppose qu'il faut le placer après un
<?php
imagecopyresampled($destination, $source, 0, 0, 0, 0, $largeur_destination, $hauteur_destination, $largeur_source, $hauteur_source);
?>

Mais est-ce que je dois faire après 2 imagesdestroy() comme ci-dessous ?
<?php
imagedestroy($destination);
imagedestroy($source);
?>

Merci par avance.
Modifié par Claw69 (24 May 2012 - 17:44)
Tu peux faire un appel à imagedestroy() sur toutes les ressources GD lorsque tu n'en a plus besoin.
D'ailleurs ce n'est pas "tu peux", mais "tu dois" Smiley smile
Donc aucun problème à faire plusieurs appels de suite sur différentes ressources.
moust a écrit :
Tu peux faire un appel à imagedestroy() sur toutes les ressources GD lorsque tu n'en a plus besoin.
D'ailleurs ce n'est pas &quot;tu peux&quot;, mais &quot;tu dois&quot; Smiley smile


+12

Je pense que ton problème viens certainement de là. Ce n'est pas normal que le script te demande 4 Go de RAM pour dezipper une archive de 10 Mo. Au passage PHP est un "process" 32 bit donc il ne pourra jamais allouer autant de mémoire vive en un seul bloc, quelle que soit la machine sur laquelle il tourne.

En pratique, faire des unset() et close pour libérer de la mémoire en cours d’exécution d'un script : si c'est pour libérer un nombre (entier ou flottant) ou une chaine ou de petites variables, ça ne sert à rien, par contre sur des objets ou des tampons mémoire plus lourd comme des images ou des archives c'est absolument obligatoire.
Modifié par jb_gfx (25 May 2012 - 00:52)
Merci à vous 2, moust et jb_gfx, je vous avoue que ça me redonne espoir Smiley smile , car là j'étais déjà en train d'étudier solutions d'hébergement VPS ou dédiés.

Donc après chaque image traitée, je vais faire des
imagedestroy($destination);
imagedestroy($source);

Pour les unset(), je ne suis pas sûr d'avoir compris: est-ce qu'il faut que je place en dessous des 2 lignes imagedestroy() ci-dessus, 2 unset() comme ci-dessous ?:
unset($destination);
unset($source);

Par rapport à mon besoin est-ce que vous pensez que 256 Mo de RAM suffisent ?
Il est prévu que pour l'exécution d'un seul traitement (mon script), il y ait "n" fichiers zip de tant de Mo à traiter .
J'attends une réponse d'un partenaire pour savoir de quel volume max (en Mo) peuvent avoir ces fichiers?

Merci pour votre aide. Smiley smile

Claw
Modérateur
Alors unset détruit la variable (et donc libère la place réservée à son contenu).

Dans le cas d'une image, la ressource $im définie comme suit:

$im = imagecreatefromjpeg( … );

$im est une variable qui content un numéro (entier) qui fait référence à la ressource image chargée en mémoire. Autrement dit ce n'est pas l'image chargée en mémoire mais une référence.

si je fais à nouveau:

$im = imagecreatefromjpeg( … );

je charge une seconde une image en mémoire, et j'écrase l'ancienne référence par une nouvelle, ce qui fait que j'ai deux images chargées en mémoire dont une à laquelle je ne sais plus accéder.

imagedestroy permet au contraire de vider cette espace mémoire. Alors que unset($im) supprimera la référence à l'image, qui ne pèse pas bien lourd. Dans le cas d'une image, tu peux sauter le unset.
OK ça marche j'ai fait mes 1ers tests avec imagedestroy() (source et destination) et je n'ai pas mis unset().

'y a du mieux .

J'ai fait un test en reprenant mon fichier zip de 10Mo mais ne contenant QUE les images ( 366 fichiers pour un total de 9.9Mo) et pas le fichier de data (39ko).

Résultat: même s'il manque le fichier de data pour que mon traitement se passe bien, je n'ai par contre plus ma "Fatal error: Allowed memory size" et mes 366 images ont bien toutes été extraites et déplacées dans le répertoire que je veux.

Donc j'en déduits que le problème vient du traitement de mon fichier de données (fichier XML).
La fatal error se situe sur la ligne du fwrite() à partir de ce fichier.


Ci-dessous mon code:

<?php
// on extrait et créer le fichier dans le répertoire de destination (temp/)
if (zip_entry_open($zip_file, $zip_entry, "r"))
{																	

	$fd = fopen($path_temp.$in_zip_file_name, 'w');

	fwrite($fd, zip_entry_read($zip_entry, zip_entry_filesize($zip_entry))); <== FATAL ERROR
																		
	fclose($fd);
	zip_entry_close($zip_entry);

	$xml_file_name = $in_zip_file_name;

}
?>


Qu'est-ce que je peux faire ?
Je vois pas trop quoi faire . Smiley ohwell

EDIT - 15h42:
ce que je peux ajouter c'est que malgré l'erreur j'ai bien un fichier de données qui se créé avec le bon nom et le bon répertoire de destination mais ce dernier est à 0.
FIN EDIT

Merci .

Claw
Modifié par Claw69 (25 May 2012 - 15:43)
Après une petite recherche dans le doc de PHP on trouve ceci :
http://www.php.net/manual/fr/ref.zip.php#96038

// get the file's size
$fileSize = zip_entry_filesize($zip_entry);
// open the target file
$outFile = fopen($file_name,"wb");
while ($fileSize>0) {
    // read/write only up to 10kb per step
    $readSize = min($fileSize,10240);

    // decrease the size of the remaining data to read
    $fileSize -= $readSize;

    // get the data
    $content = zip_entry_read($zip_entry, $readSize);

    // write the data (if any)
    if ($content !== false)
        fwrite($outFile,$content);
}
fclose($outFile);
J'ai réussi à faire passer mon fichier zip de 10Mo avec images et fichier DATA.

Comme je l'avais dit dans mon message précédent, le problème semblait s'orienter sur le fichier de data et pas les fichiers images et ça s'est confirmé!

TEST 1 = Si j'utilise le fichier zip d'origine ("mon_fichier_zip.zip") contenant:
- 366 fichiers images
- 1 "mon_fichier_data.xml"
==> PLANTAGE A CHAQUE FOIS

TEST 2 = Si j'extrais "mon_fichier_data.xml", que je le renomme en "mon_fichier_data_rename.xml" et que je le ré-intègre dans un fichier zip (copie du zip initial sans le fichier "mon_fichier_data.xml") et que je relance le traitement, CA MARCHE !! Smiley eek (à part une erreur sur une image mais ça n'a rien à voir je pense)

TEST 3 = Si maintenant je renomme "mon_fichier_data_rename.xml"
en "mon_fichier_data.xml" (même nom que dans TEST 1 donc), que je le ré-intègre dans un zip (copie du zip initial) et bien CA MARCHE AUSSI alors que c'est exactement le même fichier xml, même nom que dans TEST 1 ! Smiley eek Smiley eek

La seule différence que je vois c'est que j'extrais le fichier de data du zip d'origine, pour le renommer puis pour le ré-intégrer dans un autre zip!

Est-ce que le problème peut donc venir de la façon dont le fichier a été zippé ?
Sachant qu'on m'a envoyé ce fichier ZIP, ce n'est pas moi qui l'ait créé.

Merci par avance pour votre aide car ça me dépasse là ! Smiley ohwell


Claw

EDIT 18h00
Excuses moi MOUST je n'avais pas vu que tu avais répondu, je vais regarder ta piste.
Modifié par Claw69 (25 May 2012 - 18:03)
MOUST, j'ai essayé avec ta piste (tranches de 10240):

Je ne tombe plus sur ma "Fatal error: Allowed memory size" mais sur celle-ci désormais :
Fatal error: Maximum execution time of 90 seconds exceeded in /home/****/public_html/*****/mon_script.php on line 330

Même résultat en modifiant le temps max d'exécution à 30s, 60s, 90s

Ce que je constate c'est que mon fichier de data (xml) a presque été totalement extrait (contrairement à avant où il était vide) MAIS que ça soit pour un traitement de 30, 60 ou 90s, exactement 7979 lignes sont extraites sur 8063 lignes (ce qui correspond à 7992 bytes)... autrement dit quelque soit le temps de traitement 30, 60 ou 90s, ça plante alors que le fichier est extrait à 99% !

Donc en gros je tombe sur un traitement sans fin d'où la "Fatal error: Allowed memory size" d'origine qui subvenait avant que je fasse une lecture par tranche.

J'ai regardé entre la ligne 7980 et 8063, rien de particulier, mais pas étonnant car comme je le disais, si j'extrais manuellement ce fichier, que je le renomme ou pas, et que je le ré-injecte ds un fichier ZIP et que je relance le traitement, ça marche.

Bilan: le problème est toujours là et tout laisse à penser que ça vient de la façon dont le fichier qu'on m'a envoyé a été ZIPPé ! Enfin du moins c'est ce que je suppose, mais je sais pas du tout quoi faire une fois que j'ai dit ça.

HEEELLLPPP !!!

Merci.
Modifié par Claw69 (25 May 2012 - 19:14)