12
votes

Possible de forcer la mémoire Delphi Threadvar à libérer?

Je poursuis ce qui semble être une fuite de mémoire dans une DLL construite à Delphi 2007 pour Win32. La mémoire des variables de filvar n'est pas libérée si les threads existent toujours lorsque la DLL est déchargée (il n'y a pas d'appels actifs dans la DLL lorsqu'il est déchargé).

La question : y a-t-il un moyen de causer une mémoire Delphi à la libre mémoire associée aux variables de filetage? Ce n'est pas aussi simple que de ne pas les utiliser. Un certain nombre de composants Delphi existants les utilisent, alors même si la DLL ne les déclare pas explicitement, elle finit par les utiliser.

Quelques détails Je l'ai suivi dans un appel de la région locale qui se produit en réponse à l'utilisation d'une variable de filvar, qui est "Wrapper" de Delphi autour du stockage de fil-local dans Win32. Pour le curieux, l'appel d'allocation est dans le fichier source Delphi Sysinit.PAS. L'appel local correspondant ne se produit que pour les threads qui obtiennent des appels dll_thread_detach . Si vous avez plusieurs threads dans une application et déchargez une DLL, il n'y a aucun dll_thread_detach appel à chaque fil. La DLL obtient un dll_process_detach et rien d'autre; Je crois que cela est attendu et valide. Ainsi, toutes les allocations de stockage locales de thread-local effectuées sur d'autres threads sont divulguées.

J'ai ré-créé avec un programme C court-circuit qui commence plusieurs threads "travailleurs". Il charge la DLL (via LoadLibrary) sur le thread principal, puis apporte des appels dans une fonction exportée sur les threads de travailleur. La fonction exportée à partir de la DLL Delphi attribue une valeur à une variable d'entier de filetage et de retour. Le programme C décharge ensuite la DLL (via freelibrary sur le fil principal) et répète. Après environ 32 000 itérations, l'utilisation de la mémoire de processus indiquée dans l'explorateur de processus augmente à plus de 130 Mo. Je l'ai également vérifié plus avec précision avec UMDH. UMDH a montré 24 octets perdus par instance. Mais les 130 Mo d'explorateur de processus semblent indiquer environ 4K par itération; Je suppose qu'un segment de 4k a été fui à chaque fois en fonction de cela, mais je ne sais pas avec certitude.

Pour clarification, voici la déclaration de filvar et toute la fonction exportée: < Pré> xxx

merci.


0 commentaires

3 Réponses :


6
votes

