MemoryLeak bei TList<IMyInterface>
Hi,
ich bin noch nicht dazu gekommen, genauer nachzugraben - aber vielleicht ist das Problem ja auch direkt bekannt? Ich arbeite mit XE3 an einem Projekt, das intensiv Interfaces benutzt. Jetzt zeigt mir EurekaLog ein Speicherleck. Ich konnte das soweit eingrenzen, dass es wohl an einer generischen Liste liegen muss:
Delphi-Quellcode:
Ich bin davon ausgegangen, dass der Referenzzähler des Eintrags dann runter gezählt und das Objekt hinter dem Interface freigegeben wird. Das ist aber nicht der Fall.
List := TList<IMyInterface>.Create;
List.Add(aInterface); List.Free; Ich habe es auch mal (war schon in der Nacht) dann mal mit
Delphi-Quellcode:
versucht - auch ohne Erfolg.
List[0] := nil
Ich bin noch nicht dazu gekommen, das mal in einem Testprojekt nachzubauen (kann ich erst heute Abend). Aber schon mal an Euch die Frage: Mache ich einen Denkfehler oder ist das ein XE3-Bug oder bemeckert das EurekaLog zu Unrecht (wobei der Destruktor des Objektes offenbar wirklich nicht aufgerufen wurde)? |
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
Delphi-Quellcode:
Mavarik
IFoo = Interface
['{FD7BFFD9-1F75-4580-8C4A-375223FA0953}'] End; TFoo = Class(TInterfacedObject,IFoo) Public Procedure beforeDestruction;Override; end; var Form37: TForm37; implementation Uses System.Generics.Collections; {$R *.dfm} procedure TForm37.Button1Click(Sender: TObject); var Foo : IFoo; List : TList<IFoo>; begin Foo := TFoo.Create; List := TList<IFoo>.Create; List.add(Foo); List.free; end; { TFoo } procedure TFoo.beforeDestruction; begin Form37.Caption := 'Free!'; Inherited; end; |
AW: MemoryLeak bei TList<IMyInterface>
Woher kommt denn das aInterface? Wenn Du es als Parameter reingibst, könnte es von draussen noch Referenzen haben. Lokal erzeugt (wie im Beispiel von Mavarik) gibt es kein Leck.
|
AW: MemoryLeak bei TList<IMyInterface>
So ist das:
Delphi-Quellcode:
TFoo = class( TInterfacedObject )
end; procedure fooLeak( aInterface : IInterface ); begin end; procedure fooNoLeak( aInterface : IInterface ); var LRef : IInterface; begin LRef := aInterface; end; procedure bar; begin fooLeak( TFoo.Create ); fooNoLeak( TFoo.Create ); end; |
AW: MemoryLeak bei TList<IMyInterface>
Es gibt im Projekt einige Querverbindungen.
Aber wenn ich das List.Add mal rausnehme passt offenbar alles. Ich werde das heute Abend mal weiter untersuchen. Gestern bin ich daran etwas verzeifelt. Hätte ja sein können, dass da ein Bug bekannt wäre oder ich einen generellen Denkfehler hätte. |
AW: MemoryLeak bei TList<IMyInterface>
Mit diesen Querverbindungen hat es aber nichts zu tun.
Ursache ist einzig und allein, das hier
Delphi-Quellcode:
. Dabei wird nämlich kein RefCount erhöht!
foo( TFoo.Create );
|
AW: MemoryLeak bei TList<IMyInterface>
@Sir
Ich hatte vorhin keinen roten Kasten... Heißt das, wenn ich Interface-Parameter in einer Methode nicht benutzt wird gibt es ein MemoryLeak? Das wäre ja eine sehr unschöne Sache. Nach meiner derzeitigen Einschätzung dürfte das aber nicht das vorliegende Problem sein, da ich das Speicherleck vermeiden kann, indem ich die Anseisung List.Add(aInterface) auskommentiere. Mir fällt zwar gerade ein, dass ich wohl List.Add(MyObject) hinzufüge, aber das müsste ja problemlos dennoch das Interface verwalten. Oder wäre es besser, in einem solchen Fall explizit zu casten: List.Add(MyObject as IMyInterface)? Oder sollte man sogar unbedingt eine temporäre Interface-Variable anlagen und verwenden? PS: Dein eben nachgeschobenes Construkt verwende ich definiv nicht. Mal sehen, genaueres dann heute Abend... |
AW: MemoryLeak bei TList<IMyInterface>
Es heisst ganz einfach:
Wenn ich eine Instanz erzeuge und das direkt als ein Argument/Parameter übergebe, dann springt der Referenzzähler nicht an.
Delphi-Quellcode:
IFoo = interface
procedure Bar; end; procedure foo( aFoo : IFoo ); begin aFoo.Bar; end; var LFoo : IInterface; LFooObj : TFoo; foo( TFoo.Create ); // aufpassen, hier kann es zu einem Leak kommen LFoo := TFoo.Create; foo( LFoo ); // alles in Butter LFooObj := TFoo.Create; foo( LFooObj ); // aufpassen, hier wird auch keine Referenz erhöht! |
AW: MemoryLeak bei TList<IMyInterface>
Das ist dann aber ein Bug?
Hier
Delphi-Quellcode:
muß die Referenzzählung hoch gehn
procedure foo( aFoo : IFoo );
und hier
Delphi-Quellcode:
natürlich nicht.
procedure foo( const aFoo : IFoo );
Innerhalb der letzten Methode hat man dann viel Spaß, sobald der Zähler das erste Mal wieder auf 0 fällt. Und ohne referenzzählenden Zugriff, auf den Parameter, gibt es ein schönes Speicherleck. |
AW: MemoryLeak bei TList<IMyInterface>
@himitsu
Stimmt ... das
Delphi-Quellcode:
ist der Üpeltäter
const
Delphi-Quellcode:
ergibt bis zum abschließenden
program dp_184063;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type IFoo = interface ['{FEF0782C-9F48-4060-A79D-8697F8431718}'] procedure Bar; end; TFoo = class( TInterfacedObject, IFoo ) private FName: string; public constructor Create( const Name: string ); destructor Destroy; override; procedure Bar; end; { TFoo } constructor TFoo.Create( const Name: string ); begin inherited Create; FName := Name; WriteLn( 'TFoo(', FName, ').Create' ); end; destructor TFoo.Destroy; begin WriteLn( 'TFoo(', FName, ').Destroy' ); inherited; end; procedure TFoo.Bar; begin WriteLn( 'TFoo(', FName, ').Bar' ); end; procedure CallConstFoo( const aFoo: IFoo ); begin aFoo.Bar; end; procedure CallFoo( aFoo: IFoo ); begin aFoo.Bar; end; procedure Test; begin CallConstFoo( TFoo.Create( 'ConstFoo' ) ); CallFoo( TFoo.Create( 'Foo' ) ); end; begin ReportMemoryLeaksOnShutdown := True; try Test; except on E: Exception do WriteLn( E.ClassName, ': ', E.Message ); end; ReadLn; end.
Delphi-Quellcode:
ReadLn
Code:
und somit einen MemLeak für die ConstFoo benannte Instanz
TFoo(ConstFoo).Create
TFoo(ConstFoo).Bar TFoo(Foo).Create TFoo(Foo).Bar TFoo(Foo).Destroy |
AW: MemoryLeak bei TList<IMyInterface>
Wir haben somit einen mindestens 20 Jahre alten "schweren" Fehler entdeckt. :firejump:
Mist, ich hätte noch eine Minute warten müssen, dann hätte ich die 10101 ergatterst. (jetzt muß ich noch einen Monat warten, bis zur 1010101 :stupid:) https://quality.embarcadero.com/browse/RSP-10100 Wie sieht das beim ARC aus? Müsste ja eigentlich gleich sein. |
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
Delphi-Quellcode:
ganz bewusst die Referenzzählung außer Kraft. Man muss halt wissen was man tut. Wir sind hier ja schließlich nicht im Java-Forum.
const
Du schreibst ja auch selbst, daß der Compiler eine Warnung ausgeben soll, die man dann ja selbst zu einen Fehler aufwerten kann, wenn man das möchte. Und eine fehlende Warnung ist vielleicht unschön, aber für den Fehler ist man schon selbst verantwortlich und nicht der Compiler - der kann ja dort nun mal nicht anders. Das Konstrukt ist syntaktisch und semantisch korrekt, tut sogar das was es soll, führt nur leider zu einem Memoryleak. Das täte ein fehlendes
Delphi-Quellcode:
bei jeder anderen Objektinstanz aber auch und dort gibt es auch keine Warnung vom Compiler (allerdings von anderen Tools).
Free
|
AW: MemoryLeak bei TList<IMyInterface>
Der Compiler könnte eine Temp-Variable erstellen und diese an den Parameter übergeben. :stupid:
Gut, daß macht der Compiler bestimmt, aber ich wette das ist eine Objekt-Variable, die hier ja nicht referenzgezählt sind. [edit] Dann könnte es im ARC doch funktionieren, aber dann müssten wie auch noch prüfen, ob das bei allen Objekten, auch aus Objektvariablen, bei Übergabe an Const-Interface-Parameter knallt. :shock: |
AW: MemoryLeak bei TList<IMyInterface>
Wie bereits im Ticket erwähnt, hat dies nichts mit dem direkten übergeben von erstellten Objekten zu tun. Dieses verhalten bekommt ihr bei impliziten casts von Objectinstanzen auf Interface bei Parametern.
|
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
Gab erst vor nicht allzu langer Zeit dazu eine Diskussion (und Ausreden, warums nich gemacht wird) Sehr lustig auch der "Closed" Kommentar zu dem von mir verlinkten Ticket: "Works as expected. Should not pass const interface parameter to non-const parameter." What the f...? Am besten einfach drauf sch...verzichten, dass sie es jemals fixen und hier nen upvote geben: http://fixinsight.userecho.com/topic...e-same-object/ |
AW: MemoryLeak bei TList<IMyInterface>
Im neuen QC ist er noch nicht doppelt. :mrgreen:
|
AW: MemoryLeak bei TList<IMyInterface>
generell:
Der Fehler ist ganz sicher als ein solcher zu bezeichnen. Über das Problem wird wohl jeder stolpern, der Interfaces in Delphi benutzt. Ein logisches und zu erwartendes Verhalten ist das jedenfalls nicht. Es ist für mich nicht nachvollziehbar, dass Emba das nicht in Ordnung bringt (ggf. mit einem Projektschalter: InterfaceMemoryLeakProblemBeibehalten = True). mein Problem: ... konnte ich damit aber noch nicht bereinigen. Muss ich heute Abend mal weiter graben. Ich erzeuge über eine Factory Objekte und verwalte die Schnittstellen in verschiedenen Listen. An einer Stelle stelle funktioniert das irgendwie nicht richtig (entweder gibt es MemoryLeaks oder Zugriffsverletzungen) und ich konnte den Fehler bisher trotz der oben Hinweise noch nicht finden. eine andere Frage noch: In den überwachten Ausdrücken kann ich ja MyObject.RefCount einsehen. Wenn ich aber nur eine Schnittstelle IMyInterface habe geht das nicht. Ein casten (MyInterface as TMyClass).RefCount funktioniert auch nicht. Wie kann ich den RefCount eines Interfaces (oder des Objektes hinter einem Interface) einsehen? |
AW: MemoryLeak bei TList<IMyInterface>
AS kann in den überwachten Ausdrücken nicht verwendet werden,
auch solltest du Seiteneffekte erlauben.
|
AW: MemoryLeak bei TList<IMyInterface>
Ok, danke für die Idee.
Ich dachte eigentlich, dass ich mich als NixBlicker oute. Dass die IDE das wirklich nicht anzeigt ist echt schwach. Das braucht doch eigentlich jeder, der intensiver mit Interfaces arbeitet... |
AW: MemoryLeak bei TList<IMyInterface>
"intensiv" ist gut, ich wollte eine gemeinsame Funktion (speicherverwaltung für sich gegenseitig referenzierende Interfacelisten) in einem generischen Interface+Klasse umsetzen, aber kurz vorm Ende, bin ich mal wieder an die Grenzen der Generics gestoßen. :wall:
|
AW: MemoryLeak bei TList<IMyInterface>
Hmm, offenbar sind gegenseitige Referenzen mein Problem.
Ich habe ein MyIntf, das eine Eigenschaft IMyParentList unterstützt. Das Interface weiß also, von welcher Liste es kontrolliert wird und kann diese über bestimmte Änderungen informieren. Jetzt wird aber wohl die IMyParentList nicht freigegeben weil MyIntf nicht freigegeben wird und anders rum. In der Richtung scheint das Problem zu liegen. Heute Abend geht´s dann weiter. So wie ich das verstehe, fällt das thematisch wohl auch in diesen Bereich: http://www.delphipraxis.net/184100-weak-tlist.html Aber mal noch 2 andere Dinge: Warum sind denn _AddRef und _Release nicht virtuell? Dann könnte man sich mal schnell reinhängen und die Ref-Änderungen leichter nachvollziehen. Sehr verwirrend finde ich auch das Verhalten im Constructor.
Delphi-Quellcode:
Der RefCounter wird erst mal erhöht und dann wieder auf 0 reduziert.
constructor TFoo.Create;
begin // RefCount = 0 IrgendwasInZeileEins; // RefCount = 1 ... hier nur um zu zeigen, dass es nicht mit inherited zusammenhängt inherited; // RefCount = 1 end; // RefCount = 0 .. FooObj := TFoo.Create; // RefCount = 0 FooIntf := TFoo.Create; // RefCount = 1 .. In bestimmten Fällen scheint er aber auf 1 zu bleiben und das FooIntf hat dann RefCount = 2. Bisher habe ich nicht gefunden, wann das auftritt. Irgendwas muss da im Contructor anders laufen (und meine Vermutung, dass ich da etwas vermurkst habe, konnte ich bisher nicht bestätigen). |
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
Jupp, fällt in den Bereich. :stupid: Zitat:
Du kannst aber (theoretisch) das Interface neu einbinden und auf die neuen/eigenen Methoden verweisen. Früher hatte ich einfach nur selber das IInterface implementiert, aber wenn du jetzt mal in TInterfacedObject nachsiehst, was dort gemacht wird ... das will ich nicht mehr selber (nach)machen. :freak: |
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
Delphi-Quellcode:
doch virtuell zu haben?
IFDEF DEBUG
|
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
|
AW: MemoryLeak bei TList<IMyInterface>
Ja, habe ich natürlich auch vor (hatte ich natürlich auch schon mal oberflächlich).
Aber egal, was dort passiert, das dürfte doch erst bei "inherited" abgearbeitet werden und nicht bei "begin"!? Daher bin ich davon ausgegangen, dass der Linker da irgendwas tut, das man innerhalb der Klasse nicht wirklich erkennen kann. |
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
Zitat:
|
AW: MemoryLeak bei TList<IMyInterface>
Das Pendant dazu steht dann unter
Delphi-Quellcode:
procedure TInterfacedObject.AfterConstruction;
|
AW: MemoryLeak bei TList<IMyInterface>
Ok, danke, so tief war ich da noch nicht eingestiegen (verstehe das auch jetzt nur ansatzweise).
Das Problem RefCounter mal 1 mal 2 konnte ich jetzt etwas eingrenzen: Von der Factory wird nur ein Objekt erzeugt und als Interface zurück gegeben. Wenn ich das Ergebnis einer lokalen Interfacevariable zuweise ist RefCounter 1. Die gleiche Zuweisung zu einem privaten Feld führt zu RefCount = 2. Ich kann mir vorstellen, dass es daran liegt, dass die Methode (aktueller Scope) auf das Feld zugreift und die Klasseninstanz als solche auch nochmal. Ist das so oder lässt sich das anders erklären?
Delphi-Quellcode:
TDings = class(TInterfacedObject)
private fBumsList: IGuidList; ... public constructor Create; override; destructor Destroy; override; ... end; constructor TDings.Create; var tmpBumsList: IGuidList; begin inherited; tmpBumsList := TFactory.GetNewGuidList; // tmpBumsList.RefCount = 1 fBumsList := TFactory.GetNewGuidList; // fBumsList.RefCount = 2 end; |
AW: MemoryLeak bei TList<IMyInterface>
Es kann sein, daß der Compiler die Variable tmpBumsList direkt als Result für GetNewGuidList verwendet.
Nein, das ist bestimmt so. Gemanagete Typen als Result werden in einen Var-Parameter umgewandelt. Aus
Delphi-Quellcode:
macht der Compiler ein
function GetNewGuidList: IGuidList;
Delphi-Quellcode:
.
procedure GetNewGuidList(var Result: IGuidList);
Da das Feld nicht direkt übergeben werden kann (bzw. er zu doof dafür ist), wird automatisch eine temporäre Variable generiert. Der Code des Compilers sieht also so aus
Delphi-Quellcode:
constructor TDings.Create;
var tmpBumsList: IGuidList; {$REGION 'AUTOGENERATED'} AutogeneratedVariable1: IGuidList; {$ENDREGION} begin {$REGION 'BEGIN'} tmpBumsList := nil; AutogeneratedVariable1 := nil; try {$ENDREGION} inherited Create; TFactory.GetNewGuidList(tmpBumsList); // tmpBumsList.RefCount = 1 TFactory.GetNewGuidList(AutogeneratedVariable1); Self.fBumsList := AutogeneratedVariable1; // fBumsList.RefCount = 2 {$REGION 'END'} finally tmpBumsList := nil; AutogeneratedVariable1 := nil; end; {$ENDREGION} end; |
AW: MemoryLeak bei TList<IMyInterface>
Zitat:
@Himi Und wo soll das jetzt nen Memoryleak sein? Temporäre RefCounts sind doch egal. Am Ende der Methode wird die compilergenerierte implizite Variable gecleart und alles is gut. :) |
AW: MemoryLeak bei TList<IMyInterface>
@himi
Einen IchVerneigeMichVorDir-Smily hat die DP ja nicht - hilfsweise dann so: :kiss: Das klingt einleuchtend und plausibel. Aber wie bekommt man das raus? @Stevie Das MemoryLeak ist woanders verursacht. Mein 1:2-Problem ergab sich als Fragestellung zwischendurch bei der Fehlersuche, ist aber nicht Fehlerverursachend. |
AW: MemoryLeak bei TList<IMyInterface>
Jupp, kein Speicherleck ... zwei Variablen = zwei Referenzen :D
Zitat:
Oder man versucht rauszubekommen was man im Assembler sieht. Aber so ist es die einzige sichere Möglichkeit, den Speicher/Referenzen auch bei Exceptions konsistent zu halten, also indem man den Speicher außenrum absichert. Die Result-Variable wird ja bei Exceptions einfach "ignoriert" und nicht weiter behandelt. |
AW: MemoryLeak bei TList<IMyInterface>
Jetzt wieder zum eigentlichen Thema: gegenseitige Referenzierung IListe <-> IEintrag ...
Ein klassischer Lösungsansatz geht offenbar über TAggregatedObject. Das Pointergeschiebe ist mir aber irgendwie suspekt (genauer gesagt verstehe ich das nicht ausreichend und erscheint mir das recht unsicher). Mir erscheint es für meinen Anwendungsfall sinnvoller, auf die automatische Referenzzählung zu verzichten. Diese finde ich (wenn es um Datenklassen geht) auch etwas fragwürdig. Ein abstraktes Beispiel: Ich habe ein Spiel mit einer Straße und einem Gullydeckel. Der Gullydeckel wird gesprengt (FreeAndNil bzw. :=nil). Wenn jetzt irgendeine Variable noch eine Instanz darauf hält, existiert der Deckel noch und die Figur kann weiter darüber laufen - tatsächlich müsste sie aber in das Loch fallen oder ein Fehler auftreten. Da ich meine Objekte ohnehin alle selbst verwalte und dies innerhalb meines geschlossenen Projektes, kann ich mir auch die Kontrolle über meine Objekte vorbehalten. Implementation von klassenübergreifenden Funktionalitäten (als eigentlichen großen Vorteil von Interfaces) kann ich ja weiter über Interfaces realisieren. So sollten auch gegenseitigen Referenzierungen kein Problem mehr darstellen (wenn ich das intern ordentlich verwalte - dafür gibt es ja wieder verschiedene Ansätze). Will da jemand Veto einlegen? Falls jemand zu dem Thema nachlesen will, hier mal meine gefundenen Links: http://www.delphipraxis.net/331268-post6.html http://www.delphipraxis.net/812576-post5.html http://www.delphipraxis.net/176352-w...-compiler.html http://www.delphipraxis.net/178822-d...verhalten.html http://www.delphipraxis.net/177999-w...tedobject.html http://www.delphipraxis.net/919620-post5.html <-- so sehe ich das jetzt auch http://www.delphipraxis.net/155315-i...zzaehlung.html http://forum.delphi-treff.de/index.p...-deaktivieren/ |
AW: MemoryLeak bei TList<IMyInterface>
Wie immer heißt es hier: kommt drauf an
Ich unterscheide schon seit einiger Zeit Klassen in Daten- und Serviceklassen. Siehe dazu diesen interessanten Artikel. Das ist, wie er dort erläutert, auch bei DI wichtig. Und aus diesem Grunde vermeide ich es auch, in den Datenobjekten zu viel Logik unterzubringen, die möglicherweise Abhängigkeiten auf Services nach sich ziehen. |
AW: MemoryLeak bei TList<IMyInterface>
Wenn ich mir die fremden Umbauten an meinem Beispielcode anseh, dann macht dort jemand scheinbar Unittests.
Kann also nicht mehr lange dauern, bis zur Problemlösung. :-D https://quality.embarcadero.com/browse/RSP-10100 |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:41 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz