Silverlight Shinobi

Secret techniques, tips and deadly tricks with Silverlight!

August, 2011

  • Identifying the root of a memory leak in Silverlight using windbg

    Considering all of the nasty blog comments out there you would think it be easy to create a test project to reproduce a leak – it wasn’t. I downloaded the latest version of SL4 and first attempted to create a leak using the inline template, then control template, then listbox items – all of these prior issues now appear to be resolved as I could not reproduce a leak using any of them with Build 4.0.60310.0 (Released April 19, 2011). In reviewing the release history (which I should have done prior to attempting to reproduce these myself) I can see various leaks were already fixed: nested popup (Build 4.0.60310.0), Controls leak INotifyDataErrorInfo.ErrorsChanged, Datagrid leak (4.0.60310.0), Inline data template leak (4.0.60129.0), Mousecapture & usercontrol and various leaks (4.0.50826.0).  

    So I ended up creating a more mundane simple leak that may occur if you register an event-handler somewhere such as in your view to an object that remains alive (such as a shared model) - and never unregister your event-handler and keep the event publisher instance alive. If you have something more exciting in a simple demo project please email me.  First I’ll show you this with windbg (free) and then I’ll show you in ANTS (my new favorite but not free debugging tool). In either case regardless of the tool it helps to have an idea where the leak(s) may be in advance. In other words – the tools don’t know the software’s intention. If you intend to have something collected then you can use these techniques to test to see if they are indeed collected or not. You can also check the number of instances and size of objects and inspect those in Gen 2 and LOH as prime suspects but this walkthrough doesn’t focus on that technique – just touches on it.

    Step 1

      1. Download the leaky sample zip file, unzip and open the VS 2010 project, set web project as startup and F5 debug to build and run it once. Copy the localhost url (including port) to notepad, and stop debugging in VS – we will be using windbg for this exercise.  

    1. Open that url directly in IE (you may want to close other instances of IE except for these instructions).
      1. Example: http://localhost:21589/LeakyExamplesSLTestPage.aspx#/Home

     

    1. Click “eventpinned”, then click back to home, then click “Force a GC”.

    Step 2

    1. If you haven’t already, install ProcessExplorer (free from Microsoft’s sysinternals). Use the target symbol from the toolbar – drag it onto your executing SL app to find and note the correct process ID. Also check that process “Image Type” column in ProcessExplorer to determine if it is 32 or 64 bit. If you don’t see that column in ProcessExplorer – right-click the column header and add it.

    Step 3

    1. Open the version of debugdiag that corresponds to your process Image Type (eg DebugDiag (x86) for 32bit). (Run à debugdiag).
    2. Find your process by ID, right-click, “create full user dump”

    Step 4 (enter windbg

    1. Open the corresponding version of Microsoft’s windbg (x86 for debugging your 32bit process) – right-click “Run as Administrator”. If you haven’t downloaded – go to windbg download – if you scroll to the bottom you will find direct links to both versions.
    2. Open the dump file you just created (File | Open Crash Dump)
    3. Now let’s load the Silverlight debugging extensions (sos) and the silverlight coreclr.

    0:000> .load C:\Program Files (x86)\Microsoft Silverlight\4.0.60531.0\mscordaccore.dll
    0:000> .load C:\Program Files (x86)\Microsoft Silverlight\4.0.60531.0\sos.dll

        4. Now let’s check the managed threads (remember though from previous session that your Silverlight thread may not show up in managed threads.. you may need to execute ~*kL):

     0:000> !threads
    ThreadCount: 3
    UnstartedThread: 0
    BackgroundThread: 3
    PendingThread: 0
    DeadThread: 0
    Hosted Runtime: yes
    PreEmptive GC Alloc Lock
    ID OSID ThreadOBJ State GC Context Domain Count APT Exception
    4 1 4d90 08b47888 220 Enabled 0a858950:0a859fe8 08a10978 0 STA
    29 2 578c 08a4f028 b220 Enabled 00000000:00000000 08a10978 0 MTA (Finalizer)
    30 3 21c0 08a52ab0 1220 Enabled 00000000:00000000 08a10978 0 Ukn

     

    1. Okay – that confirms we’ve got sos loaded…. but since we are not at a breakpoint of anything interesting in the code we aren’t searching for the clrstack… so we don’t need to track down the thread at this time. Instead we care about what is held in memory. Let’s get a summary of what’s in the heap:

     0:000> !eeheap -gc
     Number of GC Heaps: 1
     generation 0 starts at 0x0a661018
     generation 1 starts at 0x0a66100c
     generation 2 starts at 0x0a661000
     ephemeral segment allocation context: none
     segment begin allocated size
     0a660000 0a661000 0a859ff4 0x1f8ff4(2068468)
     Large object heap starts at 0x0b661000
     segment begin allocated size
     0b660000 0b661000 0b668260 0x7260(29280)
     Total Size: Size: 0x200254 (2097748) bytes.
     ------------------------------
     GC Heap Size: Size: 0x200254 (2097748) bytes.
     

    1. Interesting – but not what we are looking for. If you had serious memory leak problems you would see here Gen2 and the LOH grow between two memory dump snapshots. Now let’s look at all the objects in the heap… remember we are checking to see if our view (EventPinnedPage) has been GC’ed or not…

    0:000> !dumpheap –stat
    Statistics:
    MT Count TotalSize Class Name
    7aa1a178 1 12 System.Windows.Hosting.ManagedRuntimeHost
    7aa1941c 1 12 System.Windows.Browser.ManagedObjectInfo+ScriptMemberGroup
    [ ---- RESULTS CUT OUT FOR BREVITY --- ]
    794aff90 135 250928 System.Byte[]
    794b1a50 8284 274040 System.Object[]
    794c05e8 7939 365732 System.String
    Total 45639 objects

    1. Well…. There’s a lot of instances in the heap. Typically the System.String are expected and not something to worry about – but I can’t find my “EventPinnedPage” in this huge list. Let’s try a shell search for it:

    0:000> .shell -ci "!dumpheap -stat" find "Pinned"
    084b2164 2 208 LeakyExamplesSL.Views.EventPinnedPage
    .shell: Process exited

     Bingo we found it and it’s still in memory – let’s take a look at it:
    0:000> !dumpmt -md 084b2164
    EEClass: 070c5c0c
    Module: 06dc49e0
    Name: LeakyExamplesSL.Views.EventPinnedPage
    mdToken: 02000007
    File: LeakyExamplesSL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    BaseSize: 0x68
    ComponentSize: 0x0
    Slots in VTable: 67
    Number of IFaces in IFaceMap: 4
    --------------------------------------
    MethodDesc Table
    Entry MethodDesc JIT Name
    792ca980 790ca0e4 PreJIT System.Object.ToString()
    792ca9a0 790ca0ec PreJIT System.Object.Equals(System.Object)
    792caa10 790ca10c PreJIT System.Object.GetHashCode()
    792caa20 790ca124 PreJIT System.Object.Finalize()
    082c65a0 084b2118 JIT LeakyExamplesSL.Views.EventPinnedPage..ctor()
    082c6780 084b210c JIT LeakyExamplesSL.Views.EventPinnedPage.InitializeComponent()
    [-- deleted for brevity --- ]
    06dcca75 084b2120 NONE
    LeakyExamplesSL.Views.EventPinnedPage.commander_SomethingChanged(System.Object, System.EventArgs)
    082c6ca0 084b212c JIT LeakyExamplesSL.Views.EventPinnedPage.EventPinnedPage_Unloaded(System.Object, System.Windows.RoutedEventArgs)
     

    1. Okay but to find out what is causing our instance(s) of this to get stuck in memory we need to get to the instances themselves first. This command will list all instances in memory of a type (and their size) and most importantly – their virtual address.
    0:000> !dumpheap -type LeakyExamplesSL.Views.EventPinnedPage
    Address MT Size
    0a78b844 084b2164 104
    0a7ffc30 084b2164 104
    total 0 objects
    Statistics:
    MT Count TotalSize Class Name
    084b2164 2 208 LeakyExamplesSL.Views.EventPinnedPage

    Now lets take a look at one of those instances and find out what is causing it to stay around like that one pesky party guest that hangs out when he’s no longer welcome…

     

    0:000> !gcroot 0a78b844
    Note: Roots found on stacks may be false positives. Run "!help gcroot" for
    more info.
    Scan Thread 4 OSTHread 4d90
    Scan Thread 29 OSTHread 578c
    Scan Thread 30 OSTHread 21c0
    DOMAIN(08A54278):HANDLE(Pinned):6e112f8:Root: 0b664260(System.Object[])->
    0a78b8ac(LeakyExamplesSL.Common.MockCommandManager)->
    0a801b40(System.EventHandler)->
    0a801b28(System.Object[])->
    0a78d954(System.EventHandler)->
    0a78b844(LeakyExamplesSL.Views.EventPinnedPage)

    Well – that’s a pretty clear picture that the event handler to MockCommandManager is causing the problem. I must say that at this point Red Gates ANT memory profiler does a wonderful diagram (copied below). After playing around with that tool for a while I may just shell out the $500 out of pocket to use it.

     

     

  • Silverlight pre-cache dlls in browser cache - the secret compressed dlls as png Trick!

    This is a trick used by the Bing Maps Silverlight folks - but as far as I can tell (by Binging the web) I"m the first to actually blog about this secret technique and show you how it's done.

    I'm using the browser’s cache to download our dlls as PNG files from the ASPX page hosting the Silverlight control, then dynamically load those dlls after the application loading and they are already be in the browser’s cache.

    To keep this blog relatively short and sweet - I'll point out only the key parts of the example solution under each project - the rest of the code is self-explanatory.

    The solution contains 3 projects: (1) MyExternalLib (2) PngPerformanceTrick (3) PngPerformanceTrick.Web

    MyExternalLib
    This contains a page (representing your view) and a simple class library. This is a Silverlight Application project that when compiled creates a separate xap, the xap (as normal) is in fact a compressed zip file containing the application manifest (AppManifest.xaml) and the dll (MyExternalLib.dll). This library is NOT included in the main project's xap (PngPerformanceTrick) - thus reducing the over-all initial download time.Yet - we are able to have strong type references to this library from our main application project. This is a common technique used normally by adding a reference to the project and then setting the property on the reference to "Copy Local", and adding the attribute "MethodImplOptions.NoInlining" to avoid JIT issues prior to lazy-loading.

    I also added a post-build event to this project which copies the output xap to the web project's ExternalLibs folder as a png file. This could be done manually... but this approach makes it much faster to develop code and debug without any manual steps in-between.

    Post-Build Event (found by right-clicking project, properties, Build Events)

    Copy $(TargetDir)$(TargetName).xap $(SolutionDir)PngPerformanceTrick.Web\ExternalLibs\$(TargetName).png


     

    PngPerformanceTrick
    This is the main entry point application. It has the ExternalLibLoader and a single MainPage. The mainpage uses the ExternalLibLoader to load our library - and display's the ExamplePage on the mainpage. This project contains the ExternalLibLoader which loads the external library as a png (by name). As the web project already loaded this as a png - it will reside in the browser's cache improving loading performance. You can open up the xap for this project (by renaming to zip) and verify the dlls inside only - and it only contains the dll for this project (PngPerformanceTrick.dll).

    To accomplish this when I added the MyExternalLib project as a reference I right-clicked the reference, Properties, Copy Local=false. Also you will note that in the MainPage.cs where I actually use the MyExternalLib I set

    [MethodImpl(MethodImplOptions.NoOptimization|MethodImplOptions.NoInlining)]

    That is to avoid the JIT from pre-compiling prior to actually dynically loading the external library. That is handled in the ExternalLibrLoader.
     

    PngPerformanceTrick.Web
    This is the simple web project that hosts the main application xap. The only "fancy" thing here is that in the aspx page I've got an image tag that references the external library as a png. I've got some aspx code in there to iterate through all files in the "ExternalLibs" folder and create an image tag for each - so that as devs add more external projects this doesn't have to be updated.

    Tip: (You could speed things up even further by putting the PNGs in separate domains to get around the browser’s port limit for downloads).

    And that's it - now you can really maximize your silverlight application performance.

    If you have any questions or problems using the sample code, or suggestions for improvements - please let me know.

     

     

     

     

     

     

  • Debugging Silverlight with windbg (finding eaten exceptions)

    Lately lot's of people are asking me the question “Isn’t Silverlight dead?” Even with the post rumors still abound strong – likely due to the prevalence of html5 in the windows 8 demo. Today two people asked me. I tell them what I tell you – stay tuned to the 2011 Build Conference September 13th

    Today we will start where we left off in last post and we will take a quick look at exceptions... so load up windbg just like you did last week.

    Note that the “4” represents the 4th thread. We switched to that thread in the last post. Here we are listing all objects of type “Exception” that exist on the heap.

    0:004> !dumpheap -stat -type Exception

    And that gives us the results below.

    total 0 objects

    Statistics:

    MT            Count    TotalSize Class Name
    0cacdb20        1           32 MS.Internal.Error+GetExceptionTextDelegate
    0caa8a08        1           32 MS.Internal.Error+ClearExceptionDelegate
    0ca9f974        1           32 System.EventHandler`1[[System.Windows.ApplicationUnhandledExceptionEventArgs, System.Windows]]
    0c403200        1           32 System.UnhandledExceptionEventHandler
    0c42d5ec        1           80 System.UnauthorizedAccessException
    0c42b4ec        1           80 System.ExecutionEngineException
    0c42b45c        1           80 System.StackOverflowException
    0c42b414        1           80 System.OutOfMemoryException
    0c422a48        1           80 System.Exception
    0c421990        1           88 System.ArgumentOutOfRangeException
    0c4285fc        1           92 System.IO.FileNotFoundException
    792c044c        2          160 System.UriFormatException
    0c42d8dc        2          160 System.Threading.ThreadAbortException

    Total 15 objects

    _____________________________________________________________________

    Now you may recall we only threw (and caught) 3 types of Exceptions (ArgumentOutOfRangeException, FileNotFoundException, UnauthorizedAccessException) and we can see those there… but there are tons more also - so you may wonder where all of these exceptions on the heap are coming from? Some exception info is pre-loaded in the heap for certain exception types [todo: add more info here].

     Now let’s take a look at the “UnauthorizedAccessException”

     0:004> !dumpheap -mt 0c42d5ec       

    Address       MT     Size
    09e7a798 0c42d5ec       80    
    total 0 objects
    Statistics:

    MT    Count    TotalSize Class Name
    0c42d5ec        1           80 System.UnauthorizedAccessException

    Total 1 objects
    ____________________________________________________

    So if there are multiple instances of the same type of exception they will all be listed there and you can look at them individually. In this case there is only one, so let’s do a Print Exception (pe) on that address. 

    0:004> !pe 09e7a798

    Exception object: 09e7a798
    Exception type:   System.UnauthorizedAccessException
    Message:          you are unauthorized to do that!
    InnerException:   <none>
    StackTrace (generated):

        SP       IP       Function
        0342D6BC 00601F25 UNKNOWN!SLHelloWndbg.MainPage.button1_Click(System.Object, System.Windows.RoutedEventArgs)+0xd5

    StackTraceString: <none>
    HResult: 80070005

    And that gives us the exception details and the custom message string that we are throwing in the application. So keep in mind – even though this exception was eaten (caught and handled) we can still determine what exceptions are being thrown. These have a relatively short life-span on the heap so be sure to capture your dump at the right time.

    In DebugDiag (comes with Windows SDK) cancel out of the wizard. Then go to “Tools” in the toolbar, click “Add Rule” and add a first chance exception configuration (by clicking “Exceptions” in the Advanced Settings). Play around with these settings – you may want to make your action limit greater (say 50) to take the memory dump with several hidden exceptions in the heap for you to then examine. Be sure to point it at the right process (see previous post for details).

       and  

    Ah Grasshopper – you are truly learning very quickly now!