1. #1
    Membre éclairé
    Inscrit en
    avril 2005
    Messages
    1 045
    Détails du profil
    Informations forums :
    Inscription : avril 2005
    Messages : 1 045
    Points : 797
    Points
    797

    Par défaut Pas assez de mémoire pour récupérer un BSTR de 50MB d'un objet COM

    Bonjour,

    J'ai un projet ASP vieux d'au moins 10 ans qui est en train d'être réécrit en MVC.
    Il utilise entre autres des ressources externes dont des objets COM accessibles via des services.
    Ça tourne comme une horloge depuis des années en ASP sans anicroche (même sous 32bits).

    La réécriture en MVC est bien avancée et toutes les fonctionnalités externes à IIS et MVC sont accessibles et opérationnelles, dont ces objets COM.
    Sauf que sous MVC on a des Out Of Memory inexplicables, et pourtant les serveurs sont bourrés de mémoire et leurs applications tournent en 64bits (dont IIS et le framework .NET, je suppose et j'espère).

    Les Out Of Memory sont reproductibles et on croit savoir où ils ont lieu.
    Lors d'un appel COM, une méthode renvoit un BSTR et, parfois mais pas toujours, lorsque ce BSTR contient quelques dizaines de mégas (au delà de 40MB) le Out Of Memory se produit après le return de la méthode et avant de rendre la main à MVC. Le core .NET ne parvient pas à trouver suffisamment de mémoire pour convertir ces quelques malheureux mégas de BSTR en System.String ().

    En bref, il reste une bonne dizaine de GB physique de disponible par l'OS mais le core .NET ne les voit/prend pas.
    J'ai placé quelques System.GC.Collect(), mais ça ne change rien.

    Nous avons des serveurs de test sur lesquelles les deux applications ASP et MVC tournent en même temps. La vieille version ASP ne pose aucun soucis, et les objets COM répondent à toutes les requêtes via des scripts de stresstest. Le problème semble bien se situer du coté de .NET (ou MVC).

    Je suis développeur C++, donc un peu perdu avec le GC de .NET que je suppose performant et apte à utiliser tout ce que l'OS et le hardware dispose, comme en C++.
    Pour moi, c'est donc incompréhensible qu'une application 64bits tournant sur une machine récente bien pourvue en mémoire ne puisse pas allouer quelques mégas alors qu'une ancienne technologie sous 32bits et vieille de 20 ans (ASP) fonctionne très bien dans des conditions semblables, et fonctionnait même aussi bien sur du hardware plus ancien et moins bien pourvu.

    Merci pour votre aide.

  2. #2
    Rédacteur/Modérateur

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    juillet 2016
    Messages
    1 416
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : juillet 2016
    Messages : 1 416
    Points : 4 774
    Points
    4 774
    Billets dans le blog
    5

    Par défaut

    Bonjour,

    Citation Envoyé par camboui Voir le message
    Les Out Of Memory sont reproductibles et on croit savoir où ils ont lieu.
    Lors d'un appel COM, une méthode renvoit un BSTR et, parfois mais pas toujours, lorsque ce BSTR contient quelques dizaines de mégas (au delà de 40MB) le Out Of Memory se produit après le return de la méthode et avant de rendre la main à MVC. Le core .NET ne parvient pas à trouver suffisamment de mémoire pour convertir ces quelques malheureux mégas de BSTR en System.String ().
    Besoin d'un petit éclaircissement sur ce point, notamment sur le "parfois mais pas toujours". Si j'ai bien compris, parfois, un appel avec un BSTR de 40Mo passe, et parfois il génère un OutOfMemoryException. est-ce bien cela ? Si c'est le cas, il serait intéressant de regarder la consommation mémoire de l'application. Si celle-ci croit de manière importante de manière plus ou moins continue, alors il y a sans doute un problème de "fuite de mémoire" (ce qui en .Net se résume à un objet qui est toujours référencé par au moins une variable).
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  3. #3
    Membre éclairé
    Inscrit en
    avril 2005
    Messages
    1 045
    Détails du profil
    Informations forums :
    Inscription : avril 2005
    Messages : 1 045
    Points : 797
    Points
    797

    Par défaut

    Oui, c'est bien cela. Quand on lance l'application le premier appel COM passe, et dès le deuxième l'exception OutOfMemory est levée. C'est systématiquement reproductible.
    Plus qu'un problème de fuite de mémoire, c'est plutôt un problème de fragmentation de mémoire à mon avis.

    Nous avons regardé d'un peu plus prêt, et je peux être plus précis.
    L'appel COM renvoie exactement 118518404 caractères (et donc le double en bytes). C'est plus que je ne le disais hier, mais cela n'a rien d'excessif pour les ordinateurs d'aujourd'hui. Et, je le rappelle, on n'a aucun soucis en ASP ou avec des scripts VB ou JS depuis plus de dix ans.

    Le code simplifié ressemble à ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    string GiveItAll(int param)
    {
    MyCOM comObj = new MyCOM();
    string s = comObj.MethodCom(param);
    return s;
    }
    Sur les machines de développement et en debug pas à pas (VS2015), le process iisexpress.exe passe de 400MB consommé à 1,3GB juste après le retour de l'appel de la méthode (à la grosse louche 1 giga de plus). Déjà là on se demande pourquoi autant de mémoire... C'est comme si le système avait besoin de 4x la quantité de mémoire nécessaire au stockage d'une string.

    Ceci étant, on a trouvé une piste pour résoudre le problème. De mon point de vue, SI les processus tournent en 64bits ce n'est pas raisonnable d'avoir ce genre d'exception sur des machines disposant de 16GB de mémoire physique dont plus de la moitié est encore disponible (nos machines de développement).
    J'ai donc demandé aux "experts" qui développent en .NET de s'assurer que les processus sont bien en 64bits ce à quoi on m'a répondu que c'était sensé être "automatique", qu'en .NET il n'y a pas de notion 32 ou 64 bits, que le target "AnyCPU" voulait bien dire ce qu'il voulait dire, et que les langages pourvus de GC était une bénédiction palliant définitivement aux problèmes de gestion de mémoire (tu parles...).

    Bref, en fouillant les options de "compilation" des projets dans VS2015 on peut forcer le target à 64bits au lieu de AnyCPU. Ce qu'on a fait et... il n'y a plus d'exception levée, ça marche ! Mais uniquement sur les machines de développement. On doit encore comprendre pourquoi la publication du même code vers les machines de test lève toujours la même exception (où, si l'on en croit le gestionnaire de tâche de l'OS, le processus w3wp.exe est 64bits).

    Pour info:
    - environnement de développement: Windows 10 (iisexpress.exe en 64 bits)
    - environnement de test: Windows Server 2012 R2 (w3wp.exe en 64bits)
    - environnement de production: Windows Server 2012 R2 (w3wp.exe en 32bits !)

    Les machines de production ont été changées il y a 2-3 ans. Les anciennes étaient des Windows Server 2003. On a jamais eu de soucis majeur de mémoire depuis le début du projet ASP il y a une bonne quinzaine d'années.

  4. #4
    Rédacteur/Modérateur

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    juillet 2016
    Messages
    1 416
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : juillet 2016
    Messages : 1 416
    Points : 4 774
    Points
    4 774
    Billets dans le blog
    5

    Par défaut

    Plusieurs choses, du simple au compliqué.

    Citation Envoyé par camboui Voir le message
    J'ai donc demandé aux "experts" qui développent en .NET de s'assurer que les processus sont bien en 64bits ce à quoi on m'a répondu que c'était sensé être "automatique", qu'en .NET il n'y a pas de notion 32 ou 64 bits, que le target "AnyCPU" voulait bien dire ce qu'il voulait dire, et que les langages pourvus de GC était une bénédiction palliant définitivement aux problèmes de gestion de mémoire (tu parles...).
    Ce n'est absolument pas automatique ! AnyCPU signifie que le programme peut cibler aussi bien une architecture 32 bits que 64bits, mais le programme reste compilé à la volée en ciblant une architecture (32 bits pour les architectures 32 bits, et 64bits pour les architectures 64bits). Pour compliquer les choses, il existe une petite case, qui permet de "préférer les architectures 32bits", qui fait que même sur une architecture 64bits, on utilisera du 32bits !

    L'intérêt de ce flag est de permettre de garder le AnyCPU, et ainsi de compiler également pour les architectures ARM, tout en forçant la compilation pour les architectures 32bits.

    Et l'intérêt des langages à ramasse-miette est effectivement de simplifier grandement la gestion de la mémoire :
    • en empêchant les fuites de mémoire en délestant le programmeur de la libération de la mémoire ;
    • en sécurisant les accès aux pointeurs (impossible d'accéder à une zone mémoire libérée, impossible de libérer plusieurs fois un pointeur, etc...).


    Du coup, un ramasse-miette évite de nombreux problèmes et bogues liés à la gestion de la mémoire. Mais comme chaque technologie, un ramasse-miette a des limites !

    Citation Envoyé par camboui Voir le message
    Bref, en fouillant les options de "compilation" des projets dans VS2015 on peut forcer le target à 64bits au lieu de AnyCPU. Ce qu'on a fait et... il n'y a plus d'exception levée, ça marche ! Mais uniquement sur les machines de développement. On doit encore comprendre pourquoi la publication du même code vers les machines de test lève toujours la même exception (où, si l'on en croit le gestionnaire de tâche de l'OS, le processus w3wp.exe est 64bits).

    Pour info:
    - environnement de développement: Windows 10 (iisexpress.exe en 64 bits)
    - environnement de test: Windows Server 2012 R2 (w3wp.exe en 64bits)
    - environnement de production: Windows Server 2012 R2 (w3wp.exe en 32bits !)
    Voilà de bonnes informations. Effectivement, vu la consommation mémoire en général, ce n'est pas étonnant que sur une architecture 32bits le programme tombe avec un OutOfMemoryException (où le programme ne peut adresser en théorie que 2Go de mémoire, et qu'en pratique encore moins est utilisable).

    L'environnement de production est en 32 bits, ce qui explique sans doute pourquoi l'erreur persiste.

    Citation Envoyé par camboui Voir le message
    Sur les machines de développement et en debug pas à pas (VS2015), le process iisexpress.exe passe de 400MB consommé à 1,3GB juste après le retour de l'appel de la méthode (à la grosse louche 1 giga de plus). Déjà là on se demande pourquoi autant de mémoire... C'est comme si le système avait besoin de 4x la quantité de mémoire nécessaire au stockage d'une string.
    Cette augmentation de mémoire comprends certainement plusieurs choses :
    • la mémoire allouée par le composant COM (mémoire native);
    • le string (mémoire managée).


    Bref, vu la taille de la chaîne de caractère (plus de 100 000 000 caractères), on compte déjà 200Mo en mémoire native et 200Mo en mémoire managée. Pour peu qu'il y ait une manipulation ou deux supplémentaires du côté du composant ou du côté .Net, et on a vite fait d'allouer un autre buffer de 200Mo. Pour moi, rien de forcément inquiétant à ce stade.

    Plus qu'un problème de fuite de mémoire, c'est plutôt un problème de fragmentation de mémoire à mon avis.
    Pas forcément. Si architecture 32bits, et qu'un appel alloue 1Go de RAM, un deuxième appel échouera forcément.
    Mais sinon, effectivement, la fragmentation peut jouer à ce niveau. S'il n'y a pas grand chose à faire du point de vue du composant COM, du point de vue managé, il est possible d'agir. Le garbage collector compacte les objets en mémoire de sorte que l'espace libre soit toujours defragmenté... sauf lorsque les objets dépassent une certaine taille (un peu plus de 80ko). Mais depuis le framework 4.5, il est possible de forcer ce compactage. Bien évidemment, ce compactage s'accompagne d'un impact négatif possible au niveau des performances.

    Pour forcer le compactage des objets larges, il faut utiliser le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
    GC.Collect();
    La première ligne indique qu'on souhaite que la prochaine collecte complète compacte les objets larges. La seconde lance une collecte complète.

    Attention : après une collecte complète, la propriété GCSettings.LargeObjectHeapCompactionMode reprend sa valeur par défaut. Il faut donc la réinitialiser à CompactOnce si on souhaite à nouveau procéder à un compactage des objets larges.
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  5. #5
    Membre éclairé
    Inscrit en
    avril 2005
    Messages
    1 045
    Détails du profil
    Informations forums :
    Inscription : avril 2005
    Messages : 1 045
    Points : 797
    Points
    797

    Par défaut

    Merci pour tes réponses

    Citation Envoyé par François DORIN Voir le message
    Pour info:
    - environnement de développement: Windows 10 (iisexpress.exe en 64 bits)
    - environnement de test: Windows Server 2012 R2 (w3wp.exe en 64bits)
    - environnement de production: Windows Server 2012 R2 (w3wp.exe en 32bits !)
    Voilà de bonnes informations. Effectivement, vu la consommation mémoire en général, ce n'est pas étonnant que sur une architecture 32bits le programme tombe avec un OutOfMemoryException (où le programme ne peut adresser en théorie que 2Go de mémoire, et qu'en pratique encore moins est utilisable).

    L'environnement de production est en 32 bits, ce qui explique sans doute pourquoi l'erreur persiste.
    Euh, je me suis mal fait comprendre. Il n'y a pas d'erreur en production puisque le projet MVC est toujours en phase de développement.
    Je précisais simplement un peu le contexte actuel de travail, pour info.

    En production on n'a que la version ASP du projet qui tourne, et qui fonctionne très bien (en 32bits de surcroît).
    La migration de tout le projet en MVC est en cours de développement. Donc pas encore prête pour la prod.
    Les composants COM sont rigoureusement les mêmes dans tous les environnements (dev, test, et prod).

    Ainsi, en développement on a réussi à régler ce soucis de mémoire, mais pas encore en test (c'est un environnement de préproduction qui nous permet de faire des stresstest et de montrer au client les étapes successives d'avancement du projet).
    Et comme préciser, en test, le prg w3wp.exe est 64bits. Donc on ne comprend pas pourquoi cela continue de planter sur les serveurs web de test.

    Je vais essayer l'astuce du CompactOne. Mais je doute que se soit ça si on est bien en 64bits. Et je ne comprends pas pourquoi ce ne serait pas le cas puisque toutes les DLL compilées sur la machine de développement sont recopiées telles quelles en test.

  6. #6
    Rédacteur/Modérateur

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    juillet 2016
    Messages
    1 416
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : juillet 2016
    Messages : 1 416
    Points : 4 774
    Points
    4 774
    Billets dans le blog
    5

    Par défaut

    Citation Envoyé par camboui Voir le message
    Euh, je me suis mal fait comprendre. Il n'y a pas d'erreur en production puisque le projet MVC est toujours en phase de développement.
    Je précisais simplement un peu le contexte actuel de travail, pour info.

    En production on n'a que la version ASP du projet qui tourne, et qui fonctionne très bien (en 32bits de surcroît).
    La migration de tout le projet en MVC est en cours de développement. Donc pas encore prête pour la prod.
    Les composants COM sont rigoureusement les mêmes dans tous les environnements (dev, test, et prod).
    Mea culpa, c'est moi qui ait effectivement mal compris !

    Citation Envoyé par camboui Voir le message
    Ainsi, en développement on a réussi à régler ce soucis de mémoire, mais pas encore en test (c'est un environnement de préproduction qui nous permet de faire des stresstest et de montrer au client les étapes successives d'avancement du projet).
    Et comme préciser, en test, le prg w3wp.exe est 64bits. Donc on ne comprend pas pourquoi cela continue de planter sur les serveurs web de test.
    A chaud, j'irai jeter un oeil du côté de la configuration du pool d'application. Il est possible de définir des limites quant à la mémoire utilisable.

    Et je vérifierai encore une fois que le pool d'application est bien en 64bits : il y a un programme w3wp.exe par pool d'application et s'il y a plusieurs pools d'application (disons 2, un en 32 et un autre en 64bits), l'un des programmes tournera en 32 bits, et l'autre en 64 bits. Il faut donc s'assurer que le programme w3wp.exe détecté correspond bien au bon pool d'application.
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  7. #7
    Membre éclairé
    Inscrit en
    avril 2005
    Messages
    1 045
    Détails du profil
    Informations forums :
    Inscription : avril 2005
    Messages : 1 045
    Points : 797
    Points
    797

    Par défaut

    Encore merci. Mais rien n'y fait...
    Tout les services Web sont bien en 64bits, aucune limite spécifiée quant à la mémoire utilisée dans la configuration.

    J'ai aussi recompilé le service qui distribue les objets COM (migration du projet C++ développé en VS2005 à l'origine vers VS2015, ce qui ne fut pas sans mal...).

    Nous sommes le bec dans l'eau
    Ne serait-il pas judicieux de remonter ce sujet vers une section plus générale dotnet/C# ?
    Je pense que ce n'est pas spécifique à MVC.
    Merci.

  8. #8
    Modérateur
    Avatar de DotNetMatt
    Homme Profil pro
    CTO
    Inscrit en
    février 2010
    Messages
    3 352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : CTO
    Secteur : Finance

    Informations forums :
    Inscription : février 2010
    Messages : 3 352
    Points : 8 862
    Points
    8 862
    Billets dans le blog
    3

    Par défaut

    Je ne suis pas tres pointu en COM et code non manage, mais as-tu essaye d'utiliser Marshal.PtrToStringBSTR Method (IntPtr), suivi eventuellement par Marshal.FreeBSTR Method (IntPtr) ?
    Less Is More
    Pensez à utiliser les boutons , et les balises code
    Desole pour l'absence d'accents, clavier US oblige

  9. #9
    Rédacteur/Modérateur

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    juillet 2016
    Messages
    1 416
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : juillet 2016
    Messages : 1 416
    Points : 4 774
    Points
    4 774
    Billets dans le blog
    5

    Par défaut

    Question à deux euros : quelle est la capacité en RAM des différentes machaines ?
    • dev
    • test
    • prod
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  10. #10
    Membre éclairé
    Inscrit en
    avril 2005
    Messages
    1 045
    Détails du profil
    Informations forums :
    Inscription : avril 2005
    Messages : 1 045
    Points : 797
    Points
    797

    Par défaut

    Citation Envoyé par DotNetMatt Voir le message
    Je ne suis pas tres pointu en COM et code non manage, mais as-tu essaye d'utiliser Marshal.PtrToStringBSTR Method (IntPtr), suivi eventuellement par Marshal.FreeBSTR Method (IntPtr) ?
    Merci.
    Mais la méthode importée pour le code C# depuis le fichier tlb me renvoie un string (managé donc) et non pas un BSTR.
    Le cast vers un IntPtr est refusé.
    La signature de ma méthode en C++ est comme ceci long MethodCom(long param, BSTR *s) mais C# la voit comme cela string MethodCom(long param).
    Je suppose qu'il faudrait faire un import de plus bas niveau, mais j'ignore comment.

    Machine de test et prod: 8GB
    Machine de dev: 16GB

    En commençant la migration, nous utilisions les binaires originaux du code COM en C++, c'est-à-dire compilé à l'époque sous VS2005 (et en 32 bits).
    Je rappelle que ces binaires fonctionnent très bien en liaison avec ASP, aussi bien sur d'anciennes machines que sur des récentes, et anciens OS Windows ou récents.
    Comme je l'ai dit dans mon précédant post, je viens de les recompiler sous VS2015 (toujours en 32 bits). J'ai activé le "Large Address Aware" et... plus de Out Memory ! Ça semble fonctionner !
    J'ai du mal à comprendre, mais si ça résoud le problème tant mieux

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Pas assez de mémoire pour W98 ?!
    Par Yepazix dans le forum Windows 2000/Me/98/95
    Réponses: 3
    Dernier message: 28/04/2011, 19h10
  2. Pas assez de mémoire MySQL ?
    Par sliderman dans le forum SQL Procédural
    Réponses: 3
    Dernier message: 12/12/2007, 10h13
  3. Pas assez de mémoire pour exécuter un code
    Par med_ellouze dans le forum Langage
    Réponses: 6
    Dernier message: 11/08/2007, 03h51
  4. IE7 me dit que j'ai pas assez de mémoire?!
    Par haltabush dans le forum CKeditor
    Réponses: 2
    Dernier message: 15/02/2007, 17h37

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo