线程关闭过程中Win64 Delphi RTL中的内存泄漏? [英] Memory leak in the Win64 Delphi RTL during thread shutdown?

查看:127
本文介绍了线程关闭过程中Win64 Delphi RTL中的内存泄漏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

很长一段时间以来,我注意到服务器应用程序的Win64版本会泄漏内存.尽管Win32版本可以在相对稳定的内存空间中正常运行,但64位版本使用的内存会定期增加-可能每天增加20Mb,而没有任何明显的理由(不用说,FastMM4都没有报告这两个内存泄漏) .源代码在32位和64位版本之间是相同的.该应用程序是基于Indy TIdTCPServer组件构建的,它是一个连接到数据库的高度多线程服务器,该数据库处理由Delphi XE2生成的其他客户端发送的命令.

For a long time I’ve noticed that the Win64 version of my server application leak memory. While the Win32 version works fine with a relatively stable memory footprint, the memory used by the 64 bit version increases regularly – maybe 20Mb/day, without any apparent reason (Needless to say, FastMM4 did not report any memory leak for both of them). The source code is identical between the 32bit and the 64bit version. The application is built around the Indy TIdTCPServer component, it is a highly multithreaded server connected to a database that processes commands sent by other clients made with Delphi XE2.

我花了很多时间检查自己的代码,并试图理解为什么64位版本会泄漏这么多内存.最后,我使用了旨在跟踪诸如DebugDiag和XPerf之类的内存泄漏的MS工具,似乎Delphi 64位RTL中存在一个基本缺陷,即每次从DLL分离线程时,都会导致某些字节泄漏.对于必须在不重新启动的情况下必须全天候运行24/7的高度多线程的应用程序,此问题尤为重要.

I spend a lot of time reviewing my own code and trying to understand why the 64 bit version leaked so much memory. I ended up by using MS tools designed to track memory leaks like DebugDiag and XPerf and it seems there is a fundamental flaw in the Delphi 64bit RTL that causes some bytes to be leaked each time a thread has detached from a DLL. This issue is particularly critical for highly multithreaded applications that must run 24/7 without being restarted.

我用一个非常基本的项目重现了该问题,该项目由一个主机应用程序和一个库组成,两者均使用XE2构建. DLL与主机应用程序静态链接.主机应用程序创建仅调用虚拟导出过程并退出的线程:

I reproduced the problem with a very basic project that is composed by an host application and a library, both built with XE2. The DLL is statically linked with the host app. The host app creates threads that just call the dummy exported procedure and exit:

这是该库的源代码:

library FooBarDLL;

uses
  Windows,
  System.SysUtils,
  System.Classes;

{$R *.res}

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

exports
  FooBarProc;

主机应用程序使用计时器创建一个仅调用导出过程的线程:

The host application uses a timer to create a thread that just call the exported procedure:

  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显示泄漏的屏幕截图(请查看名为"Heap"的红线).以下屏幕截图是在30分钟的间隔内拍摄的.

Here is some screenshots that show the leak using VMMap (look at the red line named "Heap"). The following screenshots were taken within a 30 minutes interval.

32位二进制文​​件显示增加了16个字节,这是完全可以接受的:

The 32 bit binary shows an increase of 16 bytes, which is totally acceptable:

64位二进制文​​件显示增加了12476个字节(从820K增加到13296K),这是个问题:

The 64 bit binary shows an increase of 12476 bytes (from 820K to 13296K), which is more problematic:

XPerf也证实了堆内存的不断增加:

The constant increase of heap memory is also confirmed by XPerf:

XPerf用法

使用DebugDiag,我能够看到分配泄漏内存的代码路径:

Using DebugDiag I was able to see the code path that was allocating the leaked memory:

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 在Embarcadero论坛上帮助了我了解发生了什么事:

Remy Lebeau helped me on the Embarcadero forums to understand what was happening:

第二次泄漏看起来更像是一个确定的错误.穿线期间 关闭时,将调用StartLib(),它将调用ExitThreadTLS()来 释放调用线程的TLS内存块,然后调用Halt0()以 调用ExitDll()引发被捕获的异常 DelphiExceptionHandler()调用AllocateRaiseFrame(),该方法 在访问a时间接调用GetTls()并因此调用InitThreadTLS() 名为ExceptionObjectCount的threadvar变量.重新分配 仍在处理中的调用线程的TLS内存块 被关闭.因此,要么StartLib()都不应该调用 DLL_THREAD_DETACH期间的Halt0()或DelphiExceptionHandler应该 当检测到一个错误时,不会调用AllocateRaiseFrame() 引发_TExitDllException.

The second leak looks more like a definite bug. During thread shutdown, StartLib() is being called, which calls ExitThreadTLS() to free the calling thread's TLS memory block, then calls Halt0() to call ExitDll() to raise an exception that is caught by DelphiExceptionHandler() to call AllocateRaiseFrame(), which indirectly calls GetTls() and thus InitThreadTLS() when it accesses a threadvar variable named ExceptionObjectCount. That re-allocates the TLS memory block of the calling thread that is still in the process of being shut down. So either StartLib() should not be calling Halt0() during DLL_THREAD_DETACH, or DelphiExceptionHandler should not be calling AllocateRaiseFrame() when it detects a _TExitDllException being raised.

对我来说似乎很清楚,Win64处理线程关闭的方式存在一个重大缺陷.这种行为禁止开发任何必须在Win64下运行27/7的多线程服务器应用程序.

It seems clear for me that there is an major flaw in the Win64 way to handle threads shutdown. A such behavior prohibits the development of any multithreaded server application that must run 27/7 under Win64.

所以:

  1. 您如何看待我的结论?
  2. 您有没有解决此问题的方法?

QC报告105559

推荐答案

一个非常简单的解决方法是重新使用线程,而不创建和销毁它们.线程非常昂贵,您也可能会获得性能提升.尽管调试很赞...

A very simple work around is to re-use the thread and not create and destroy them. Threads are pretty expensive, you'll probably get a perf boost too... Kudos on the debugging though...

这篇关于线程关闭过程中Win64 Delphi RTL中的内存泄漏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