Search code examples
delphimemory-managementinterfacedelphi-2010anonymous-methods

Interfaces, Anonymous Methods and Memory Leaks


this is a constructed example. I don't want to post the original code here. I tried to extract the relevant parts though.

I have an interface that manages a list of listeners.

TListenerProc = reference to procedure (SomeInt : ISomeInterface);

ISomeInterface = interface
   procedure AddListener (Proc : TListenerProc);   
end;

Now I register a listener:

SomeObj.AddListener (MyListener);

procedure MyListener (SomeInt : ISomeInterface);
begin
  ExecuteSynchronized (procedure
                       begin
                       DoSomething (SomeInt);
                       end);
end;

I do get memory leaks. Both the anonymous method and the interfaces are never freed. I suspect that this is due to some kind of circular reference here. The anonymous method keeps the interface alife and the interface keeps the anonymous method alife.

Two questions:

  1. Do you support that explanation? Or am I missing something else here?
  2. Is there anything I can do about it?

Thanks in advance!


EDIT: It's not so easy to reproduce this in an application small enough to post it here. The best I can do by now is the following. The anonymous method does not get released here:

program TestMemLeak;

{$APPTYPE CONSOLE}

uses
  Generics.Collections, SysUtils;

type
  ISomeInterface = interface;
  TListenerProc  = reference to procedure (SomeInt : ISomeInterface);

  ISomeInterface = interface
  ['{DB5A336B-3F79-4059-8933-27699203D1B6}']
    procedure AddListener (Proc : TListenerProc);
    procedure NotifyListeners;
    procedure Test;
  end;

  TSomeInterface = class (TInterfacedObject, ISomeInterface)
  strict private
    FListeners          : TList <TListenerProc>;
  protected
    procedure AddListener (Proc : TListenerProc);
    procedure NotifyListeners;
    procedure Test;
  public
    constructor Create;
    destructor  Destroy; override;
  end;


procedure TSomeInterface.AddListener(Proc: TListenerProc);
begin
FListeners.Add (Proc);
end;

constructor TSomeInterface.Create;
begin
FListeners := TList <TListenerProc>.Create;
end;

destructor TSomeInterface.Destroy;
begin
FreeAndNil (FListeners);
  inherited;
end;

procedure TSomeInterface.NotifyListeners;

var
  Listener : TListenerProc;

begin
for Listener in FListeners do
  Listener (Self);
end;

procedure TSomeInterface.Test;
begin
// do nothing
end;

procedure Execute (Proc : TProc);

begin
Proc;
end;

procedure MyListener (SomeInt : ISomeInterface);
begin
Execute (procedure
         begin
         SomeInt.Test;
         end);
end;

var
  Obj     : ISomeInterface;

begin
  try
    ReportMemoryLeaksOnShutdown := True;
    Obj := TSomeInterface.Create;
    Obj.AddListener (MyListener);
    Obj.NotifyListeners;
    Obj := nil;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Solution

  • Your code is far from minimal. The following:

    program AnonymousMemLeak;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils;
    
    type
      TListenerProc  = reference to procedure (SomeInt : IInterface);
    
    procedure MyListener (SomeInt : IInterface);
    begin
    end;
    
    var
      Listener: TListenerProc;
    
    begin
      try
        ReportMemoryLeaksOnShutdown := True;
    
        Listener := MyListener;
        Listener := nil;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.
    

    has the very same problem (Delphi 2009 here). This can't be worked or designed around. Looks to me like a bug in the compiler.

    Edit:

    Or maybe this is a problem of the memory leak detection. It has nothing to do with the parameter being an interface, a parameterless procedure leads to the same "leak". Very strange.