Pour un long temps, j'ai remarqué que le Win64 version de mon serveur demande une fuite de mémoire. Tout le Win32 version fonctionne très bien avec un relativement stable de la mémoire, de la mémoire utilisée par la version 64 bits augmente régulièrement, peut – être 20 mo/jour, sans aucune raison apparente (Inutile de dire que, FastMM4 n'a pas de fuite de mémoire pour deux d'entre eux). Le code source est identique entre le 32 bits et la version 64 bits. L'application est construite autour de l'Indy TIdTCPServer composant, c'est un très serveur multithread connecté à une base de données qui traite les commandes envoyées par d'autres clients avec Delphi XE2.
Je passe beaucoup de temps à l'examen de mon propre code et essayer de comprendre pourquoi la version 64 bits de fuite grande quantité de mémoire. J'ai fini par l'aide de MS des outils conçus pour suivre les fuites de mémoire comme DebugDiag et XPerf et il semble qu'il y a une erreur fondamentale dans l'Delphi 64bit RTL qui provoque des octets fuite à chaque fois qu'un fil s'est détaché d'une DLL. Ce problème est particulièrement critique pour les très multithread applications qui doivent fonctionner 24/7 sans être redémarré.
J'ai reproduit le problème avec un projet de base qui est composée par une application hôte et une bibliothèque, tous deux construits avec XE2. La DLL est lié statiquement avec l'application hôte. L'application hôte crée des threads qu'il suffit d'appeler le mannequin exporté procédure et de sortie :
Voici le code source de la bibliothèque :
library FooBarDLL;
uses
Windows,
System.SysUtils,
System.Classes;
{$R *.res}
function FooBarProc(): Boolean; stdcall;
begin
Result := True; //Do nothing.
end;
exports
FooBarProc;
L'application ordinateur hôte utilise un timer pour créer un thread qui il suffit d'appeler la procédure exporté :
TFooThread = class (TThread)
protected
procedure Execute; override;
public
constructor Create;
end;
...
function FooBarProc(): Boolean; stdcall; external 'FooBarDll.dll';
implementation
{$R *.dfm}
procedure THostAppForm.TimerTimer(Sender: TObject);
begin
with TFooThread.Create() do
Start;
end;
{ TFooThread }
constructor TFooThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
end;
procedure TFooThread.Execute;
begin
/// Call the exported procedure.
FooBarProc();
end;
Voici quelques captures d'écran qui montrent la fuite à l'aide de VMMap (regardez la ligne rouge nommé "Tas"). Les captures d'écran suivantes ont été prises dans un délai de 30 minutes d'intervalle.
Le 32 bits binaires montre une augmentation de 16 octets, ce qui est tout à fait acceptable:
Le 64 bits binaires montre une augmentation de 12476 octets (à partir de 820K à 13296K), ce qui est plus problématique :
L'augmentation constante de la mémoire heap est également confirmé par XPerf :
À l'aide de DebugDiag j'ai été en mesure de voir le chemin de code qui a allouer de la mémoire perdue :
LeakTrack+13529
<my dll>!Sysinit::AllocTlsBuffer+13
<my dll>!Sysinit::InitThreadTLS+2b
<my dll>!Sysinit::::GetTls+22
<my dll>!System::AllocateRaiseFrame+e
<my dll>!System::DelphiExceptionHandler+342
ntdll!RtlpExecuteHandlerForException+d
ntdll!RtlDispatchException+45a
ntdll!KiUserExceptionDispatch+2e
KERNELBASE!RaiseException+39
<my dll>!System::::RaiseAtExcept+106
<my dll>!System::::RaiseExcept+1c
<my dll>!System::ExitDll+3e
<my dll>!System::::Halt0+54
<my dll>!System::::StartLib+123
<my dll>!Sysinit::::InitLib+92
<my dll>!Smart::initialization+38
ntdll!LdrShutdownThread+155
ntdll!RtlExitUserThread+38
<my application>!System::EndThread+20
<my application>!System::Classes::ThreadProc+9a
<my application>!SystemThreadWrapper+36
kernel32!BaseThreadInitThunk+d
ntdll!RtlUserThreadStart+1d
Remy Lebeau m'a aidé sur l'Embarcadero forums pour comprendre ce qui se passait :
La deuxième fuite ressemble plus à un certain bug. Au cours de thread l'arrêt, StartLib() est appelée, ce qui appelle ExitThreadTLS() pour gratuit le thread appelant TLS bloc de mémoire, puis appelle Halt0() pour appel ExitDll() pour déclencher une exception qui est interceptée par DelphiExceptionHandler() pour appeler AllocateRaiseFrame(), qui appelle indirectement GetTls() et donc InitThreadTLS() lorsqu'il accède à un threadvar variable nommée ExceptionObjectCount. Qui ré-alloue de la TLS bloc de mémoire de la thread d'appel qui est encore dans le processus de de l'arrêt. Donc, soit StartLib() ne doit pas être l'appel de Halt0() au cours de DLL_THREAD_DETACH, ou DelphiExceptionHandler devrait pas d'appel AllocateRaiseFrame() lorsqu'il détecte un _TExitDllException être soulevées.
Il semble clair pour moi qu'il y a une faille importante dans la Win64 façon de gérer les threads de l'arrêt. Un tel comportement interdit le développement de toute application serveur multithread qui doivent s'exécuter 27/7 sous Win64.
Donc :
- Que pensez-vous de mes conclusions ?
- Ne l'un de vous a une solution de contournement pour ce problème ?
Le test de code source et les binaires peut être téléchargé ici.
Merci pour votre contribution !
Edit : QC Rapport 105559. Je suis en attente de vos votes :-)