delphi memory-leaks delphi-xe2 win64

¿Pérdida de memoria en Win64 Delphi RTL durante el cierre del hilo?



memory-leaks delphi-xe2 (2)

Durante mucho tiempo me he dado cuenta de que la versión Win64 de la aplicación de mi servidor tiene una fuga de memoria. Si bien la versión Win32 funciona bien con una memoria relativamente estable, la memoria utilizada por la versión de 64 bits aumenta regularmente, tal vez 20Mb / día, sin ninguna razón aparente (Huelga decir que FastMM4 no informó ninguna fuga de memoria para ambos) . El código fuente es idéntico entre la versión de 32 bits y la de 64 bits. La aplicación se basa en el componente Indy TIdTCPServer, es un servidor altamente multiproceso conectado a una base de datos que procesa los comandos enviados por otros clientes con Delphi XE2.

Paso mucho tiempo revisando mi propio código e intentando entender por qué la versión de 64 bits gastó tanta memoria. Terminé usando herramientas MS diseñadas para rastrear fugas de memoria como DebugDiag y XPerf y parece que hay un error fundamental en Delphi 64bit RTL que hace que se filtren algunos bytes cada vez que un hilo se separó de un archivo DLL. Este problema es particularmente crítico para las aplicaciones con múltiples subprocesos que deben ejecutarse 24/7 sin reiniciarse.

Reproduje el problema con un proyecto muy básico que está compuesto por una aplicación de host y una biblioteca, ambos construidos con XE2. La DLL está vinculada estáticamente con la aplicación de host. La aplicación host crea subprocesos que solo llaman al procedimiento exportado ficticio y salen:

Aquí está el código fuente de la biblioteca:

library FooBarDLL; uses Windows, System.SysUtils, System.Classes; {$R *.res} function FooBarProc(): Boolean; stdcall; begin Result := True; //Do nothing. end; exports FooBarProc;

La aplicación host utiliza un temporizador para crear un hilo que solo llame al procedimiento exportado:

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;

Aquí hay algunas capturas de pantalla que muestran la fuga usando VMMap (mire la línea roja llamada "Heap"). Las siguientes capturas de pantalla se tomaron dentro de un intervalo de 30 minutos.

El binario de 32 bits muestra un aumento de 16 bytes, lo cual es totalmente aceptable:

Uso de memoria para la versión de 32 bits http://img401.imageshack.us/img401/6159/soleak32.png

El binario de 64 bits muestra un aumento de 12476 bytes (de 820K a 13296K), que es más problemático:

Uso de la memoria para la versión de 64 bits http://img12.imageshack.us/img12/209/soleak64.png

El constante aumento de la memoria dinámica también lo confirma XPerf:

Uso de XPerf http://desmond.imageshack.us/Himg825/scaled.php?server=825&filename=soxperf.png&res=landing

Usando DebugDiag pude ver la ruta del código que estaba asignando la memoria filtrada:

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 me ayudó en los foros de Embarcadero a entender lo que estaba sucediendo:

La segunda fuga se parece más a un error definido. Durante el cierre del hilo, se llama a StartLib (), que llama a ExitThreadTLS () para liberar el bloque de memoria TLS del hilo llamante, luego llama a Halt0 () para llamar a ExitDll () para generar una excepción atrapada por DelphiExceptionHandler () para llamar a AllocateRaiseFrame ( ), que indirectamente llama a GetTls () y por lo tanto InitThreadTLS () cuando accede a una variable threadvar llamada ExceptionObjectCount. Eso vuelve a asignar el bloque de memoria TLS del hilo de llamada que aún está en proceso de cierre. Entonces, o StartLib () no debería estar llamando a Halt0 () durante DLL_THREAD_DETACH, o DelphiExceptionHandler no debería estar llamando a AllocateRaiseFrame () cuando detecta que se está emitiendo _TExitDllException.

Parece claro para mí que hay una falla importante en la forma de Win64 para manejar el cierre de subprocesos. Un comportamiento de este tipo prohíbe el desarrollo de cualquier aplicación de servidor multiproceso que deba ejecutarse 27/7 bajo Win64.

Asi que :

  1. ¿Qué piensas de mis conclusiones?
  2. ¿Alguno de ustedes tiene una solución para este problema?

El código fuente de prueba y los binarios se pueden descargar aquí .

¡Gracias por tu contribución!

Editar : Informe de QC 105559 . Estoy esperando tus votos :-)


Para evitar la excepción memoryleak trap, podrías tratar de poner a prueba / excepto alrededor de FoobarProc. Tal vez no sea para una solución definitiva, sino para ver por qué se plantea la axcepción en primer lugar.

Por lo general, tengo algo como esto:

try FooBarProc() except if IsFatalException(ExceptObject) then // checks for system exceptions like AV, invalidop etc OutputDebugstring(PChar(ExceptionToString(ExceptObject))) // or some other way of logging end;


Un trabajo muy simple es reutilizar el hilo y no crearlo ni destruirlo. Los hilos son bastante caros, probablemente obtendrás un impulso de perf también ... Felicitaciones por la depuración ...