1

Introduction

I found out my code leaks memory of an instance of a TInterfacedObject that I keep as a Interface reference. Although I reset the reference variable to nil after usage, it remains alive.

The leaked object is of class TMasterObject, which implements two interfaces IInterfaceX and IInterfaceY. The object's reference is kept in a variable of type IInterfaceY.

TMasterObject's implementation of IInterfaceX is merely coincidental. Because it has two instances of TSomeObject, which requires a reference to this interface, I implemented it in TMasterObject as well.

MCVE / SSCCE

program InterfaceDependencies;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  IInterfaceX = interface end;

  IInterfaceY = interface end;

  TSomeObject = class(TObject)
    FReference: IInterfaceX;
    constructor Create(AReferenceX: IInterfaceX);
  end;

  TMasterObject = class(TInterfacedObject, IInterfaceX, IInterfaceY)
    FObjectA: TSomeObject;
    FObjectB: TSomeObject;
    constructor Create;
    destructor Destroy; override;
  end;

{ TSomeObject }

constructor TSomeObject.Create(AReferenceX: IInterfaceX);
begin
  FReference := AReferenceX;
end;

{ TMasterObject }

constructor TMasterObject.Create;
begin
  FObjectA := TSomeObject.Create(Self); // increments "FRefCount" by 1
  FObjectB := TSomeObject.Create(Self); // increments "FRefCount" by 1
end;

destructor TMasterObject.Destroy;
begin
  FObjectA.Free;
  FObjectB.Free;

  inherited;
end;

var
  LMasterObject: IInterfaceY;
begin
  try
    LMasterObject := TMasterObject.Create;
    // 'TMasterObject(LMasterObject).FRefCount' is now "3" because of 'FObjectA.FReference' and 'FObjectB.FReference'
    LMasterObject := nil; // decrements 'TMasterObject(LMasterObject).FRefCount' by 1
    // 'LMasterObject' is not destroyed because 'TMasterObject(LMasterObject).FRefCount' is still "2"
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Question

What strategies can be used to

  1. solve this specific problem and
  2. avoid such problems in future?
René Hoffmann
  • 2,766
  • 2
  • 20
  • 43
  • You're leaking `FObjectA` and `FObjectB`. You must destroy them in `TMasterObject` destructor. By doing that you decrement `FReference` reference count by remaining 2. – Victoria Nov 17 '17 at 09:50
  • It's true that these objects are leaked in that way, @Victoria, but the circular reference remains. Both would need to be addressed to avoid a leak. – David Heffernan Nov 17 '17 at 09:55
  • Relevant articles: http://delphisorcery.blogspot.co.uk/2012/06/weak-interface-references.html https://www.finalbuilder.com/resources/blogs/postid/410/weakrefence-in-delphi-solving-circular-interfac http://blog.synopse.info/post/2012/06/18/Circular-reference-and-zeroing-weak-pointers – David Heffernan Nov 17 '17 at 09:56
  • @David, oh, that's right. My mistake. `TMasterObject` destructor would be called when you reached ref. count 0, which can happen after you release those inner objects. – Victoria Nov 17 '17 at 09:58
  • @Victoria, you are right. I omitted the destructor code. I will add it for clarification. Still, David's point remains valid. – René Hoffmann Nov 17 '17 at 09:58
  • 1
    `FReference: IInterfaceX;` must be marked as `[weak]`, `[unsafe]` or stored as raw pointer - depending on the Delphi version you are using and other considerations - raw pointer and unsafe require that `TSomeObject` instance never outlives object instance stored in `FReference`. – Dalija Prasnikar Nov 17 '17 at 10:50
  • As a rule of thumb when using interface types in connection with parameters: always defined the parameter as `const` to "disable" the ref counting otherwise you have to deal with the reference issue you are describing. – ViRuSTriNiTy Nov 17 '17 at 13:02
  • 2
    @ViRuS That's not the case. For a method argument if you don't use const then the ref count is incremented on entry and then decremented on exit. For sure that can be avoided by const, but it never results in leaks. And here we are explicitly taking a reference in a variable of the instance. – David Heffernan Nov 17 '17 at 13:37
  • My solution is to implement `IInterfaceX` in a separate class and have a private reference to an instance of it in `TMasterObject` and pass it to the instances of `TSomeObject`. Thus, all instances are freed when `LMasterObject` goes out of scope or is set to `nil`. – René Hoffmann Nov 17 '17 at 14:15
  • @DavidHeffernan Yes, that ref count behaviour should be the case but at work we had serious issues with interfaces when not using const (i remember we were using Delphi XE). We had leaks all over the place, we debugged even the assembler code to see how the ref counting was done exactly but the ref counts were always wrong. Perhaps it was a bug in the compiler back then ... who knows. – ViRuSTriNiTy Nov 18 '17 at 18:29
  • @virus there is an issue with const args but it isn't what you said – David Heffernan Nov 18 '17 at 18:55
  • @ViRuSTriNiTy There is no bug in compiler. there never was such bug in compiler. You have probably mixed interface and object references. You can use interfaces with or without const in parameter. If it does not work properly, then error is in your code. – Dalija Prasnikar Nov 21 '17 at 13:28
  • @ViRuSTriNiTy Actually, there is a bug in compiler where const parameter changes the behavior, but the issue is constrained to inline construction of passed objects http://stackoverflow.com/q/4509015/4267244 and is in no way related to this question. – Dalija Prasnikar Nov 21 '17 at 13:41

0 Answers0