1
votes

I use FDQUERY to select a single record from MYSQL that has a blob contining jpg image. Everything functions perfectly as expected.

I added ReportMemoryLeaksOnShutdown:=True to the project DPR. On shutdown I receive the Message:

"An unexpected memory leak has occurred. The unexpected small block leaks are: 1-12 bytes : TStream x 1"

(1 increases for each call to the below line of code.) If I comment out the line there are no memory leaks.

I have tried to trace back through the source code to find the leak but I just can't find it.

Any Help, or workaround would be most appreciated. Many Thanks.
Development Environment is:

Windows 10

Delphi 10.4.2 Pro

MYSQL 8.0.25 (64bit)

MS:=FDQuery1.CreateBlobStream(FDQuery1.FieldbyName('Image'),TBlobStreamMode.bmRead);`

MS Stream Variable IS being freed in a try .. finally block.

Reproducible Code Below:

1 CREATE MYSQL Table Script

CREATE TABLE `TestTable` (
  `pk_Id` int NOT NULL AUTO_INCREMENT,
  `Image` longblob,
  UNIQUE KEY `pk_Id_UNIQUE` (`pk_Id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=27607 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2 Delphi Form

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 322
  ClientWidth = 720
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 56
    Top = 272
    Width = 129
    Height = 25
    Caption = 'Assign Blob To Stream'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 512
    Top = 40
    Width = 137
    Height = 25
    Caption = 'Insert JPG into Table'
    TabOrder = 1
    OnClick = Button2Click
  end
  object FDConnection1: TFDConnection
    Params.Strings = (
      'Database= INSERT DATABASE NAME HERE'  // <-- Change to your MYSQL Database Name
      'User_Name= INSERT YOUR USERNAME HERE' // <-- Change to Your MYSQL User name
      'Password=INSET YOUR PASSWORD HERE'    // <-- Change to Your MYSQL Password
      'Server=INSER YOUR SERVER HERE'        // <-- Change to Your MYSQL Server Typically 127.0.0.1
      'DriverID=MySQL')
    Connected = True
    LoginPrompt = False
    Left = 55
    Top = 96
  end
  object FDGUIxWaitCursor1: TFDGUIxWaitCursor
    Provider = 'Forms'
    Left = 55
    Top = 32
  end
  object FDQuery1: TFDQuery
    Connection = FDConnection1
    ResourceOptions.AssignedValues = [rvDirectExecute]
    ResourceOptions.DirectExecute = True
    SQL.Strings = (
      'Select Image from testtable'
      ''
      '')
    Left = 56
    Top = 166
  end
  object FDTable1: TFDTable
    IndexFieldNames = 'pk_Id'
    Connection = FDConnection1
    TableName = 'foodforlife.testtable'
    Left = 672
    Top = 40
  end
  object FileOpenDialog1: TFileOpenDialog
    DefaultExtension = 'jpg'
    DefaultFolder = 'C:\'
    FavoriteLinks = <>
    FileTypes = <
      item
        DisplayName = ''
        FileMask = '*.jpg'
      end>
    Options = []
    Left = 432
    Top = 24
  end
end

3 Delphi Unit

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
  FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.MySQL,
  FireDAC.Phys.MySQLDef, FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS,
  FireDAC.DApt.Intf, FireDAC.DApt, Data.DB, FireDAC.Comp.DataSet,
  FireDAC.Comp.Client, FireDAC.Comp.UI, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    FDConnection1: TFDConnection;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    FDQuery1: TFDQuery;
    Button2: TButton;
    FDTable1: TFDTable;
    FileOpenDialog1: TFileOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Var
  MS : TStream;
begin
  MS := TStream.Create;
  try
    FDQuery1.Active:=True;
    MS:=FDQuery1.CreateBlobStream(FDQuery1.FieldbyName('Image'),TBlobStreamMode.bmRead);  // --< Memory Leak Here
    MS.Position:=0;
  finally
    ms.Free;
  end;

end;

procedure TForm1.Button2Click(Sender: TObject);
Var
fs: TFileStream;
begin

  If FileOPenDialog1.execute then fs:=TFileStream.Create(FileOPenDialog1.Files[0],fmShareDenyNone);
  try
     fs.position:=0;
     With fdtable1 do
      begin
        active:=True;
        insert;
        (FieldByName('Image') as TBlobField).LoadFromStream(fs);
        Post
      end;
  finally
    fs.Free;

  end;



end;

end.

4 Delphi DPR

program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  ReportMemoryLeaksOnShutdown:=True;
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

To Use:

  • 1 Run the Create Script in a MYSQL DB to which you have access

    2 Insert the MYSQL DB Paramaters for the table created above in FDConnection1

    3 Compile and Run

    4 Click Button Titled Insert JPG into Table to display a file open dialog from which you will select a jpg file. When you execute the dialog it will save into the MYSQL table

    5 Click the Button Titled Assign Blob to Stream. This will assign the blob to a stream and create the memory leak for every button click

2
Please create a minimal reproducible example. Just the minimum to reproduce the problem, yet a complete program. No need to make a REST Server. A simple console mode application with a simple question and the blob stream.fpiette

2 Answers

1
votes

Your Button1Click code is wrong. You are Create()'ing an object instance of TStream itself (which you should not be doing, since TStream is an abstract base class), but you are not Free()'ing that object, thus it is leaked. When you create the blob stream, you are assigning that object to the same variable that the TStream object was assigned to, thus you lose your reference to the TStream object.

Get rid of the call to TStream.Create, it doesn't belong here, eg:

procedure TForm1.Button1Click(Sender: TObject);
Var
  MS : TStream;
begin
  FDQuery1.Active := True; 
  MS := FDQuery1.CreateBlobStream(FDQuery1.FieldbyName('Image'), TBlobStreamMode.bmRead);
  try
    // use MS as needed...
  finally
    MS.Free;
  end;
end;
1
votes

You can find an example in Embarcadero blobs demos.

Applied to the code you showed in the question, it becomes:

var
  BStream : TStream;
begin
  BStream := MyFDProductScanQuery.CreateBlobStream(MyFDProductScanQuery.Fields[I], TBlobStreamMode.bmRead);
  try
    Base64ImgStr := TIdEncoderMIME.EncodeStream(BStream);
  finally
    BStream.Free;
  end;
end;

Edit: Your code do not follow what I shown above. That's why you still have a memory leak. You are creating two streams and only free one: You call TStream.create and then overwrite the value by the stream returned by FDQuery1.CreateBlobStream. And also take care of what happens to Base64ImgStr which is probably also a stream that must be free'ed when you no longer need it.