2012-05-11 1 views
35

오랫동안 내 서버 응용 프로그램의 Win64 버전에서 메모리 누수가 발생했습니다. Win32 버전은 상대적으로 안정적인 메모리 풋 프린트로 잘 작동하지만 64 비트 버전에서 사용되는 메모리는 명백한 이유없이 20Mb/일 정도의 속도로 정기적으로 증가합니다 (말할 필요도없이 FastMM4는 두 메모리 누수를보고하지 않았습니다) . 소스 코드는 32 비트와 64 비트 버전이 동일합니다. 이 응용 프로그램은 Indy TIdTCPServer 구성 요소를 기반으로 구축되었으며 Delphi XE2로 만든 다른 클라이언트가 보낸 명령을 처리하는 데이터베이스에 연결된 고도로 다중 스레드 된 서버입니다.스레드 종료 중 Win64 Delphi RTL에서 메모리 누수가 발생합니까?

나는 내 자신의 코드를 검토하고 왜 64 비트 버전이 너무 많은 메모리를 유출했는지 이해하려고 노력한다. DebugDiag 및 XPerf와 같은 메모리 누수를 추적하도록 설계된 MS 도구를 사용하여 결국 스레드가 DLL에서 분리 될 때마다 일부 바이트가 유출되는 원인이되는 Delphi 64bit RTL에 근본적인 결함이있는 것으로 보입니다. 이 문제는 특히 재시작하지 않고 24/7로 실행해야하는 고도의 멀티 스레드 응용 프로그램에 중요합니다.

XE2로 작성된 호스트 응용 프로그램과 라이브러리로 구성된 매우 기본적인 프로젝트에서이 문제가 재현되었습니다. DLL은 호스트 응용 프로그램과 정적으로 연결됩니다. 호스트 응용 프로그램은 단지 내 보낸 호출 스레드를 생성하기 위해 타이머를 사용

library FooBarDLL; 

uses 
    Windows, 
    System.SysUtils, 
    System.Classes; 

{$R *.res} 

function FooBarProc(): Boolean; stdcall; 
begin 
    Result := True; //Do nothing. 
end; 

exports 
    FooBarProc; 

: 여기

라이브러리의 소스 코드 : 호스트 응용 프로그램은 단지 더미 수출 절차 종료를 호출 쓰레드를 생성 절차 :

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; 

다음은 VMMap을 사용하여 누설을 보여주는 일부 스크린 샷입니다 ("힙"이라는 빨간색 선 참조). 다음 스크린 샷은 30 분 간격으로 촬영되었습니다.

32 비트 이진 완전히 허용되는 16 바이트의 증가, 도시

:

Memory usage for the 32 bit version http://img401.imageshack.us/img401/6159/soleak32.png

64 비트 이진 (820K에서 13296K까지) 12,476 바이트의 증가를 나타내고, 더 많은 문제가 : 힙 메모리

Memory usage for the 64 bit version http://img12.imageshack.us/img12/209/soleak64.png

상수의 증가는 XPerf 의해 확인된다

,617,451

두 번째 누출 외모 : 무슨 일이 일어 났는지 이해

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 

레미 Lebeau helped me on the Embarcadero forums :

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

는 DebugDiag를 사용하여 나는 누수 된 메모리를 할당 된 코드 경로를 볼 수 있었다 더 확실한 버그 같아. 스레드 종료 중에 StartThreadTLS()를 호출하여 ExitThreadTLS()를 호출하면 은 호출 스레드의 TLS 메모리 블록을 해제 한 다음 Halt0()을 에 호출하여 ExitDll()을 호출하여 에 걸린 예외를 발생시킵니다. DelphiExceptionHandler ( )은 threadvar 변수 ExceptionObjectCount에 액세스 할 때 간접적으로 GetTls()를 호출하고 InitThreadTLS()를 호출하는 AllocateRaiseFrame()을 호출합니다.이 프로세스는 여전히 종료되고있는 프로세스에있는 호출 스레드의 TLS 메모리 블록을 다시 할당합니다. 따라서 StartLib()은 DLL_THREAD_DETACH 동안 Halt0()을 호출해서는 안되며, _TExitDllException이 발생하면 DelphiExceptionHandler는 이 AllocateRaiseFrame()을 호출하지 않아야합니다.

스레드 종료를 처리하는 Win64 방식에는 중대한 결함이 있음이 분명합니다. 이러한 동작은 Win64에서 27/7을 실행해야하는 다중 스레드 서버 응용 프로그램의 개발을 금지합니다.

그래서 :

  1. 당신은 무엇을 나의 결론에 대해 어떻게 생각하십니까?
  2. 이 문제의 해결 방법이 있습니까?

테스트 소스 코드 및 바이너리 can be downloaded here.

귀하의 기여에 감사드립니다!

: QC Report 105559을 편집하십시오. 귀하의 투표를 기다리고 있습니다 :-)

+3

"이 문제에 대한 해결책이 있습니까?"나는 다음의 까지 32 비트 어플리케이션을 사용할 것입니다. 안정적인 64 비트 컴파일러로 델파이를 출시했습니다 ... – ComputerSaysNo

+4

만약 내가 당신이라면, 최소한의 크기로 샘플을 잘라내어 누출을 보여 주며 단순히 QC에 제출하십시오. –

+0

@DorinDuminica : 그때 델파이 XE4가 될 것입니다.) – whosrdaddy

답변

2

매우 간단한 해결 방법은 스레드를 재사용하고 생성하고 파괴하지 않는 것입니다. 스레드는 꽤 비쌉니다. 아마도 퍼포먼스를 얻게 될 것입니다 ... 디버깅에 대한 명성이 있습니다 ...

+0

예, 저의 첫 번째 아이디어였습니다. 내 구체적인 경우에는 확실히 스레드 풀을 사용할 수 있습니다. 하지만 그건 내 프로젝트에 포함 된 제 3 자 코드가 누출 될 새 스레드를 예약하는 것을 막지는 않을 것입니다 ... –

+0

사실,하지만 써드 파티 물건의 소스가 있다면 스레드 풀을 사용하도록 조정할 수 있습니다 , 당신이하지 않으면 아무것도 그것에 대해 어쨌든 할 수있을거야 ... 절대적으로 외부의 새는 dll을 실행해야한다면, 당신은 다시 시작할 수있는 별도의 프로세스에서 그것을해야한다 지금은 그렇지만 항상 가능한 것은 아닙니다. – Eric