2012-05-11 3 views
35

В течение долгого времени я заметил, что Win64-версия моего сервера утечки памяти. Хотя версия Win32 отлично работает с относительно стабильным объемом памяти, память, используемая 64-битной версией, регулярно увеличивается - возможно, 20 Мб/день без каких-либо очевидных причин (Нет необходимости говорить, что FastMM4 не сообщал о утечке памяти для них обоих) , Исходный код идентичен между 32-битной и 64-битной версиями. Приложение построено вокруг компонента Indy TIdTCPServer, это многопотоковый сервер, подключенный к базе данных, которая обрабатывает команды, отправленные другими клиентами, выполненными с помощью Delphi XE2.Утечка памяти в RT64 Win64 Delphi при отключении потока?

Я провожу много времени, просматривая свой собственный код и пытаясь понять, почему в 64-битной версии просочилось столько памяти. Я закончил с помощью MS-инструментов, предназначенных для отслеживания утечек памяти, таких как DebugDiag и XPerf, и кажется, что существует фундаментальный недостаток в RTL Delphi 64 бит, который вызывает утечку некоторых байтов каждый раз, когда поток отделился от DLL. Эта проблема особенно важна для многопоточных приложений, которые должны запускаться 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 двоичными показывает увеличение 12476 байт (от 820k до 13296K), которая является более проблематичной :

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

постоянное увеличение динамической памяти также подтверждается Xperf:

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

Использование DebugDiag я смог увидеть путь кода, который был выделяющий утечка памяти:

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 

Реми Лебо helped me on the Embarcadero forums, чтобы понять, что происходит:

Вторые взгляды утечки больше похоже на определенную ошибку. Во время потока вызывается выключение, запускается StartLib(), который вызывает ExitThreadTLS() до , освобождает блок памяти TLS вызывающего потока, затем вызывает Halt0() до , вызывая ExitDll(), чтобы возбудить исключение, которое уловлено DelphiExceptionHandler() для вызова AllocateRaiseFrame(), который опосредованно вызывает GetTls() и, таким образом, InitThreadTLS(), когда он обращается к переменной threadvar с именем ExceptionObjectCount.Это перераспределяетTLS-блок памяти вызывающего потока, который все еще находится в процессе . Поэтому либо StartLib() не должен вызывать Halt0() во время DLL_THREAD_DETACH, либо DelphiExceptionHandler должен не вызывать AllocateRaiseFrame(), когда он обнаруживает возникшее исключение _TExitDllException _TExitDllException.

Мне кажется ясным, что существует большой недостаток в способе Win64 для работы с отключением потоков. Такое поведение запрещает разработку любого многопоточного серверного приложения, которое должно запускаться 27/7 под Win64.

Итак:

  1. Что вы думаете о моих выводах?
  2. У любого из вас есть обходной путь для этой проблемы?

Исходный код испытания и двоичные файлы can be downloaded here.

Спасибо за ваш вклад!

Редактировать: QC Report 105559. Я жду ваших голосов :-)

+3

«У любого из вас есть обходной путь для этой проблемы». Я бы использовал 32-битное приложение до следующего стабильный выпуск delphi с 64-битным компилятором приходит ... – ComputerSaysNo

+4

Если бы я был вами, я бы сократил это до образца минимального минимального размера, который показывает утечку, и просто отправьте его в QC. –

+0

@DorinDuminica: это будет Delphi XE4, тогда;) – whosrdaddy

ответ

2

Очень простая работа заключается в том, чтобы повторно использовать поток, а не создавать и уничтожать их. Нитки довольно дорогие, вы, вероятно, получите импульс повышения ... Престижность в отладке, хотя ...

+0

Да, это была моя первая идея. В моем конкретном случае я мог бы использовать пул потоков. Но это не помешает стороннему коду, включенному в мой проект, планировать новые потоки, которые также будут протекать ... –

+0

Это правда, но если у вас есть источник для сторонних материалов, вы можете адаптировать его для использования пула потоков , если вы этого не сделаете, вы все равно сможете это сделать ... Если вам абсолютно необходимо запускать внешние просачивающиеся DLL, вы должны сделать это в отдельном процессе, который вы можете перезапустить каждый время от времени, но это не всегда возможно. – Eric

Смежные вопросы