Sunday, June 3, 2012

Weak interface references

We all know how painful it can be working with interfaces and reference counting when it comes to circular or cross references.

Vincent Parrett wrote about that a while ago and presented a nice solution.

The only disadvantage about his solution was the special class type (TWeakReferencedObject) you have to inherit from to use a weak reference to. What if you want to use a weak reference to something that already exists and that you cannot change?

That is where my idea comes in.

The Weak<T> type supports assignment from and to T and makes it usable as if you had a variable of T. It has the IsAlive property to check if the reference is still valid and not a dangling pointer. The Target property can be used if you want access to members of the reference.

Let's assume you have that typical parent child relationship where both have a reference to each other. Normally that would cause a memory leak because that cross references would keep both objects alive. Change the parent reference to be a weak reference and both objects get destroyed properly because the child is not keeping the parent alive.

type
  TParent = class(TInterfacedObject, IParent)
  private
    FChild: IChild;
    procedure AddChild(const AChild: IChild);
  public
    destructor Destroy; override;
  end;

  TChild = class(TInterfacedObject, IChild)
  private
    FParent: Weak<IParent>;
  public
    constructor Create(AParent: IParent);

    function GetParent: IParent;
  end;

So how to check if the object of the reference is still valid? That is done by hooking the TObject.FreeInstance method so every object destruction is noticed and if a weak reference for that object exists it gets removed from the internal list where all weak references are stored.

While it works I am aware that this is hacky approach and it might not work if someone overrides the FreeInstance method and does not call inherited. It also is not yet threadsafe. It also might have a small performance impact because of the dictionary access in every FreeInstance call.

But hey, nothing is for free, isn't it? ;)

