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:
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.
Common Types of Memory Leaks. Leaks in managed platforms are effectively references to an element that is no longer necessary. There are many samples of this, but they all boil down to discarding said reference. The most common problem is caching.
If you have many of those your application takes a lot of memory and eventually you run out of it. In C#, these are some common memory leaks: Not removing event listeners. Any event listener that is created with an anonymous method or lambda expression that references an outside object will keep those objects alive.
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.
Looks to me like a definite circular reference issue. Anonymous methods are managed through hidden interfaces, and if the TList<TListenerProc>
is owned by the object that ISomeInterface is implemented on, then you've got a circular reference issue.
One possible solution would be to put a ClearListeners
method on ISomeInterface which calls .Clear
on the TList<TListenerProc>
. As long as nothing else is holding a reference to the anonymous methods, that would make them all vanish and drop their references to the ISomeInterface.
I've done a few articles about the structure and implementation of anonymous methods that might help you understand what you're really working with and how they operate a little bit better. You can find them at http://tech.turbu-rpg.com/category/delphi/anonymous-methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With