W7 64 bit
The attached demos the issue on 11.4.
test.p creates an instance of ClassA
ClassA in the constructor creates an instance of ClassB and subscribes to the event. This causes the circular reference between the instance of ClassA and ClassB - as the instance of B keeps a reference of the instance of A to be able to execute the event handler as a callback.
test.p then assigns it’s reference to the instance of ClassA to ?. And the objects don’t go out of scope (messages from destructors only shown when the session exits).
When you remove the event subscription from ClassA’s constructor, both objects will go out of scope when test.p assigns the reference variable to ?.
Similar behavior between ClassC and ClassD – where C subscribes to an event from D and D subscribes to an event from C. No other references between C and D are kept besides the event subscription.
Run test.p to see that after test.p clears the reference both objects still stay around until after test.p exists (and the session closes).
And now with the attachments.
Great stuff, Mike. Thank you sir.
Looking at the code, I can see how the structure's a problem.
Since both "A" and "C" hold references to "B" and "D", then B and D will never go out of scope and get collected.
If the event subscription constitutes another reference, then that results in both scenarios having a circular reference, the classes will never go out of scope, and the Julian memory leak will strike again.
Personally I'd structure it differently:
This would completely avoid the circular references you're seeing now, the "Event" classes would get collected when the container class was collected, and then the "worker" classes when then go away.
Looking at the code, I can see how the structure's a problem.
Since both "A" and "C" hold references to "B" and "D", then B and D will never go out of scope and get collected.
If the event subscription constitutes another reference, then that results in both scenarios having a circular reference, the classes will never go out of scope, and the Julian memory leak will strike again.
Personally I'd structure it differently:
This would completely avoid the circular references you're seeing now, the "Event" classes would get collected when the container class was collected, and then the "worker" classes when then go away.
Flag this post as spam/abuse.
Looking at the code, I can see how the structure's a problem.
Since both "A" and "C" hold references to "B" and "D", then B and D will never go out of scope and get collected.
If the event subscription constitutes another reference, then that results in both scenarios having a circular reference, the classes will never go out of scope, and the Julian memory leak will strike again.
Personally I'd structure it differently:
This would completely avoid the circular references you're seeing now, the "Event" classes would get collected when the container class was collected, and then the "worker" classes when then go away.
Flag this post as spam/abuse.
It turns out this is a common OO problem - there's even a wiki page on it!
In .NET and Java there's a way to use weak references, which don't prevent garbage collection of the object they're referring to. If these could be implemented in ABL, than that could be a solution to this problem...
You could try experimenting with the .NET WeakReference class, but I doubt it will work as expected inside the AVM runtime..
I've used weak references in the past via the INTEGER() function, which returns a reference ("handle") to the object. You can store this anywhere as an integer without the object being prevented from garbage collection.
Turning an integer value into an object again requires that you walk the session object tree. From around 11.4 there's a method called ResolveWeakReference in OpenEdge.Core.Session which can do this for you, although it's trivial to implement (method included here)
/** Resolves a weak reference into an object instance. A weak reference is an integer representation of an object reference. This method is analogous to the WIDGET-HANDLE() function. Notes: * Based on http://msdn.microsoft.com/en-us/library/ms404247(v=VS.90).aspx * Performance of ResolveWeakReference() will probably suck. * An ABL statement "OBJECT-REFERENCE(int)" would entirely replace this method. @param integer A weak reference to an object. @return Object The object instance corresponding to that reference. The unknown value/null is returned if the referecen cannot be resolved. */ method static public Object ResolveWeakReference(input piReference as integer): define variable oInstance as Object no-undo. define variable oReference as Object no-undo. oInstance = session:first-object. do while valid-object(oInstance) and not valid-object(oReference): if piReference eq int(oInstance) then oReference = oInstance. oInstance = oInstance:Next-Sibling. end. return oReference. end method.
This approach is not as sophisticated as the WeakReference class, but seems to work well enough.
-- peter
Great find Peter! I'm just a bit worried about the "Performance of ResolveWeakReference() will probably suck." remark...
Have you done any performance tests of this method on applications with a reasonably large amount of objects?
> Have you done any performance tests of this method on applications with a reasonably large amount of objects?
Great find Peter! I'm just a bit worried about the "Performance of ResolveWeakReference() will probably suck." remark...
Have you done any performance tests of this method on applications with a reasonably large amount of objects?
Flag this post as spam/abuse.