Detecting hung Objects in Visual FoxPro
March 31, 2008 •
One of the big problems with Visual FoxPro is that the garbage collector makes it very difficult to completely release object references in some cases. It's well known that the VFP garbage collector will refuse to release objects if circular references exist – ie. You've passed a reference of one object to a second object which stores it on its own properties and when you then try to clear the second object, the object will appear to be null and cleared but in effect this object is still live in the background as its hanging on to the reference of the first object. Unless you first release the first object reference on the second, you'll end up with a hung reference that persists even after the object is 'released' in code by setting it to NULL or .F. You've effectively created a memory leak in your application.
In Web Connection and the Web Control Framework for example I use a rather large number of objects to define the page layout of an HTML page where each control is an object. These objects have interdependencies with properties like Parent and ChildControls that reference other controls and so there's a purposeful relationship that requires circular references.
This is a well known and fairly well understood problem and there are workaround for it, which basically involve having explicit code in place to release child references. In Web Connection WCF each object has an explicit Dispose method that is used to clean up itself and any child objects for example. Here's the core Dispose implementation on the base wwWebControl class:
************************************************************************
* wwWebControl :: Dispose
****************************************
FUNCTION Dispose()
LOCAL loCtl, lnX
*** Don't do if we were already here
IF THIS.lDisposeCalled
RETURN
ENDIF
this.lDisposeCalled = .t.
*** Explicitly release child controls
IF THIS.IsContainerControl AND !ISNULL(this.ChildControls)
FOR lnX = 1 TO this.ChildControls.Count
loCtl = this.ChildControls.aItems(lnX,2)
IF __DEBUGMODE && Public Var should be defined prior to using component
TRY
loCtl.Dispose() && This can be problematic during debug
CATCH
ENDTRY
ELSE
loCtl.Dispose() && This can be problematic during debug
ENDIF
loCtl = null
ENDFOR
this.ChildControls = null
ENDIF
*** Release all objects
THIS.Page = null
THIS.ParentControl = null
THIS.ViewState = null
THIS.PreservedProperties=null
*THIS.Context = null
THIS.Attributes = null
ENDFUNC
* wwWebControl :: Dispose
The method basically goes through and cleans up any objects that were explicitly declared and this method is explictly called from cleanup code of the page, which in turn drills into all child controls to clean up.
Note that Dispose() needs to be MANUALLY called from code – you can't rely on Destroy() to fire Dispose() for you. Therein lies the core of the FoxPro problem. Destroy for a class doesn't fire until all instances/references of that class have been released. If another reference exists anywhere else – even on an already 'destroyed' object the Destroy() method is not fired.
What this means is that in many situations code placed into the Destroy() method is not effective of actually release resources of a class. It also means that for applications like the Web Control Framework that purposefully use circular references (ie. Parent, Child Controls) have to explicitly build code – and error handling recovery – that can release resources. In the Web Control Framework there's a Dispose method in the top level wwWebPage class that is called as part of the page processing cycle. When Dispose() is fired on page it ends up calling dispose on all child controls in the entire page via the code above that recursively digs into all controls. If Dispose is called on all controls (and all controls clean up properly after themselves) then everything is fine.
The problem with this is that it requires a ton of discipline to do this right. You have to remember to implement your Dispose() methods and match any declarations. That's the easy part. The hard part – especially in desktop applications – is to figure out when an how to call the Dispose() method for each object. In desktop apps there's usually not a clean cleanup point, and instead you end up with events that create and cleanup objects so if there are interdepencies it's easy to end up with hung references.
Tip: How can you detect hung references?
So once you have hung references, how do you find them? Hung references by definition belong to objects that have been released from the variable stack, but that still are effectively live and holding references to other objects. IOW, there effectively invisible objects that you can't access via FoxPro code.
To detect a problem like this, one tool is to use the VFP SYS(1016) which returns memory usage for objects used in Visual Foxpro. If you run code and this memory usage keeps increasing even after you come back to a starting point you probably have a leak issue. Some fluctuation here is normal, but generally this level should stabilize once an application is running and most of the features have loaded. In Web apps you should definitely see this variable stay stable if everything releases properly between hits.
The next and harder question though is this: I know I have leak, where is it? There's no easy way to figure out which object is leaking. But there's a trick you can use to take a pretty good guess.
At the shutdown of your application – after all of your own application cleanup code has run – and most everything should have been cleaned up and released issue the following:
SET STEP ON
CLEAR ALL
This will release all objects that are still alive at this point and cause the debugger to step into each of the Destroy methods (assuming there's code there) for these objects. By stepping into this code you can look at the debugger dialog to see WHICH object is hanging. This won't tell you which object is the culprit that has the hanging reference but hopefully the child reference will give you a good idea to narrow down your options. The parent object will also still be live so it should fire later in the Destroy sequence.
Note that this trick works only if there's code in the Destroy() method of the child object that's not been released.
This has been an invaluable trick to help me debug circular reference issues in Web Connection not just in the framework itself (and there have been those doozeys to debug <g>), but also in application level code that declares objects and fails to clean them up in some cases. Seeing the objects that are actually hung is a big help in isolating the probem.
harvey mushman
April 25, 2008