В течение долгого времени я заметил, что 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:
Использование 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.
Итак:
- Что вы думаете о моих выводах?
- У любого из вас есть обходной путь для этой проблемы?
Исходный код испытания и двоичные файлы can be downloaded here.
Спасибо за ваш вклад!
Редактировать: QC Report 105559. Я жду ваших голосов :-)
«У любого из вас есть обходной путь для этой проблемы». Я бы использовал 32-битное приложение до следующего стабильный выпуск delphi с 64-битным компилятором приходит ... –
ComputerSaysNo
Если бы я был вами, я бы сократил это до образца минимального минимального размера, который показывает утечку, и просто отправьте его в QC. –
@DorinDuminica: это будет Delphi XE4, тогда;) – whosrdaddy