Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory pattern, memory leak

I am reading Hodges book "More Coding in Delphi", section on Factory Pattern. Trying to learn stuff. Broke down my code into small unit. I use ReportMemoryLeaksOnShutDown := True; and fallowing code produces me a memory leak. Why does it occur and how do I fix it?

unit Unit2;

interface

uses
  Generics.Collections, System.SysUtils;

type
  TGatewayTpe = (gtSwedbank, gtDNB);

type
  TBaseGateway = class

  end;

type
  TSwedbankGateway = class(TBaseGateway)
  end;

type
  TGatewayFunction = reference to function: TBaseGateway;

type
  TGatewayTypeAndFunction = record
    GatewayType: TGatewayTpe;
    GatewayFunction: TGatewayFunction;
  end;

type
  TGatewayFactory = class
  strict private
    class var FGatewayTypeAndFunctionList: TList<TGatewayTypeAndFunction>;
  public
    class constructor Create;
    class destructor Destroy;
    class procedure AddGateway(const AGatewayType: TGatewayTpe;
      const AGatewayFunction: TGatewayFunction);
  end;

implementation

class procedure TGatewayFactory.AddGateway(const AGatewayType: TGatewayTpe;
  const AGatewayFunction: TGatewayFunction);

var
  _GatewayTypeAndFunction: TGatewayTypeAndFunction;
begin
  _GatewayTypeAndFunction.GatewayType := AGatewayType;
  _GatewayTypeAndFunction.GatewayFunction := AGatewayFunction;

  FGatewayTypeAndFunctionList.Add(_GatewayTypeAndFunction);
end;

class constructor TGatewayFactory.Create;
begin
  FGatewayTypeAndFunctionList := TList<TGatewayTypeAndFunction>.Create;
end;

class destructor TGatewayFactory.Destroy;
begin
  FreeAndNil(FGatewayTypeAndFunctionList);
end;

initialization
  TGatewayFactory.AddGateway(
    gtSwedbank, 
    function: TBaseGateway
    begin
      Result := TSwedbankGateway.Create;
    end
  );

end.
like image 894
Edijs Kolesnikovičs Avatar asked Jan 09 '16 13:01

Edijs Kolesnikovičs


1 Answers

This is a compiler defect. Defining an anonymous method in the initialization section of the unit appears to lead to that anonymous method not being finalized, and so leaked. In this case I would work around the issue by moving the code from the initialization section to the class constructor.

So, remove the initialization section completely, and change the class constructor to be like this:

class constructor TGatewayFactory.Create;
begin
  FGatewayTypeAndFunctionList := TList<TGatewayTypeAndFunction>.Create;
  AddGateway(
    gtSwedbank,
      function: TBaseGateway
      begin
        Result := TSwedbankGateway.Create;
      end
  );
end;

Here is the simplest reproduction that I can concoct:

unit Unit1;

interface

implementation

type
  TProc = reference to procedure;

var
  Foo: TProc;

initialization
  ReportMemoryLeaksOnShutdown := True;
  Foo := procedure begin end;

end.

When you include this unit in a project, the anonymous method is reported as being leaked.

But this variant does not report a leak:

unit Unit1;

interface

implementation

type
  TProc = reference to procedure;

var
  Foo: TProc;

procedure DoInit;
begin
  Foo := procedure begin end;
end;

initialization
  ReportMemoryLeaksOnShutdown := True;
  DoInit;

end.

The defect was fixed in XE8.

like image 112
David Heffernan Avatar answered Oct 24 '22 17:10

David Heffernan