Comme vous avez déjà déterminé, le stockage local de thread- em> sera libéré pour chaque fil qui se détache de la DLL. Cela se produit dans system._startlib code> lorsque motif code> est dll_thread_detach code>. Pour cela arriver, cependant, le fil doit se terminer. Les notifications de thread-détachent se produisent lorsque le thread se termine, non pas lorsque la DLL est déchargée. (Si c'était l'inverse, le système d'exploitation devrait interrompre le fil de fil quelque part afin de pouvoir insérer un appel à dllmain code> sur le nom du fil. Ce serait désastreux.)

la DLL est em> censé recevoir des notifications thread-détachons. En fait, c'est le modèle suggéré par Microsoft dans sa description de Comment utiliser thread-local Stockage avec dlls . p>

Le seul moyen de libérer le stockage local du thread est d'appeler TLSFree > dans le contexte du fil dont vous souhaitez stocker le stockage. De ce que je peux dire, Delphi conserve tous ses connaisseurs dans un seul index TLS, donné par la variable tlsindex code> dans sysinit.pas em>. Vous pouvez utiliser cette valeur pour appeler tlsfree code> quand vous le souhaitez, mais vous feriez mieux d'être sûr qu'il n'y aura plus de code exécuté par la DLL dans le fil actuel. P>

Étant donné que vous souhaitez également libérer la mémoire utilisée pour contenir tous les capsules threads, vous devez appeler tlsgetvalue code> pour obtenir l'adresse du tampon Delphi alloue. Appelez localfree code> sur ce pointeur. P>

Ce serait le code Delphi (non testé) pour libérer le stockage de fil-local. P>

var
  TlsBuffer: Pointer;
begin
  TlsBuffer := TlsGetValue(SysInit.TlsIndex);
  LocalFree(HLocal(TlsBuffer));
  TlsFree(SysInit.TlsIndex);
end;


1 commentaires

Ah oui - je n'avais pas compris que c'était juste un emplacement TLS par fil. Merci d'avoir fait remarquer cela. Je crois cependant que cette solution nécessiterait de faire appel à chaque fil. Et comme vous l'avez dit correctement, il n'est pas possible / souhaitable d'interrompre les autres threads de tout ce qu'ils font pour apporter un appel à TLSgetValue pour obtenir le pointeur et libérer-le. Incidemment, je crois que l'appel TLSFree se produit sur le dll_process_detach appel. Mais sachant que c'est un seul emplacement TLS par fil est utile. Je vais réfléchir à ça. marque



3
votes

Notez qu'il est clairement spécifié dans l'aide que vous devez prendre soin de vous libérer vos connaisseurs.
Vous devriez le faire dès que vous savez que vous n'en aurez plus besoin.

de l'aide:

variables dynamiques gérées normalement par le compilateur (chaînes longues, chaînes larges, des tableaux dynamiques, des variantes et des interfaces) peuvent être déclarées avec ThreadVar, mais le compilateur fait Ne libérez pas automatiquement la mémoire allouée en tas créée par chaque fil d'exécution. Si vous utilisez ces types de données dans des variables de fil, Il est de votre responsabilité de disposer de leur mémoire de l'intérieur du fil, avant que le fil se termine . Par exemple, xxx

Remarque: l'utilisation de telles constructions est découragée.

Vous pouvez libérer une variante en la définissant à une interface et une interface ou une matrice dynamique en le réglant à NIL.


1 commentaires

Le boîtier de test utilise un entier de 4 octets (qui n'a pas besoin d'être libéré); Il n'utilise aucun type de variable dynamique. La mémoire qui est en cours de fuite est la mémoire que Delphi attribue sous les couvertures pour stocker des variables de filetage.



3
votes

À risque de trop de code, voici une solution possible (pauvre) à ma propre question. En utilisant le fait que la mémoire de stockage à thread-local est stockée dans un seul bloc pour les variables de filetage (comme indiqué par M. Kennedy - Merci), ce code stocke les pointeurs alloués dans une liste, puis les libère au processus de détachement. Je l'ai écrit surtout juste pour voir si cela fonctionnerait. Je n'utiliserais probablement pas cela dans le code de production car cela rend les hypothèses sur le temps d'exécution Delphi qui pourrait changer avec différentes versions et certainement des problèmes qui manquent probablement des problèmes même avec la version que j'utilise (Delphi 7 et 2007).

Cette implémentation fait l'UMDH Heureux, il ne pense pas qu'il y ait plus de fuites de mémoire. Toutefois, si j'exécute le test dans une boucle (charge, appelez l'entrée d'appel sur un autre thread, le déchargement), l'utilisation de la mémoire, comme indiqué dans le processus, Explorer augmente toujours de manière alarmante. En fait, j'ai créé une DLL complètement vide avec seulement une DLLMain vide (qui n'a pas été appelée puisque je n'ai pas attribué à celle de Delphi's Global Dllmain Pointer ... Delhi lui-même fournit le véritable point d'entrée DLLMAIN). Une boucle simple de chargement / déchargement de la DLL a encore fui 4k par itération. Il peut donc y avoir autre chose d'autre qu'un Delphi DLL est censé inclure (le point principal de la question initiale). Mais je ne sais pas ce que c'est. Une DLL écrite en C ne se comporte pas de cette façon.

Notre code (un serveur) peut appeler des DLL écrits par des clients pour étendre la fonctionnalité. Nous décharge généralement la DLL une fois qu'il n'y a plus de références. Je pense que ma solution au problème va être d'ajouter une option pour laisser la DLL chargée de "définitivement" en mémoire. Si les clients utilisent Delphi pour écrire leur DLL, il devra transformer cette option sur (ou peut-être que nous pouvons détecter qu'il s'agit d'une Delphi DLL sur la charge ... Besoin de vérifier cela). Néanmoins, cela a été un exercice intéressant. xxx


0 commentaires