IndySockets / Indy

Indy - Internet Direct
https://www.indyproject.org
458 stars 155 forks source link

Memory leaks reported for Indy 10.6.2.0 #237

Closed D3lphi3r closed 6 years ago

D3lphi3r commented 6 years ago

The following code reported memory leak

program Project1;
uses
  IdHTTP;

begin
end. 

Below is the trace log

Heap dump by heaptrc unit of /tmp/project1
111 memory blocks allocated : 77828/78104
108 memory blocks freed     : 77708/77984
3 unfreed memory blocks : 120
True heap size : 393216
True free heap : 392448
Should be : 392520
Call trace for block $00007F891AB83B00 size 48
  $00000000004FA4A0  IDTHREAD_$$_init$,  line 730 of source/IdThread.pas
  $0000000000416AFC
Call trace for block $00007F891AB83A00 size 24
  $00000000004FA4A0  IDTHREAD_$$_init$,  line 730 of source/IdThread.pas
  $0000000000416AFC
Call trace for block $00007F891AB82E00 size 48
  $0000000000416AFC
rlebeau commented 6 years ago

This is normal and intentional behavior.

Look at the initialization and finalization sections of the IdThread.pas unit:

initialization
  ...
  GThreadCount := TIdThreadSafeInteger.Create;
  {$IFNDEF FREE_ON_FINAL}
    {$IFDEF REGISTER_EXPECTED_MEMORY_LEAK}
  IndyRegisterExpectedMemoryLeak(GThreadCount);
  IndyRegisterExpectedMemoryLeak(TIdThreadSafeIntegerAccess(GThreadCount).FCriticalSection);
    {$ENDIF}
  {$ENDIF}
finalization
  ...
  {$IFDEF FREE_ON_FINAL}
  //only enable this if you know your code exits thread-clean
  FreeAndNil(GThreadCount);
  {$ENDIF}
end.

As well as in the IdStack.pas unit, too:

initialization
  ...
  GStackCriticalSection := TIdCriticalSection.Create;
  {$IFNDEF DOTNET}
    {$IFDEF REGISTER_EXPECTED_MEMORY_LEAK}
  IndyRegisterExpectedMemoryLeak(GStackCriticalSection);
    {$ENDIF}
  {$ENDIF}
finalization
  // Dont Free. If shutdown is from another Init section, it can cause GPF when stack
  // tries to access it. App will kill it off anyways, so just let it leak
  {$IFDEF FREE_ON_FINAL}
  FreeAndNil(GStackCriticalSection);
  {$ENDIF}
end.

As you can see, there are a few global objects being intentionally leaked when FREE_ON_FINAL is not defined (which it is not by default, see IdCompilerDefines.inc).

See Why does FREE_ON_FINAL exists? for more details.

On systems where the memory manager supports registering known leaks, they are registered by default so that they DO NOT appear in leak reports:

{$IFDEF VCL_2006_OR_ABOVE}
  ...
  {$DEFINE HAS_System_RegisterExpectedMemoryLeak}
  {$IFNDEF FREE_ON_FINAL}
    {$IFNDEF DOTNET}
      {$DEFINE REGISTER_EXPECTED_MEMORY_LEAK}
    {$ENDIF}
  {$ENDIF}
  ...
{$ENDIF}
...
{$IFNDEF FREE_ON_FINAL}
  {$IFNDEF REGISTER_EXPECTED_MEMORY_LEAK}
    {$IFDEF USE_FASTMM4}
      {$DEFINE REGISTER_EXPECTED_MEMORY_LEAK}
    {$ENDIF}
    {$IFDEF USE_MADEXCEPT}
      {$DEFINE REGISTER_EXPECTED_MEMORY_LEAK}
    {$ENDIF}
    {$IFDEF USE_LEAKCHECK}
      {$DEFINE REGISTER_EXPECTED_MEMORY_LEAK}
    {$ENDIF}
  {$ENDIF}
{$ENDIF}

{$IFDEF REGISTER_EXPECTED_MEMORY_LEAK}
  {$IFDEF DOTNET}
    {$UNDEF REGISTER_EXPECTED_MEMORY_LEAK}
  {$ENDIF}
  {$IFDEF VCL_CROSS_COMPILE}
    // RLebeau: this should be enabled for Windows, at least...
    {$IFNDEF MSWINDOWS}
      // RLebeau: LeakCheck has leak reporting on other platforms...
      {$IFNDEF USE_LEAKCHECK}
        {$UNDEF REGISTER_EXPECTED_MEMORY_LEAK}
      {$ENDIF}
    {$ENDIF}
  {$ENDIF}
{$ENDIF}

When REGISTER_EXPECTED_MEMORY_LEAK is defined, the IdGlobal.pas unit implements a public IndyRegisterExpectedMemoryLeak() function to call the appropriate registration function:

{$IFNDEF DOTNET}
  {$IFDEF REGISTER_EXPECTED_MEMORY_LEAK}
function IndyRegisterExpectedMemoryLeak(AAddress: Pointer): Boolean;
{$IFDEF USE_INLINE}inline;{$ENDIF}
begin
  // use only System.RegisterExpectedMemoryLeak() on systems that support
  // it. We should use whatever the RTL's active memory manager is. The user
  // can override the RTL's version of FastMM (2006+ only) with any memory
  // manager they want, such as MadExcept.
  //
  // Fallback to specific memory managers if System.RegisterExpectedMemoryLeak()
  // is not available.

  {$IFDEF HAS_System_RegisterExpectedMemoryLeak}
  // RLebeau 4/21/08: not quite sure what the difference is between the
  // SysRegisterExpectedMemoryLeak() and RegisterExpectedMemoryLeak()
  // functions in the System unit, but calling RegisterExpectedMemoryLeak()
  // is causing stack overflows when FastMM is not active, so call
  // SysRegisterExpectedMemoryLeak() instead...

  // RLebeau 7/4/09: According to Pierre Le Riche, developer of FastMM:
  //
  // "SysRegisterExpectedMemoryLeak() is the leak registration routine for
  // the built-in memory manager. FastMM.RegisterExpectedMemoryLeak is the
  // leak registration code for FastMM. Both of these are thus hardwired to
  // a specific memory manager. In order to register a leak for the
  // *currently installed* memory manager, which is what you typically want
  // to do, you have to call System.RegisterExpectedMemoryLeak().
  // System.RegisterExpectedMemoryLeak() redirects to the leak registration
  // code of the installed memory manager."

  //Result := System.SysRegisterExpectedMemoryLeak(AAddress);
  Result := System.RegisterExpectedMemoryLeak(AAddress);
  {$ELSE}
    // RLebeau 10/5/2014: the user can override the RTL's version of FastMM
    // (2006+ only) with any memory manager, such as MadExcept, so check for
    // that...
    {$IFDEF USE_FASTMM4}
  Result := FastMM4.RegisterExpectedMemoryLeak(AAddress);
    {$ELSE}
      {$IFDEF USE_MADEXCEPT}
  Result := madExcept.HideLeak(AAddress);
      {$ELSE}
        {$IFDEF USE_LEAKCHECK}
  Result := LeakCheck.RegisterExpectedMemoryLeak(AAddress);
        {$ELSE}
  Result := False;
        {$ENDIF}
      {$ENDIF}
    {$ENDIF}
  {$ENDIF}
end;
  {$ENDIF}
{$ENDIF}

As you can see above, IndyRegisterExpectedMemoryLeak() is being called for the leaked objects when possible.

However, all of the above applies to Delphi only, because FreePascal's heaptrc DOES NOT support registering known leaks!

D3lphi3r commented 6 years ago

It's all clear now.