Minor memory leak introduced in .NET Framework 2.0 SP1

NOTE: This article was originally published as a post in the .NET and Memory blog (by Andreas Suurkuusk, co-founder of SciTech Software AB).

A while ago we received a support request from a customer that had a memory leak in his application. After showing, closing and disposing an MDI child form, the Form instance was still alive. The root path indicated that the child form instance was kept alive by the MDI parent, via the internal propertyStore field. I tried to reproduce the problem using an MDI application that was easily available: the Game of Life application used in the .NET Memory Profiler tutorials. And to my surprise the Game of Life application had a memory leak, even after the memory leak fix in the tutorial had been applied.

After having collected the last snapshot in the second .NET Memory Profiler tutorial (after closing the Game of Life MDI window), there should be no new instances of any type (since the memory leak should have been fixed). Thus if the type filter is set to only show types “With new or removed instances”, the Types list shold be empty. If the original version of .NET Framework 2.0 is used, this is almost true. A single new <GCHandle> instance will exist, but for now I will ignore this (it will be discussed in a future post).

GameOfLife snapshot (without SP)

However, testing the tutorial after having installed .NET Framework 2.0 SP1 shows completely different results (as can be seen in the picture below). Even though no additional instances have been created (Delta is 0), there are still a lot of New and Removed instances.

GameOfLife snapshot (SP1)

At the time of the last snapshot, no Game of Life windows are open, so I would expect all LifeForm instances to be GCed, but there’s still one new live instance left. So the new LifeForm instance is a good candidate for memory leak analysis. The details of the new LifeForm instance is shown in the picture below.

LifeForm instance details

The shown root path of the instance indicates that a reference to the LifeForm instance is stored in a PropertyStore. The PropertyStore class is used internally by WinForms to save space when storing property values. Instead of using instance fields, the dictionary-like PropertyStore is used. Anyway, after doing some digging around using Reflector, I came to the conclusion that the LifeForm instance was put into the PropertyStore using the Form.FormerlyActiveMdiChild property. This is a private property that seems to be only used to update the window icon when an MDI child is deactivated. Below is the code originally used to update the icon:

if( this.FormerlyActiveMdiChild != null )
{
    this.FormerlyActiveMdiChild.UpdateWindowIcon(true);
this.FormerlyActiveMdiChild = null;
}

It seems like Microsoft tried to fix a problem with the icon update when the deactivated form was actually closing, so in .NET Framework 2.0 SP1, the code now looks like this:

if( this.FormerlyActiveMdiChild != null &&
    !this.FormerlyActiveMdiChild.IsClosing )
{
    this.FormerlyActiveMdiChild.UpdateWindowIcon(true);
    this.FormerlyActiveMdiChild = null;
}

However, this causes the assignment “this.FormerlyActiveMdiChild = null” to be skipped and a reference is left to the closed (and possibly disposed) MDI form. This prevents it (and all its child instances) from being GCed. It would probably be more correct if the original code was changed to:

if( this.FormerlyActiveMdiChild != null )
{
    if( !this.FormerlyActiveMdiChild.IsClosing )
    {
        this.FormerlyActiveMdiChild.UpdateWindowIcon(true);
    }
    this.FormerlyActiveMdiChild = null;
}

Note that this memory leak only causes a single form (and its child instances) to be kept in memory. If you open and close another form, the original form will become eligible for GC (i.e., the memory leak doesn’t grow over time). Nonetheless, it is preferable to make sure that all unused instances are correctly GCed. One workaround to this problem is to use reflection to set the private property “FormerlyActiveMdiChild” to null, for instance in the MdiChildActivate event in the MDI parent. Something like this:

protected override void OnMdiChildActivate( EventArgs e )
{ base.OnMdiChildActivate( e ); try { typeof( Form ).InvokeMember( "FormerlyActiveMdiChild", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic, null, this, new object[] { null } ); } catch( Exception ) { // Something went wrong. Maybe we don't have enough // permissions to perform this or the // "FormerlyActiveMdiChild" property no longer // exists. } }

Most of the text in this post is copied from my reply to a forum question at http://forum.memprofiler.com/viewtopic.php?t=1160. In the forum I said that the problem was introduced in .NET Framework 3.5. However, installing .NET Framework 3.5 will automatically install .NET Framework 2.0 SP1 and .NET Framework 3.0 SP1, and the memory leak exists in the .NET 2.0 assembly “System.Windows.Forms.dll”.

Unfortunately, this problem affects the .NET Memory Profiler tutorials. When we get the time to update them, we will have to take this into consideration.