Is it leaking or not?

Catching memory leaks on the ARC compiler can be a daunting task. Even if the destructor of an object instance is called, that still does not mean your object is completely released.
In the classic desktop compiler, a quick test to see whether some object instance was released or not was to add a breakpoint (or some logging code) into the destructor, and if you got there, all was good. This is not the case with the ARC compiler.

The last method called in the object instance lifecycle after which all instance memory will be completely released is TObject.FreeInstance. While this method is common to both ARC and classic compilers, on the classic compiler executing the destructor would inevitably (unless it throws an uncaught exception) mean executing FreeInstance and completing the release cycle. On the ARC compiler, there is no such guarantee. The destructor can also be called directly via DisposeOf, and FreeInstance will be called only after all strong references to the object are cleared. Since calling the destructor does not imply clearing of all strong references FreeInstance may or may not run. If it does not run - you have a memory leak.

Fortunately, FreeInstance is also a virtual method, which means it can be easily overriden. Of course, you can also use TVirtualMethodInterceptor and intercept the FreeInstance method for a particular object instance, but overriding FreeInstance is the fastest way to confirm the memory leak. Of course, if you already have your leaking object suspect.

The following class is broken in such a way that it creates a strong reference cycle with itself inside its constructor. This is the simplest example of an unbreakable strong reference cycle, where even DisposeOf will not help. It will merely call the destructor of the object, but the memory allocated on the heap will never be released.

type TFoo = class(TObject) public Strong: TFoo; constructor Create; destructor Destroy; override; procedure FreeInstance; override; end; constructor TFoo.Create; begin Strong := Self; end; destructor TFoo.Destroy; begin Log.d('FOO DESTROY'); inherited; end; procedure TFoo.FreeInstance; begin Log.d('FOO FREEINSTANCE'); inherited; end;

If you use the above class in the following manner, niling the object reference is not enough to trigger the destructor. The strong reference inside the object itself holds it alive. Neither Destroy nor FreeInstance will be executed.

Example 1.
var foo: TFoo; begin foo := TFoo.Create; foo := nil; end;

Using DisposeOf instead of niling helps a bit, but it will only take you so far. It will only call the destructor, but not FreeInstance. If you have such a case where FreeInstance is never called on an object, that means the object is leaking.

Example 2.
var foo: TFoo; begin foo := TFoo.Create; foo.DisposeOf; end;

To completely release our foo object instance, we have to fix the code. There are several ways to do so. First, the most obvious one is not to take a strong reference to Self. Commenting out that code in the constructor fixes the code and FreeInstance will be called in both of the above examples.
The same would happen if we mark the Strong reference inside the TFoo class with the [weak] or [unsafe] attribute.

type TFoo = class(TObject) public [weak] Strong: TFoo;

There is also another way, less ARC friendly, and that is clearing the strong reference cycle inside dthe estructor, by explicitly clearing the Strong reference. However, in that case you must use DisposeOf to force execution of the destructor code. Merely niling the object like in the first example will not help.

destructor TFoo.Destroy; begin Log.d('FOO DESTROY'); Strong := nil; inherited; end;

Comments

Popular posts from this blog

Delphi 12.1 & New Quality Portal Released

Coming in Delphi 12: Disabled Floating-Point Exceptions

Assigning result to a function from asynchronous code