UserControl and millions of undisposed Font instances

Use this forum for questions on how to use .NET Memory Profiler and how to analyse memory usage.

Moderator: SciTech Software

UserControl and millions of undisposed Font instances

Postby FrankyDM » Tue May 27, 2008 7:46 am

I've been tearing my hair out over a resource leak problem in a WinForms program I've developed. Users report errors such as:

System.OutOfMemoryException: Out of memory.
at System.Drawing.Graphics.FromHdcInternal(IntPtr hdc)
at System.Drawing.BufferedGraphicsContext.CreateBuffer(IntPtr src, Int32 offsetX, Int32 offsetY, Int32 width, Int32 height)


and:

The operation completed successfully
at System.Drawing.Font.ToHfont()
at System.Windows.Forms.Control.FontHandleWrapper..ctor(Font font)
at System.Windows.Forms.OwnerDrawPropertyBag.get_FontHandle()
at System.Windows.Forms.TreeView.CustomDraw(Message& m)


Users get this error after intensive use of the program for a few hours.
I think it's all related to my use of the FlowLayoutPanel control, where I dynamically add UserControls, instantiated by code. These UserControls are then removed from the FlowLayoutPanel and Disposed, to be replaced by others, etc. The UserControls are panels with TreeViews on them, + a label and button.

-----

Debugging:

I have created a very simple new WinForms program, with just a main form and a Timer. The timer creates, and disposes an empty UserControl (with just one label, using a bold font) at each Tick. That's all. I've set the timer interval to 1 ms. Setting it slower makes no difference, so there is no timing issue in my test. (the interval is enough to allow the code to run and GCs to take place).

Interestingly, running this simple test under MemoryProfiler, shows that huge amounts of undisposed instances are left behind for the following types:
- EventHandlerList
- FontFamily
- Font

The first (EventHandlerList) doesn't worry me much, since I read in these forums that this is not necessarily a resource or memory leak (?).

The Font and FontFamily types however (and also WindowsFont in my original program) DO worry me, and may be the key to my problems.

What's a bit strange is that the Profiler snapshots show 10 million Undisposed instances (after running a night at 1ms interval), and the Realtime display reports 15 million.

What I'd like to find out now is to make sure that I'm not on some wrong track with my tests, and that I'm just seeing artifacts.

The sequence (C#) that I execute in my test is:

UserControl uc = new UserControl1();
uc.Dispose();
uc = null;


It seems that creating a UserControl and then Disposing it does not actually dispose all of its resources?

Here are screendumps of the MemProfiler snapshow and realtime windows:
http://www.eazign.be/Temp/MemProf_Realtime.jpg
http://www.eazign.be/Temp/MemProf_Snapshots.jpg

And here is a ZIP of the simple test program I described:
http://www.eazign.be/Temp/MemProfileTest.zip

I'd be extremely grateful for any advice on this. Thanks!
FrankyDM
 
Posts: 3
Joined: Tue May 27, 2008 7:23 am

Postby Andreas Suurkuusk » Tue May 27, 2008 9:25 pm

An undisposed instance does indicates that an instance has been collected (or at least finalized) without being properly disposed. Since the instance have been collected (and the finalizers have run), having a lot of undisposed instances do not indicate a memory or resource leak. It does however indicate that the memory or resource utilization might not be optimal.

Failing to dispose a Font or FontFamily instance has some effect on memory and resource utilization, since it has a finalizer and it wraps an unmanaged resource. However, it's not likely that failing to dispose these instances are causing your memory problem. Do you have any other types that are using a lot of memory? Have you tested to run with the unmanaged resources tracker enabled? If you have a resource leak you will get additional information about it if you use the resource tracker.

I looked into the issue with the discrepancy between undisposed instances in the real-time view and the snapshots. It seems like we have introduced a bug when fixing another problem with the dispose tracker. The dispose tracker counts finalized instances as undisposed, even if they're not actually disposable instances, which gives a higher total count of undisposed instances in the real-time view. The number presented for undisposed instances of specific types are correct though. A fix for this problem will be released within a few days.
Best regards,

Andreas Suurkuusk
SciTech Software AB
Andreas Suurkuusk
 
Posts: 970
Joined: Wed Mar 02, 2005 7:53 pm
Location: Sweden

Postby FrankyDM » Wed May 28, 2008 8:21 pm

Thanks very much, Andreas, for the quick and thorough reply! You've been a great help already.

As I learned from your answer, I now realize that Undisposed instances are not necessarily leaks, just non-optimal cases of memory handling.

Hmmm ... I had to upgrade from Standard to Professional edition to be able to track resources ;)

I do have a feeling I'm much closer to the root of my problem however. With Win resource "Total instances" in the real-time graph, I could easily see these increase systematically, as I opened and closed my Dialog, which has many (up to 50) treeViews in a FlowLayoutPanel.

What's really nice is that I was able to create a very simple Windows Forms app to reproduce the problem, with just a button on the Main form. This button opens a dialog with 100 treeview controls. That's all. Each time you click the button and close the dialog, 100 ImageLists (HIMAGELIST) and 200 Bitmaps (HBITMAP) have been created and stay there, even after a GC.Collect. Interestingly, this only happens when you set the property "Checkboxes" for the TreeView. I guess this results in 2 bitmaps (1 checked, 1 unchecked) to be created in the image list. But why aren't these disposed along with the TreeView??

