Memory Leak in CFClientBase<T> Service Proxy for Compact Framework .NET


Version 3.5 of the .NET Compact Framework didn’t initially ship with any direct support for calling remote WCF services. Technically all of the network-related components necessary were there, but getting a working WCF client stood up without something like the full .NET framework’s ServiceModel Metadata Utility Tool (svcutil.exe) would be a lot of very tedious work. Thankfully Microsoft eventually released a PowerToys package that included a Compact Framework flavor of the ServiceModel Metadata Tool (netcfsvcutil.exe) that could inspect a remote WCF service and generate client proxy classes for consuming it from a CF .NET application.

I work on a CF .NET application that makes heavy use of WCF services to support workflows that take place within the confines of a warehouse-type building by various workers with rugged handheld devices. The devices need to “phone home” to a server throughout the day to both provide and receive updates on the work that is currently in progress and remains to be done. As I mentioned in an earlier post I recently went through a round of memory profiling of this CF .NET application to find and eliminate memory leaks.  One of the more surprising sources of leaks that was discovered during this exercise stemmed from the code generated by the netcfsvcutil.exe metadata tool.

When used to generate code, the netcfsvcutil.exe tool creates two files. The first file contains classes representing the service and data contracts that map to the running service endpoints or metadata that were passed in to the tool. The file contains the classes that your application will use to communicate with the remote services and will differ depending on what services are provided. These class names are suffixed with the word ‘Client’ so I’ll refer to them as the client classes for the rest of this post. The second file that gets generated contains a base class (CFClientBase<TChannel>) that the client classes from the first class inherit from. This base class handles the actual communication with the remote service endpoint including opening/closing of connections and serialization/deserialization of the data going back and forth across the wire. The code in this base class is common to all services and is always generated the same regardless of the services that the netcfsvcutil.exe tool is pointed at.

During the course of memory profiling the CF .NET application I noticed that object reference counts seemed to be growing pretty steadily with each new call to a particular remote WCF service. After some digging around in the code for a bit a couple of things became clear:

  1. The object references being “leaked” did not seem to be any that were part of our application code. That is to say that the leak appeared to be originating from the code generated by the netcfsvcutil.exe metadata tool.
  2. The symptoms of the memory leak only occurred when talking to a remote service for which we instantiated a single instance  of the generated proxy client class and re-used that instance for the life of the application. Some of the remote services that app used were invoked infrequently and in those cases we would instantiate and dispose a client class instance as-needed. The service that was causing issues was used very frequently and we decided it was better to instantiate a single instance of the client once and make it available as a singleton for the life of the application.

Some research on the topic of memory leaks in netcfsvcutil.exe generated classes lead me to a post on the MSDN Social site titled, “Memory leak in Windows Mobile WCF proxy client?” in which user Kendall J Bennett described a memory issue similar to the one I had observed. User Kimberly Z posted the answer which I’ll summarize here.

The CFClientBase<TChannel> class contains a protected struct called CFContractSerializerInfo. The purpose of this struct is to keep track of information about the various XmlObjectSerializer instances that the client classes need to use when serializing and deserializing data that is sent and received over the wire. The CFClientBase class attempts to cache the XmlObjectSerializer instances that it creates in a generic Dictionary. The dictionary is keyed off of the CFContractSerializerInfo struct. The declaration looks like this:

private System.Collections.Generic.Dictionary<CFContractSerializerInfo, System.Runtime.Serialization.XmlObjectSerializer> serializers = 
   new System.Collections.Generic.Dictionary<CFContractSerializerInfo, System.Runtime.Serialization.XmlObjectSerializer>(2);

When the client class needs to obtain a serializer for a request or response it calls a protected method called GetContractSerializer that attempts to lazily initialize and cache the serializers in the generic Dictionary:

if (serializers.ContainsKey(info))
{
    serializer = this.serializers[info];
}
else
{
    serializer = new CFContractSerializer(info);
    serializers[info] = serializer;
}

At a quick glance this code might look OK. If the serializers dictionary already contains this a serializer matching the serializer info that we’re looking for, then just just use that one. If not, create a new CFContractSerializer (which inherits the abstract XmlObjectSerializer) and assign it to a dictionary entry that will be keyed off of the serializer info that was requested so that it can be used by the next request. The idea here is that we’ll only ever need to create one XmlObjectSerializer for each type of request and response that the client class needs to deal with. Unfortunately this code doesn’t work the way it should. The ‘serializers.ContainsKey(info)’ call always returns false because the CFContractSerializerInfo struct that is being used as the key type for the dictionary does not implement the ‘Equals’ and ‘GetHashCode’ methods necessary to do the equality comparisons that the dictionary relies on. Every time a request is sent or a response is received a new instance of an XmlObjectSerializer is being created and placed into the ‘serializers’ collection in the client class that is being used to talk to the remote service. If a single instance of a client class remains in scope and active for long enough the memory used by the application will continue to grow unbounded.

At a high level there are probably at least 3 ways to fix this issue:

  1. Don’t let a single instance of a client class remain in-scope for too long. If you create a new instance of the client class each time you need to invoke a new service, the old client class instances and XmlObjectSerializers that they contain will eventually get garbage collected like any other object that is no longer needed.
  2. Change the CFClientBase<TChannel> to key the ‘serializers’ dictionary off of something other than the CFContractSerializerInfo struct. Since the struct is being used to describe serializers of a certain type of message, you could key the the dictionary off of the type itself or maybe even a string representing the fully qualified name of the type.
  3. Update the CFContractSerializerInfo struct to implement the Equals and GetHashCode methods.

Depending on the situation and specific needs of an application I think any of these three options could be viable. In my case I opted to go with option 3. The biggest drawback to this approach is that it requires modifying the generated CFClientBase code file, but since that file doesn’t change depending on the services being invoked this change is pretty safe to make. I ended up leaving the generated code file mostly intact and instead just modified the struct definition to be partial and then added another code file that defines the Equals and GetHashCode methods. For my purposes simply using the MessageContractType property of the struct was sufficient for use in the Equals and GetHashCode methods since we need to use one XmlObjectSerializer per type that needs to be serialized. I’ve put the code here in a Gist: CFClientBase.CFContractSerializerInfo.cs.