Rick Strahl's FoxPro and Web Connection Web Log
White Papers | Products | Message Board | News |

Detecting hung Objects in Visual FoxPro


Monday, March 31, 2008, 2:44:00 PM

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.

Posted in:

Feedback for this Weblog Entry


Re: Detecting hung Objects in Visual FoxPro



harvey mushman
Thanks, this has come up before. I like the debug, set step on trick. Very clever!<g>

--hm

Finding memory leak in COM Server



Darren
any idea how to do this in a COM Server? Clearly SET STEP ON isn't an option! :(

re: Detecting hung Objects in Visual FoxPro


Thanks Rick for yet another informative post.
On a similar note, I was looking for a hung object but the memory leak turned out to be a foxpro feature. When foxpro loads an image like with the image control, it keeps the image in memory in case it is needed again. You have to issue [release resources] or [release resource <<filename>>] to free up the memory.

re: Detecting hung Objects in Visual FoxPro


Good grief... Every time I run into a VFP problem, I dig into it enough to think I understand the issues, and when I Google during my research, Rick has already tackled it and explained it well enough to help me finish solving my problem. Again!

I have now added Dispose() method into my base business object class, and my objects can now be cleanly and fully released from memory.

Thanks, Rick!!

 



© Rick Strahl, West Wind Technologies, 2003 - 2018