views:

298

answers:

5

Here is my scenario:

  1. Start my test program and immediately break into the debugger (WinDBG)
  2. Take a !DumpHeap -stat and observe there are no System.Net* or System.Xml* objects anywhere
  3. Create my WCF client, make a WCF request
  4. Close the WCF client and force GC.
  5. Take a !DumpHeap -stat and observer there are tons of System.Net*, System.Xml* objects still in memory.

So from my observation in step #5, it looks like WCF is not cleaning up properly (or more likely I am missing something). Why is this? How can I reclaim the memory taken up by these objects that shouldn't be here?

Here is my test code...

class Program
{
    static void Main(string[] args)
    {
        Debugger.Break();
        // !DumpHeap -stat here indicates no System.Net* or System.Xml* objects


        BasicHttpBinding binding = new BasicHttpBinding();
        binding.Security.Mode = BasicHttpSecurityMode.None;
        binding.UseDefaultWebProxy = false;
        WcfClient client = new WcfClient(
            binding, new EndpointAddress("http://myserver.com/service.svc"));
        client.Endpoint.Binding = binding;
        client.GetChain(new GetChainType()); // WCF call

        // my ineffective clean up code
        client.Close();
        client = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();


        // !DumpHeap -stat here indicates many System.Net* or System.Xml* objects

        Console.WriteLine("Request complete, press enter to quit");
        Console.ReadLine();
    }
}

P.S. Here are my !FinalizeQueue results...not sure if this is relevant

0:010> !FinalizeQueue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 1 finalizable objects (000000001b1527f8->000000001b152800)
generation 1 has 81 finalizable objects (000000001b152570->000000001b1527f8)
generation 2 has 0 finalizable objects (000000001b152570->000000001b152570)
Ready for finalization 0 objects (000000001b152800->000000001b152800)
Statistics:
              MT    Count    TotalSize Class Name
000007fef033d078        1           32 Microsoft.Win32.SafeHandles.SafePEFileHandle
000007fef0326ab0        1           32 System.Security.Cryptography.SafeProvHandle
000007fef0326680        1           32 Microsoft.Win32.SafeHandles.SafeTokenHandle
000007feef7a2c18        1           32 Microsoft.Win32.SafeHandles.SafeProcessHandle
000007feef7a2b78        1           32 Microsoft.Win32.SafeHandles.SafeFileMapViewHandle
000007feef7a2ad8        1           32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
000007feef793510        1           32 System.Net.SafeLocalFree
000007feef78d980        1           40 System.Net.SafeCloseSocket
000007feef7a34e8        1           48 Microsoft.CSharp.CSharpCodeProvider
000007fef030fb18        2           64 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
000007fef030fa78        2           64 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
000007feef791048        1           80 System.Net.Sockets.NetworkStream
000007fef03101f8        3           96 Microsoft.Win32.SafeHandles.SafeFileHandle
000007feef78caa0        1          120 System.Net.Sockets.Socket
000007fef02fdd10        2          128 System.Threading.ReaderWriterLock
000007feef78f520        4          160 System.Net.SafeRegistryHandle
000007feef78cd08        5          160 System.Net.SafeCloseSocket+InnerSafeCloseSocket
000007feef78f368        4          192 System.Net.SafeCloseSocketAndEvent
000007fef03071f8        2          208 System.Threading.Thread
000007fef02ffc40        7          224 Microsoft.Win32.SafeHandles.SafeRegistryHandle
000007fef02f4978       10          320 Microsoft.Win32.SafeHandles.SafeWaitHandle
000007fef02fdc70       20          640 System.WeakReference
000007feef7a1de0       10         1680 System.Diagnostics.PerformanceCounter
Total 82 objects

Adding !GCRoot trace of a random object that I think should be gone...

0:010> !GCRoot -nostacks 000000000288e110 
DOMAIN(000000000020B2B0):HANDLE(Pinned):e17b8:Root:0000000012840770(System.Object[])->
00000000028b5380(System.Collections.Hashtable)->
00000000028b53d8(System.Collections.Hashtable+bucket[])->
00000000028b5528(System.Collections.Hashtable)->
00000000028b5580(System.Collections.Hashtable+bucket[])->
00000000028c1f68(Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer)->
0000000002898a38(System.Xml.Serialization.XmlMembersMapping)->
0000000002898a70(System.Object[])->
0000000002898a98(System.Xml.Serialization.XmlMemberMapping)->
0000000002896950(System.Xml.Serialization.MemberMapping)->
00000000028b90a8(System.Object[])->
00000000028969b8(System.Xml.Serialization.ElementAccessor)->
0000000002896aa8(System.Xml.Serialization.StructMapping)->
0000000002895eb8(System.Xml.Serialization.StructMapping)->
00000000028928f8(System.Xml.Serialization.StructMapping)->
000000000288bd10(System.Xml.Serialization.StructMapping)->
0000000002890da8(System.Xml.Serialization.StructMapping)->
000000000288f8e8(System.Xml.Serialization.StructMapping)->
000000000288ea78(System.Xml.Serialization.StructMapping)->
000000000288dc70(System.Xml.Serialization.StructMapping)->
000000000288dd20(System.Xml.Serialization.NameTable)->
000000000288dd38(System.Collections.Hashtable)->
000000000288dd90(System.Collections.Hashtable+bucket[])->
000000000288e1f8(System.Xml.Serialization.AttributeAccessor)->
000000000288e2f0(System.Xml.Serialization.EnumMapping)->
000000000288e110(System.Xml.Serialization.TypeDesc)
A: 

I discovered when tracing in WinForms that you had to do a GC.Collect() twice with an Application.DoEvents() in between to force collection well enough to use a memory tracer to look for leaks.

I'll bet the same is true for WPF.

Joshua
Do you really mean WPF?...P is asking for WCF
Shankar Ramachandran
Thanks for the suggestion! I tried it and doesn't seem to work for this case though :(
RichAmberale
I really meant WPF but I somehow think this is an artifact of the core runtime so it should apply to WCF as well.
Joshua
+1  A: 

What is the necessity to force a Garbage Collection?

If your Channel is closed the CLR takes care of GC.

Check this out>>>

Shankar Ramachandran
I agree - GC.Collect should not be necessary. I'm doing it here to make sure the GC runs before I take a heap dump that still shows a bunch of WCF object laying around.
RichAmberale
+1  A: 

Did you check for the roots?

Naveen
Thanks, for the suggestion. I updated my question with a GCRoot trace on a random TypeDesc element (of which there are lots of hanging around).
RichAmberale
A: 

You are setting the client to null, before dispose is called on client.

Shiraz Bhaiji
Thanks for the suggestion. My understanding is that Close() is sufficient. Dispose isn't even exposed on my client. At your suggestion, I cast my client to an IDisposable and called Dispose() but this didn't seem to solve it (tried Dispose(), then Close() and vice versa).
RichAmberale
Have you tried not setting it to null before the GC
Shiraz Bhaiji
+1  A: 

The XmlSerializer architecture statically caches all kinds of information about the types it has encountered so that it doesn't constantly have to create serializers (which totally makes sense), so it's really no surprise to me that these things are not being cleaned up. Judging by the fact that your !GCRoot dump is littered with things from the System.Xml.Serialization, it sure seems to be the culprit.

Drew Marsh
Agree. You could also do a !SaveModule and open it up in reflector to checkout the type.
Naveen
Thanks! This seems to the answer. The WCF framework seems to be doing similar smart caching. I think that accounts for the System.Net* and related objects I see sticking around.
RichAmberale