The dialog creation is put in a "using" clause, so it's properly disposed.

In fact, after 25 times opening and closing this Dialog, the app starts behaving funny, no longer drawing the controls...

Does this ring any bells? I'm stuck here.
I'm running the latest .Net 2.0 build.

Here is a screendump of the MemProfiler snapshot window after 10x opening + closing the dialog:
http://www.eazign.be/Temp/MemProf2.jpg

And here is a ZIP of the simple test program I described:
http://www.eazign.be/Temp/MemProfileTest2.zip

And here a ZIP of the saved session:
http://www.eazign.be/Temp/ProfilerSession2.zip

Any further ideas or directions would be warmly appreciated!
Thanks.
FrankyDM
 
Posts: 3
Joined: Tue May 27, 2008 7:23 am

Postby Andreas Suurkuusk » Thu May 29, 2008 12:01 pm

Hi,

I tested your application and it does have an unmanaged resource leak. Each time the TreeView form is opened and closed, 200 HBITMAPs, 200 HDCs and 100 HIMAGELISTs are left-over.

When I started investigating this I thought that there was actually a resource leak in the unmanaged Tree-View control, but then I found the following information about the Tree-View Control Window Styles:
TVS_CHECKBOXES
(...)
Once a tree-view control is created with this style, the style cannot be removed. Instead, you must destroy the control and create a new one in its place. Destroying the tree-view control does not destroy the check box state image list. You must destroy it explicitly. Get the handle to the state image list by sending the tree-view control a TVM_GETIMAGELIST message. Then destroy the image list with ImageList_Destroy.


Investigating the System.Windows.Forms.TreeView implementation reveals that the state image-list created when enabling "CheckBoxes" is not destroyed. To fix this problem you can add code to explicitly destroy the image-list, for instance by handling the HandleDestroyed event or deriving the TreeView class and overridding OnHandleDestroyed. The code below shows an example on how you can do it:

Code: Select all
public class FixedTreeView : TreeView
{
    protected override void OnHandleDestroyed(EventArgs e)
    {
        if( this.CheckBoxes )
        {
            IntPtr hStateImageList = SendMessage( this.Handle, TVM_GETIMAGELIST, (IntPtr)TVSIL_STATE, IntPtr.Zero );

            if( hStateImageList != IntPtr.Zero )
            {
                ImageList_Destroy( hStateImageList );
            }
        }
        base.OnHandleDestroyed( e );
    }
   
    #region Windows interop

    const int TV_FIRST = 0x1100;
    const int TVM_GETIMAGELIST = (TV_FIRST + 8);
    const int TVSIL_STATE = 2;

    [DllImport( "User32" )]
    static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);


    [DllImport("comctl32")]
    [return:MarshalAs( UnmanagedType.Bool)]
    static extern bool ImageList_Destroy(IntPtr himl);
   
    #endregion
}


There is a problem with this workaround though. If Microsoft corrects this problem, there is a risk that the image-list gets released twice, which can possibly cause problems. Hopefully the state image list will be set to IntPtr.Zero after being released, in which case the workaround still works.

Another thing I noticed was that there were also some HWNDs left-over when opening and closing the form. Investigating this turned out that these HWNDs were actually released, but the profiler missed that. We have corrected this problem and the problem with the discrepancy between the total undisposed instances numbers. A new maintenance release containing the fixes is available at http://memprofiler.com/MemProfilerInstaller3_1_301.msi. It has not been made officially available yet, but it probably will be later today or tomorrow.
Best regards,

Andreas Suurkuusk
SciTech Software AB
Andreas Suurkuusk
 
Posts: 970
Joined: Wed Mar 02, 2005 7:53 pm
Location: Sweden

Postby FrankyDM » Sun Jun 01, 2008 9:21 am

BRILLIANT!

Thanks a lot for your detailed replies, Andreas, and of course for a wonderful piece of software.

I can honestly say I've been struggling with this problem for over 8 months, going different directions, trying out different strategies. Without a specialized tool for these types of debugging, it's pretty much working in the dark.

I've tried out the code you gave in your last post ... and updated to your latest release. Then went back to the simple test program with 100 TreeViews , and already in the real-time view I could see the changed behavior: no more systematic rising of used resources.

I've now changed my actual software to have all TreeViews derive from the changed implementation, which does the clean-up, and ...

PROBLEM SOLVED!

I'm quite amazed as well to see such a major 'leak' to be present even in the latest version of the 2.0 .Net framework! If I'm correct, this means that all 2.0 .Net applications that use TreeViews with checkboxes are leaking resources...

Well, I just wanted to report this back to you. I've now released a new version of my software with the fix, and I'm confident I won't get any reports back from users that the program crashes after a few hours of intensive use.

Your software + excellent support has been a true life-saver for me.
Thank You.
FrankyDM
 
Posts: 3
Joined: Tue May 27, 2008 7:23 am


Return to Using .NET Memory Profiler

Who is online

Users browsing this forum: Google [Bot] and 2 guests

SciTech Software logo

© Copyright 2001-2016. SciTech Software AB
All rights reserved.


SciTech Software AB
Kartvägen 21
SE-175 46 Järfälla
Sweden


E-mail: mail@scitech.se

Telephone: +46-706868081

cron