Search code examples
delphimemory-leaksdunitleakcheck

How to handle "false" / expected memory in DUnit using LeakCheck?


In order to prune out all of memory leaks I am trying to incorporate LeakCheck library into DUnit. The problem is that LeakCheck will report me memory leaks that are not true leaks. These are objects or other stuff allocated by RTL and destroyed on program exit.

LeakCheck contains a lot of handful routines that allow you to specify what to ignore and I was able to use them to ignore most of such "leaks". However, I don't know how to get rid one in particular:

program LeakCheckMemLeak;
{$APPTYPE CONSOLE}
uses
  LeakCheck, TestFramework, LeakCheck.DUnit, LeakCheck.Utils,  LeakCheck.Setup.Trace, System.SysUtils,
  Forms, System.Classes;

{$R *.RES}

procedure LeakMemory;
var
  LThread: TThread;
begin
  LThread := TThread.Create(True);
  LThread.Free;
end;

procedure DetectLeak;
var
  Snapshot: TLeakCheck.TSnapshot;
  Report: LeakString;
begin
  Snapshot.Create;
  LeakMemory;
  Report := TLeakCheck.GetReport(Snapshot.Snapshot);
  try
    Writeln(string(Report));
  finally
    Report.Free;
  end;
end;

begin
  Application.Initialize;
  DetectLeak;
  Readln;
end.

Creating instance of TThread (originally it was TThread.CreateAnonymousThread but results are the same) causes 64 bytes of memory leak:

Total allocation count: 297 (12592 B)
Leak detected 02BB3DC0 size 64 B
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E8 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ????????????????????????????????
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ????????????????????????????????
Stack trace when the memory block was allocated:
  $00406E76 - LeakCheckMemLeak.exe - System.AllocMem + $A (4562 +25)
  $004C4A53 - LeakCheckMemLeak.exe - System.Classes.TThread.WaitFor + $8F (15565 +23)
  $005E4EB0 - LeakCheckMemLeak.exe - LeakCheckMemLeak.DetectLeak + $34 (24 +3)
  $005ED5B9 - LeakCheckMemLeak.exe - LeakCheckMemLeak.LeakCheckMemLeak + $29 (35 +3)
  $763E343D - kernel32.dll
  $76F19832 - ntdll.dll

In other cases LeakCheck provided me name of class that instance leaked so I could add it to list of ignored, but in this case it does not. How can I suppress this "leak"?

One a side note, this and other leaks that I've encountered don't happen in GUI application. I guess, RTL preallocates memory for most of these objects before tests run.


Solution

  • FWIW the results I get after removing Forms and Application.Initialize is this:

    Total allocation count: 113 (4152 B)
    Leak detected 0262F540 size 44 B for class: TExternalThread
    Leak detected 0260A988 size 20 B for class: TThreadList<System.Classes.TThread>
    Leak detected 02618A90 size 8 B for class: TObject
    Leak detected 026451F8 size 52 B for class: TList<System.Classes.TThread>
    Leak detected 02618AC8 size 12 B
      01 00 00 00 01 00 00 00 40 F5 62 02 | ????????@?b?
    

    I know from using LeakCheck in Spring4D unit tests that this comes from the lazy initialization of some instances in TThread.GetCurrentThread which gets called during your LeakMemory routine - more precisely during TThread.Destroy which calls WaitFor (see System.Classes.pas line 15764 in Delphi 10.2.3). This creates the instances that you see in the report I posted.

    What we are doing in Spring4D and also in the tests at work is calling all kinds of methods and routines that we know cause some lazy initialization of instances (TEncoding for example is another candidate) before running any test. This prevents lazy initializations during the test run which then manifest in the memory delta before and after the test. See InitializeLeakCheck in Spring.TestRunner.pas

    While you could configure LeakCheck to ignore these leaks it will affect performance significantly because it would find leaks to begin with. If these instances are initialized before changes are there is nothing to ignore later.