Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: Object aggregation and memory leaks using [weak] attribute

I want to build a class TParent containing several child objects by using aggregation. Some of the objects are independant, while some can be also dependant on the other children. All the children objects have to have a reference to the parent. I also want to use the interfaces where possible.

For this purpose I am using TInterfacedObject for the TParent and TAggregatedObject for the children. As both the child and the parent know about each other, I am using weak references in order to avoid the circuar dependency. In fact this behaviour is already defined in TAggregatedObject. Everything works fine when I am using only the independant child objects (TIndependantChild).

The problem arises when the child object depends on the other children too, see the constructor of TDependantChild. I store the reference to the another child object in fChild variable, which is marked with the [weak] attibute, introduced in Delphi 10 Berlin. FastMM4 reports memory leaks on shutdown:

enter image description here

Also access violation leading to System.TMonitor.Destroy raises, but this happens only when FastMM4 is in the uses and ReportMemoryLeaksOnShutDown is True.

program Project1;

{$APPTYPE CONSOLE}

uses
  FastMM4,
  System.SysUtils;

type
  IParent = interface
  ['{B11AF925-C62A-4998-855B-268937EF30FB}']
  end;

  IChild = interface
  ['{15C19A4E-3FF2-4639-8957-F28F0F44F8B4}']
  end;

  TIndependantChild = class(TAggregatedObject, IChild)
  end;

  TDependantChild = class(TAggregatedObject, IChild)
  private
    [weak] fChild: IChild;
  public
    constructor Create(const Controller: IInterface; const AChild: IChild); reintroduce;
  end;

  TParent = class(TInterfacedObject, IParent)
  private
    fIndependantChild: TIndependantChild;
    fDependantChild: TDependantChild;
  public
    constructor Create;
    destructor Destroy; override;
  end;

{ TParent }

constructor TParent.Create;
begin
  fIndependantChild := TIndependantChild.Create(Self);
  fDependantChild := TDependantChild.Create(Self, fIndependantChild);
end;

destructor TParent.Destroy;
begin
  fDependantChild.Free;
  fIndependantChild.Free;
  inherited;
end;

{ TDependantChild }

constructor TDependantChild.Create(const Controller: IInterface; const AChild: IChild);
begin
  inherited Create(Controller);
  fChild := AChild;
end;

var
  Owner: IParent; 

begin
  ReportMemoryLeaksOnShutDown := True;
  Owner := TParent.Create;
  Owner := nil;
end.

I found, that using [unsafe] instead of [weak] solves the problem, but according to the delphi help

It ([unsafe]) should be only used outside the System unit in very rare situations.

Therefore, I am not convinced, that I should use [unsafe] here, especially when I don't understand what happens.

So, what are the reassons of the memory leaks in this situation and how to overcome them?

like image 955
VyPu Avatar asked Nov 03 '17 02:11

VyPu


1 Answers

Issue with the leak and the crash when you use external FastMM4 memory manager is related to following issue concerning finalization of internal HashMap used for tracking weak references.

[REGRESSION XE2/10.1 Berlin] Unable to use 3rd party memory managers

Due to that issue it is impossible to use 3rd party memory managers for leak detection in Delphi 10.1 and newer versions, and including external FastMM4.

That is the reason why you have issues with [weak] attribute and you don't have issues with [unsafe].


As far as your code is concerned, you can safely use [unsafe] in above scenario. While there is warning in documentation about using [unsafe] attribute, that warning actually does not explain why [unsafe] should not be used.

To make long story short, you can use [unsafe] attribute when lifetime of object instance referenced by [unsafe] reference is longer than the lifetime of the reference itself.

In other words, you must ensure that you don't access [unsafe] reference after the object instance it points to has been released and that is all.

[unsafe] references are not zeroed out when the object they point to is destroyed, and using such reference after the object is gone will result in access violation exceptions.

Replacing the [weak] attribute with the [unsafe] is all you have to do in order to have properly functional code as you presented it.

  TDependantChild = class(TAggregatedObject, IChild)
  private
    [unsafe] fChild: IChild;
like image 194
Dalija Prasnikar Avatar answered Nov 07 '22 02:11

Dalija Prasnikar