This project is read-only.

Memory Leak "resolved"

Jun 20, 2009 at 9:51 PM
Edited Jun 20, 2009 at 9:59 PM

OK, let me start by saying that I make no claim to have fixed all the memory leaks in Slide.Show 2.  However, the minor fixes detailed below (which are easy to implement) address what I've found to be the major memory leak issue that plagued the application, particularly for albums with lots of photos.  Basically, the problem was that for every image that the Silverlight Slide.Show application displayed, the app would retain the associated ImageViewer in memory.  A new ImageViewer was created every time and was never released, in part because some event handlers were never being correctly unsubscribed.  For a details on why that is bad, check out these posts...

Why you should always unscubscribe event handlers

.NET Memory Leak Case Study: The Event Handlers That Made The Memory Baloon  (by Tess...see below)

To identify the culprits, I used the techniques described by Tess Fernandez on her excellent blog ("If broken it is, fix it you should"), including her details on Debugging Silverlight applications with windbg and sos.dll and her Lab 7: Memory Leak - Review.  If you aren't familiar with debugging memory issues using windbg (as I wasn't before deciding to tackle the slide.show memory useage problems), I hightly recommend her blog.  She is a debugging goddess. 

OK, so you've now got some background....

The specific problem in our application here is in the SlideViewer class.  Notice that in SlideViewer.OnApplyTemplate() method, SlideViewer subscribes to the App.Resized and App.FullScreenChanged Events.  However, it never unsubscribes from those events.  So, as long as the App instance is around and in use (and it is, if slide.show is up and running), then an instance of the SlideViewer will not be garbage collected because of the reference to the App events.  The more photos you had in your album, the worse the problem could be.

Now notice that the SlideViewer implements the IDisposable interface.  But, the Dispose() method is empty.  This is the perfect place to unsubscribe from the events.  To do so, just add the following two lines to the existing (and previously empty) Dispose method in SlideViewer.cs...

protected virtual void Dispose(bool disposing)
{
    Application.Current.Host.Content.Resized -= ContentResized;
    Application.Current.Host.Content.FullScreenChanged -= ContentResized;
}

Now as you probably know (or maybe you don't or knew but forgot, I'll make no assumptions here), the Dispose() method is not automatically called by the GC.  You must explicitly call the Dispose method on any object that you need to implement IDisposable for.  It turns out that in Slide.Show, instances of SlideViewer are created and maintained in the various ITransition base classes (CrossFadeTransition, ShapeTransition, SlideTransition, and TranslationTransitionBase).  Notice in the Execute method of each of these classes that they Create two new SlideViewer instances for each transition (one for the fromSlide and one for toSlide).  To fix the memory leak, we simply need to call the Dispose() method on the SlideViewer instances prior to creating new SlideViewer instances.  For each of those for ITransition classes, simply modify the Execute() method and add in the following lines after the call to MediaRoot.Children.Clear() and MediaRoot.Resources.Clear():

if (toSlide != null)
    toSlide.Dispose();
if (fromSlide != null)
    fromSlide.Dispose();

Prior to implementing these fixes, in my test site with 100 pictures in two albums, I would see the memory usage just constantly climb up to 500 MB or more and nothing was ever released.  Now, watching the iexplorer process you can see that while it will climb for a while, when the GC comes along it will now free up huge chunks of memory periodically and you don't see the long-term constant growth in memory usage.

There may be additional memory leaks, even some caused by event handlers not being unsubscribed, but if they exist they are extremely minor based on my testing.  I actually went so far as to remove all of the anonymous delegates first (as was suggested in this issue: 2701 Memory Leak) and making sure that they were unsubscribing appropriately, but doing so was a) extremely tedious and I don't wish to bore you with the details and b) didn't appear to have any impact on performance or memory useage.  Given how these other events that weren't being unsubscribed were used, it wasn't a shock (most were one-time events on single-instance objects like configuration).  The issues described here, however, are for an object (SlideViewer) created and destroyed repeatedly during the life of the application. 

For new people (again, making no assumptions), here is what you need to do to resolve the problem:

1) make the code changes described above to Controls\SlideViewer.cs, Transitions\CrossFadeTransition.cs, Transitions\ShapeTransition.cs, Transitions\SlideTransition.cs, and Transitions\TranslationTransitionBase.cs
2) recompile the SlideShow project
3) copy the new Vertigo.SlideShow.xap file from the ClientBin directory to your web site.
4) enjoy you new slide show with much greater client-side memory usage.

Hope this helps
--Scott

Jul 6, 2009 at 5:51 AM

Fixed in change set 25804. Note that the change set includes additional enhancements and bug fixes as well. Also, note that the change set addresses additional memory leaks not included in the discussion thread.

http://slideshow2.codeplex.com/SourceControl/changeset/view/25804