2012/8/27 Michael Schnell :
Show quoted text 8 lines
On 08/24/2012 10:36 PM, Marco van de Voort wrote:
>
> I read that as that there will be ref counted objects, not that all
> objects will be ref counted per se. --
Ok, but if there are reef-counted objects what is the point in not using
them ?
consider for example this code (simplified real world example):
TConnection is a reference counted object and the only other object that holds a reference to it is its Buddy object and the following method is called by LNet which does (and can) NOT hold a reference to it, only a TMethod callback that does not affect the reference count. Now carefully watch what happens when this method is called.
procedure TConnection.OnTCPFail(ASocket: TLHandle; const Error: String);
begin
FDisconnectLock.Acquire;
if not FShuttingDown then begin
FShuttingDown := True;
//no more callbacks
FSocket.OnRead := @Client.DummySocketEvent;
FSocket.OnWrite := @Client.DummySocketEvent;
FSocket.OnError := @Client.DummySocketError;
FSocket.Disconnect();
// remove references to the connection in all other objects.
if Assigned(FBuddy) then begin
if IsOutgoing then
FBuddy.SetOutgoing(nil)
else begin
FBuddy.SetIncoming(nil); // bug: this will free ourselves NOW!!!
end;
FBuddy := nil; // Crash because we are free already!
end;
end;
FDisconnectLock.Release;
end;
The very moment we clear the (last and only) reference to ourselves in FBuddy we have committed suicide effective immediately(!) and while the method is still running its own object instance has been freed already and the next attempt to access any of our own(!) fields will segfault! The reason is the LNet component that actually called this method did not hold a properly reference counted reference!
These type of bugs are not easy to find (or to understand) if you are not fully aware who is holding a reference (and even more importantly who is not!) and when exactly the refererence counter will reach zero (and maybe from within which thread this might happen).
Instead of explicitly callling free() on all your objects in the correct order you have to make sure you set the last variable holding a reference to nil at the correct time and in the correct order to trigger the destructors in the correct order and not too early (and also not too late!), this can sometimes be even more complicated to achieve than just explicitly calling Free()
The pragmatic fix to he above problem was:
procedure TConnection.OnTCPFail(ASocket: TLHandle; const Error: String);
begin
Self._AddRef;
FDisconnectLock.Acquire;
[...]
FDisconnectLock.Release;
Self._Release; // <-- now we will be freed exactly here!
end;
If such behavior were suddenly the norm for *all* objects (and not only for interfaces or otherwise explicitly and knowingly reference counted objects) then I am sure most of currently existing code would completely stop working from one day to the other and large parts of many applications and frameworks would have to be rewritten from scratch.