Self destructing object instance

Using reference counting classes always requires some caution. You have to pay attention to reference cycles, using interface reference for referencing such object instances, not calling FreeAndNil and more... it is quite a list...

But every once in a while, new ways of shooting yourself in the foot keep popping up... So here goes the story...

I have two reference counted classes that hold reference to each other instances. One of those references is marked as [weak] to prevent creating strong reference cycle.

type TFoo = class(TInterfacedObject) private [weak] FRef: IInterface; public constructor Create(const ARef: IInterface); end; TBar = class(TInterfacedObject) private FFoo: IInterface; public constructor Create; virtual; destructor Destroy; override; procedure AfterConstruction; override; end; constructor TFoo.Create(const ARef: IInterface); begin inherited Create; FRef := ARef; end; constructor TBar.Create; begin inherited; end; destructor TBar.Destroy; begin inherited; end; procedure TBar.AfterConstruction; begin inherited; FFoo := TFoo.Create(Self); end; procedure Test; var Intf: IInterface; begin Intf := TBar.Create; writeln(Assigned(Intf)); // TRUE as expected end; // AV here
But I cannot successfully finish construction of TBar object instance and exiting Test procedure triggers Access Violation exception at _IntfClear.
Exception class $C0000005 with message 'access violation at 0x0040e398: read of address 0x00000009'.
Stepping through debugger shows that TBar.Destroy is called before code reaches writeln(Assigned(Intf)) line and there is no exception during construction process.

Why is destructor called during construction of an object here and why there is no exception?

Reference counting overview

To understand what is happening here we need short overview of how Delphi ARC works on reference counted object instances (ones implementing some interface) under classic compiler.
Reference counting basically counts strong references to an object instance and when last strong reference to an object goes out of scope, reference count will drop to 0 and instance will be destroyed.

Strong references here represent interface references (object references and pointers don't trigger reference counting mechanism) and compiler inserts calls to _AddRef and _Release methods at appropriate places for incrementing and decrementing reference count. For instance, when assigning to interface _AddRef is called, and when that reference goes out of scope _Release.

Simplified those methods generally look like:
function TInterfacedObject._AddRef: Integer; begin Result := AtomicIncrement(FRefCount); end; function TInterfacedObject._Release: Integer; begin Result := AtomicDecrement(FRefCount); if Result = 0 then Destroy; end;

Construction of reference counted object instance looks like:
  1. construction - TInterfacedObject.Create -> RefCount = 0
  • executing NewInstance
  • executing chain of constructors
  • executing AfterConstruction chain
  1. assigning to initial strong reference Intf := ...
  • _AddRef -> RefCount = 1
To understand actual problem we need to dig deeper in construction sequence, particularly NewInstance and AfterConstruction methods
class function TInterfacedObject.NewInstance: TObject; begin Result := inherited NewInstance; TInterfacedObject(Result).FRefCount := 1; end; procedure TInterfacedObject.AfterConstruction; begin AtomicDecrement(FRefCount); end;

Why is initial reference count in NewInstance set to 1 and not to 0?

Initial reference count must be set to 1 because code in constructors can be complex and can trigger transient reference counting which could automatically destroy the object during the construction process before it has chance to be assigned to the initial strong reference that will keep it alive.

That initial reference count is then decreased in AfterConstruction and object instance reference count is properly set for further reference counting.

Problem

Real problem in this questions code is in fact that it triggers transient reference counting in AfterConstruction method after call to inherited which decreases initial object reference count back to 0. Because of that, object will have its count increased, then decreased to 0 and it will self destruct calling Destroy.

While object instance is protected from self destruction inside constructor chain, for a brief moment it will be in fragile state inside AfterConstruction method and we need to make sure that there is no code there that can trigger reference counting mechanism during that time.

Actual code that triggers reference counting in this case is hidden in rather unexpected place and it comes in form of [weak] attribute. So, the very thing that should prevent instance from participating in reference counting mechanism actually triggers it - this is a flaw in [weak] attribute design reported as RSP-20406.

Solution(s)

  • If possible, move code that can trigger reference counting from AfterConstruction to constructor
  • Call inherited at the end of AfterConstruction method instead of the beginning.
  • Perform some explicit reference counting on your own by calling AtomicIncrement(FRefCount) at the beginning and AtomicDecrement(FRefCount) at the end of AfterConstruction (you cannot use _Release because it will destroy the object)
  • Replace [weak] attribute with [unsafe] (this can only be done if TFoo instance lifetime will never exceed TBar instance lifetime

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