23 comments:

  1. I will probably implement a weak reference based on a "fake" class, created at runtime, using RTTI.
    Using the code included within our mORMot, if could be very easy to create such a fake class on need, which will create a dedicated stub block, able to redirect the interface calls into the original one.
    It may be used as such:
    SetWeak(aParent,FParent,TypeInfo(IParent));

    It will be also necessary to hack FreeInstance, to ensure no GPF will be made, just like the ARC model of Apple. See http://blog.synopse.info/post/2011/12/08/Avoiding-Garbage-Collector%3A-Delphi-and-Apple-on-the-same-side

    But AFAIK FreeInstance is ALWAYS called, even if there is no "inherited" coded within the overriden Destroy implementation. There is an hidden boolean parameter added to Destroy, to ensure this.

    And my proposal will work with every versions of Delphi, even before generics (I'll do it for Delphi 6 up to XE2, just like other mORMot code).

    ReplyDelete
    Replies
    1. So what you are saying is you basically create a proxy interface for the weak reference, right?

      Of course FreeInstance is always called. But FreeInstance is a virtual method and if someone overrides this without calling inherited, the hooked method is not called.

      Delete
    2. 1) I suspect that if anyone override FreeInstance, he/she knows what he is doing: changing the whole memory management of class instances in Delphi. So it is not a big potential issue here.

      2) I wonder about performance issues, even the dictionary approach can be slow, when you search for a pending weak reference at every instance release. We would need a O(1) implementation pattern in such case: not to search within a list. Dictionary speed depends on hashing, and hash of a pointer is commonly not O(1) ready.

      3) Yes, my proposal is a proxy interface for the weak reference: a proxy class which is able to be called directly. It would be very fast, since the proxy VMT will be created only once for the whole class.

      4) Where can your code be found out?

      Delete
    3. I have looked into the SQLite3Commons.pas and found the code you were referring to. Can you probably show something that does this proxy VMT creation? I am missing a few pieces since I am able to do that by myself like how to dynamically create the correct stub and such. That is why I relied on the stuff from RTTI to not dig into handcrafting asm code by myself (also because I don't want to do that again for x64 and future architectures)

      My code can be found on http://code.google.com/p/delphisorcery/

      Delete
    4. I've found the code in http://code.google.com/p/delphisorcery/source/browse/trunk/Source/Core/DSharp.Core.Weak.pas
      About performance, sounds like a O(n) operation (with n depending on the pointer hash, which is very basic in the current instance) at every class instance release. Could affect global application speed. We need profiling here.

      The VMT creation is for x86 only yet. There is no current proxy generation in the current code - just fake class implementation of the remote service access. Of course, using generics allow cross-platform implementation. My proposal is to use RTTI and map the VMT at runtime, just once for the 1st call of a given class. It won't be too much difficult to adapt to x64, or we may rely on TypInfo.pas. But I suspect that creating a proxy is easier that creating the service client fake class: we could probably just copy the VMT as exposed by the caller. That was just a proposal, much difficult to implement that your present pattern.
      I'm still investigating about a potential more direct implementation - perhaps like the one proposed in http://www.deltics.co.nz/blog/?p=876 i.e. using "implements".

      Delete
  2. Just for reference:
    http://andy.jgknet.de/blog/2009/06/weak-interface-references/

    ReplyDelete
    Replies
    1. Thanks, from a quick look this seems to be similar to Vincents approach of implementing a special interface.

      Delete
    2. @Stefan
      Andy's solution is elegant, cross-version (works before Delphi 2009/2010), and with a very fast implementation.
      It handles parent instance release as expected (i.e. as with your code), and code is easy to read and maintain. IMHO this code is even better than Vincent's.
      It tries to take thread-safety in account, which is worth an applause!
      It will definitively be faster than current DSharp.Core.Weak.pas implementation, for handling parent instance release.

      @kmb
      Thanks for pointing out this great link!

      Delete
    3. @kmb
      Thanks too, very interesting link

      unfortunately I tested andy's solution and found out that simply adding his unit in uses clause causes many (not always) internal errors C4310(tested with D2007)

      looking at QC report 67364 and it's workaround, I will try to comment out the helper and test with calls to the procedure version of AssignWeakIntf : hope this will work..

      Delete
  3. I managed weak reference (in Delphi 7) in the following way:

    WeakReference:

    FParent: Pointer;

    FParent := pointer(aParent) // aParent : IParent

    GetParent:
    Result := IParent(FParent);

    (Similar code can be found in the VCL)

    Regards,
    Stefano Moratto
    www.csiat.it

    ReplyDelete
    Replies
    1. That is just a work around (of course with Delphi 7 there were no language features to really support this) which has following disadvantages:
      - no type safety (using just a pointer variable/field
      - no direct access (have to use the hardcast to the interface again)
      - no check if the pointer is still valid!

      The last one is the important one to me because without possible check for dangling pointers weak references are very dangerous.

      Delete
    2. I agree, this is the only weapon available in D7.
      However the type safety in not a real problem because I wrote get and set method for each interface type I needed in some base class.

      For the sake of brevity I omitted the check for nil and since this is a weak reference there are other mechanisms to set it to nil when the referenced object is freed. In the parent/child example it would be a method IParent.RemoveChild(aChild:IChild) calling aChild.SetParent(nil).

      I suggest to wrap the pointer in a record using generic. (I'm still on D7)

      Delete
  4. I may be simple, but I always used simple pointers for a weak reference, e.g. from Child to Parent. Yes, I had to cast the pointer to the proper interface, but that does not change the lifetime, so it can be used as weak pointer.

    What am I missing?

    ReplyDelete
    Replies
    1. I have pointed out the benefits of this over hardcast to/from Pointer in previous comments.

      Delete
  5. @Stefan: Why should a check for dangling pointers be important? If the child has a weak reference to the parent, it can simply assume the parent is still there. If not, the child would not exist anymore either. Similar premiss for other cyclic references. A check for dangling pointers is never necessary (or should never be necessary, assuming proper design), IMO.

    IOW, if a check for dangling pointers is necessary, there is something fundamentally wrong with the design of the code that uses the interfaces.

    ReplyDelete
    Replies
    1. Having a hierarchy does not mean that everything gets freed if the parent gets freed. Because it might not only be the parent that has a reference to its children.

      Asking if something is still there is no wrong design. It's just different from getting told when this something gets removed.

      Delete
    2. Needing to ask if something is still there is, IMO, a sign of, well, a weak design. In a proper design, that is not necessary.

      By storing all references created an checking them later, ISTM you have an enormous and unnecessary overhead.

      Delete
    3. @velthuis
      In my blog article (see below) I tried to explain why zeroing is needed in some cases.
      It is not for nothing that Apple introduced it in their Mac OS X 10.7 ARC model...

      But I do agree that in most cases, with a proper design, you should not need to use it: weak pointers should be enough, and will introduce no overhead. Such KISS design should always be preferred.

      Sadly, from my little experiment, random access violations on customer are so difficult to track... Therefore, having zeroing weak pointers at hand does make sense, in a non-perfect world, when you do not have time to make proper code refactoring of a huge existing application.

      Delete
  6. Another implementation, freshly made for our Open Source framework: http://blog.synopse.info/post/2012/06/18/Circular-reference-and-zeroing-weak-pointers

    It has several differences with other implementations:
    - TObject.FreeInstance is hacked at the class VMT level, so will only slow down the exact class which is used as a weak reference, and not others (also its inherited classes won't be overriden) - and it will allow custom override of the virtual FreeInstance method;
    - The unused vmtAutoTable VMT slot is used to handle the class-specific orientation of this feature, for best speed and lowest memory use;
    - Thread-safe implementation (only Andreas' version is told to be as such);
    - Tested for Delphi 6 up to XE2 (no use of generics nor RTTI);
    - Optimized for speed (e.g. use hashing when any internal list has more than 32 items) and memory use: in particular, it will avoid any speed penalty for existing TObject instances.

    We needed this Zeroing ARC feature within our mORMot framework, since it now features interface-based client-server services implementation, and try to implement SOLID principles.
    Any feedback is welcome!

    ReplyDelete
    Replies
    1. I like the ideas, however:

      Using the vmtAutoTable slot for a framework specific feature is very intrusive. Now other code that might also make use of Hallvards excellent hack (I am not saying I do not like it!) does not work.

      So without that the FreeInstance hook on class level cannot be done anymore because the old FreeInstance pointer was stored in that TSQLRecordClass instance.

      Another thing I noticed: while you are claiming the support for Delphi 6 up to XE2 (which is a good thing but totally not the goal of my library) your code will not run on 64-bit (which indeed IS a goal of my library), not speaking of OSX (most of mine does neither).

      Thread safety is indeed a thing I want to implement.

      Delete
    2. Thanks for your feedback!

      You could change the vmtAutoTable trick into a more "standard" hash-based global list, containing all TSetWeakZeroClass instances. So the FreeInstance hook on class level will continue to work as expected - and I suspect this is a path to follow, for the lowest possible performance penalty on the application (using a global hook is IMHO far from efficient). Having such TSetWeakZeroClass per-class instances sounded like a good pattern, also for speed and memory use.

      Note that you can nest vmtAutoTable references, if needed. Therefore, existing code that make use of "Hallvards excellent hack" is compatible with this implementation (not SOLID, but possible). For instance, in mORMot we already used this VMT slot to access pre-compiled RTTI at class level for the ORM part of the framework. And it is compatible with the TSetWeakZeroClass use: you can create a zeroing weak pointer for an interface implemented by a ORM class. In fact, this is the reason why our SetWeak/SetWeakZero functions are included within the main unit of the framework, not the more generic SynCommons.pas file. So that's why I was not afraid of re-using the same vmtAutoTable hack, since it is already used by our framework.

      I think it could be easily extracted from mORMot, then ported to x64 and OSX. All pointer arithmetic is already using PtrInt (=NativeInt), and will work even with FPC. In its current implementation, it is tied to mORMot, but it is on purpose. It was not meant to be used stand-alone, but as part of the framework.

      So you are right about those implementation restrictions, there are on purpose. And of course, they do not match every one expectations!

      I just hope ideas and implementation patterns could be inspiring, and be adapter to every need!

      Delete
    3. I had exactly that idea. I am now using a dictionary with the replaced FreeInstance pointers to call them from the hook. Please take a look at my latest changes if you got a minute.

      About OSX: since we are using Read/WriteProcessMemory which are windows related it might not be that easy on OSX (I recently asked how to do that on OSX: http://stackoverflow.com/questions/10854910/writeprocessmemory-equivalent-on-os-x). Since I do not have a MAC I was not able to test anything that was mentioned in the answer there.

      Delete
  7. Sounds good, but where can I download it?

    ReplyDelete