1
votes

I'm trying to find the source of a memory leak coming from a thread. The thread triggers synchronized events repetitively, returning a thread-protected object.

I trigger this event within the thread by calling a procedure...

procedure TDeskMonThread.DoOnImage(const ID: Integer; const R: TRect;
  ABmp: TLockBmp);
begin
  FSyncOnImageID:= ID;
  FSyncOnImageRect:= R;
  FSyncOnImageBmp:= ABmp;
  Synchronize(SYNC_OnImage);
end;

The 3 private fields are only used for this purpose - temporary storage to be used in the event trigger. TLockBmp is just a wrapper around a TBitmap with a critical section, requiring Lock and Unlock.

Then, the Synchronize calls this procedure:

procedure TDeskMonThread.SYNC_OnImage;
begin
  if Assigned(FOnImage) then //trigger event
    FOnImage(FSyncOnImageID, FSyncOnImageRect, FSyncOnImageBmp);
end;

and this event is handled by this procedure:

procedure TfrmMain.ThreadOnImage(const ID: Integer; const R: TRect;
  ABmp: TLockBmp);
var
  B: TBitmap;
begin
  if ID = FCurMon then begin //Only draw if it's the current monitor
    B:= ABmp.Lock;
    try
      FBmp.Assign(B); //Copy bitmap over
    finally
      ABmp.Unlock; //Hurry and unlock so thread can continue its work
    end;
    ResizeBitmap(FBmp, pbView.ClientWidth, pbView.ClientHeight, clBlack);
    pbView.Canvas.Draw(0, 0, FBmp); //Draw to canvas
  end;
end;

Now I've narrowed it down to ResizeBitmap because when I comment out that line of code, I do not get the memory leak. Here's that procedure:

procedure ResizeBitmap(Bitmap: TBitmap; Width, Height: Integer; Background: TColor);
var
  R: TRect;
  B: TBitmap;
  X, Y: Integer;
begin
  if assigned(Bitmap) then begin
    B:= TBitmap.Create;
    try
      if Bitmap.Width > Bitmap.Height then begin
        R.Right:= Width;
        R.Bottom:= ((Width * Bitmap.Height) div Bitmap.Width);
        X:= 0;
        Y:= (Height div 2) - (R.Bottom div 2);
      end else begin
        R.Right:= ((Height * Bitmap.Width) div Bitmap.Height);
        R.Bottom:= Height;
        X:= (Width div 2) - (R.Right div 2);
        Y:= 0;
      end;
      R.Left:= 0;
      R.Top:= 0;
      B.PixelFormat:= Bitmap.PixelFormat;
      B.Width:= Width;
      B.Height:= Height;
      B.Canvas.Brush.Color:= Background;
      B.Canvas.FillRect(B.Canvas.ClipRect);
      B.Canvas.StretchDraw(R, Bitmap);
      Bitmap.Width:= Width;
      Bitmap.Height:= Height;
      Bitmap.Canvas.Brush.Color:= Background;
      Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
      Bitmap.Canvas.Draw(X, Y, B);
    finally
      B.Free;
    end;
  end;
end;

The memory leak message is what has me confused:

enter image description here

The x 3 varies depending on how long it's been running, but isn't the number of iterations. For example, the thread may repeat 20 iterations and show x 3 or may repeat 10 iterations and show x 7 but I can't even find a pattern of how many leaks compared to how many iterations. It seems this happens at random moments, not on every iteration.

So I went into debugging the ResizeBitmap procedure, but when I run it by its self, even repeatedly and rapidly, I never get any memory leaks. It seems to be something to do with calling it repeatedly from the thread. I know it's creating/destroying an instance of TBitmap which may not be the best practice, but still, I only get this memory leak when it's being called repeatedly from the thread. I'm assuming there's a hidden exception (out of resources) which never actually raises an exception - and thus gets trapped as a memory leak.

Where could this memory leak be coming from? How can I prevent it?

1
Note that unlocking the bitmap does nothing to allow the thread to continue doing any work. The thread still has to wait for Synchronize to return, which won't happen until the event handler finishes running.Rob Kennedy
You need full version of FastMM to get allocation stack traces. Or even better, the leak detection on madExcept 4.David Heffernan
@Rob True, I really meant so that it doesn't remain locked for too long - as soon as I no longer need it, unlock it so something else may access it (since the following two lines of code can be slightly time consuming). This is assuming however that there are even more threads, which there aren't, but for future implementation of more threads, I make sure it's ready if I intend to do so.Jerry Dodge

1 Answers

5
votes

EOutOfResources doesn't have its own constructor, so try putting conditional breakpoints in the various constructors for Exception that will only fire when self.ClassType = EOutOfResources. Then you'll find the point at which the exception objects are being created, and you should be able to use the stack trace to figure out what's going on from that point.

Also, leak checking in general becomes much, much easier if you use FullDebugMode. You'll want to download the full version of FastMM4 from SourceForge, and rebuild your project with FullDebugMode and logging enabled. Then, instead of just the memory leak dialog, you'll get a file with detailed debug information about your memory leaks, including stack traces at the moment of creation.