Sometimes it looks like select IS broken: A WPF memory leak

time to read 7 min | 1378 words

One of the most annoying bug reports that we got for NH Prof is that it crash with out of memory exception after long use. We did the usual checkups, and the reason for the memory leak was obvious, something kept a lot of objects internal to WPF in memory. In fact, here are the heavy hitters, as extracted from the dump:

Count Size (kliobytes) Type
5,844 3,337 System.Byte[]
69,346 5,474 System.String
49,400 37,198 System.Object[]
1,524,355 47,636 MS.Utility.SingleItemList`1[[System.WeakReference,mscorlib]]
3,047,755 71,432 MS.Internal.Data.ValueChangedEventArgs
1,523,918 71,434 MS.Utility.ThreeItemList`1[[System.WeakReference,mscorlib]]
3,048,292 71,444 MS.Utility.FrugalObjectList`1[[System.WeakReference,mscorlib]]
3,048,292 95,259 System.Windows.WeakEventManager+ListenerList
3,047,755 166,674 MS.Internal.Data.ValueChangedEventManager+ValueChangedRecord
3,056,462 191,029 System.EventHandler
7,644,217 238,882 System.WeakReference

As you can see, this just says that we are doing something that cause WPF to keep a lot of data in memory. In fact, this looks like a classic case of “memory leak” in .NET, where we aren’t releasing references to something. This usually happen with events, and it was the first thing that I checked.

It took a while, but I convinced myself that this wasn’t that. The next step was to try to figure out what is causing this. I’ll skip the sordid tale for now, I’ll queue it up for posting at a later date. What we ended up with is a single line of code that we could prove caused the issue. If we removed it, there was no leak, if it was there, the leak appeared.

That was the point where I threw up my hands and asked Christopher to look at this, I couldn’t think of something bad that we were doing wrong, but Christopher and Rob are the experts in all things WPF.

Christopher managed to reproduce this in an isolated fashion. here is how it goes:

public class TestModel : INotifyPropertyChanged
{
private readonly DispatcherTimer timer;
private int count;

public event PropertyChangedEventHandler PropertyChanged = delegate { };

public TestModel()
{
timer = new DispatcherTimer(DispatcherPriority.Normal)
{
Interval = TimeSpan.FromMilliseconds(50)
};
timer.Tick += Timer_Tick;
timer.Start();
}

public IEnumerable Data
{
get
{
return new[]
{
new {Name = "Ayende"},
};
}
}

private void Timer_Tick(object sender, EventArgs e)
{
if (count++ % 100 == 0)
{
GC.Collect(2, GCCollectionMode.Forced);
Console.WriteLine("{0:#,#}", Process.GetCurrentProcess().WorkingSet64);
}
PropertyChanged(this, new PropertyChangedEventArgs("Data"));
}
}

This is a pretty standard model, showing data that updates frequently. (The short time on the time is to show the problem in a short amount of time.) Note that we are being explicit about forcing a GC release here, to make sure it isn’t just waste memory that haven’t been reclaimed yet.

And the XAML:

<Grid>
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>

Executing this will result in the following memory pattern:

image

Looking at the code, I really don’t see anything that is done wrong there.

I uploaded a sample project that demonstrate the issue here.

Am I going crazy? Am I being stupid? Or is select really broken?