8

This is a really crazy bug. The following is throwing an OutOfMemoryException, for XML snippits that are very short and simple (e.g., <ABC def='123'/>):

public static T DeserializeXmlNode<T>(XmlNode node)
{
    try
    {
        return (T)new XmlSerializer(typeof(T))
            .Deserialize(new XmlNodeReader(node));
    }
    catch (Exception ex)
    {
        throw; // just for catching a breakpoint.
    }
}

I read in this MSDN article that if I were using XmlSerializer with additional parameters in the constructor, I'd end up generating un-cached serializer assemblies every time it got called, causing an Assembly Leak. But I'm not using additional parameters in the constructor. It also happens on the first time it is called in a freshly started AppDomain, so that doesn't make sense either.

What gives?

Mike Atlas
  • 8,193
  • 4
  • 46
  • 62
  • 2
    The most likely candidate would seem to be the type you're deserializing - can you post the code of the type that breaks it? – Dan Puzey May 12 '10 at 22:45
  • I generated the class using xsd.exe from an XSD schema document. – Mike Atlas May 12 '10 at 23:03
  • 2
    There is probably something wrong with the XML. Maybe some kind of recursion going on? What is specific about the type that is generating the exception? My experience is out-of-memory on deserialization is related to memory fragmentation. Have you profiled? Does a simple test project generate the same exception? – AMissico May 12 '10 at 23:44
  • 2
    Does your class have a property calling itself? This would cause recursion and I have had this problem before. (But, not with the out-of-memory exception.) – AMissico May 12 '10 at 23:46
  • @Mike Atlas look for anything weird in both the XSD and the XML. I don't think we can do more without more info. Also if possible post the stack. – eglasius May 26 '10 at 18:54
  • Hi Freddy - yes, I'm still trying to figure this out. The problem seems isolated only to a class of machines, actually, and has other odd issues in other parts of the code too. I have to leave this question open though as I haven't solved it yet myself either :( – Mike Atlas May 27 '10 at 12:33

4 Answers4

4

Well, the final answer to my question isn't going to help everyone that encounters this, but some of my coworkers also encountered this months later on a different system with a different product. The laughed when they found my post here on SO months later and wondered if I actually had solved it or not, since no solution was accepted here.

The final solution has nothing to do with problems Deserializing. Instead, it involved completely uninstalling and installing a brand new copy of Oracle ODP.NET database client, the provider many if not all of our applications use.

Based on anecdotal evidence, it seems this problem arises on improperly patched versions of ODP.NET assemblies, of which subsequently got propagated to other systems via virtual machine clones.

When ODP.NET was completely removed, and a new compatible version was retrieved from the Oracle website and installed, the problem disappeared completely.

The hypothesis is that a usable (but corrupt) ODP.NET driver has unsafe code and was repeatably overwriting the .NET protected memory area near the Deserialize method after first use. If Deserialize was called before any ODP.NET invocations, it would work just fine. However, all subsequent calls to Deserialize after using any ODP.NET calls would fail miserably.

The final solution to this that has now been resolved twice in two distinct products is to get a good/fresh/clean/new copy of ODP.NET installed.

Not pretty... but that's what solved it.

Mike Atlas
  • 8,193
  • 4
  • 46
  • 62
2

EDIT: This was not the solution, unfortunately, but it may help others track down a very similar problem. This answer here is the actual solution.

I've believe I found the solution to this problem. It is a bug in .NET 3.5 SP1.

Serialization hangs or throws an OutOfMemoryException with static delegate and ISerializable on 3.5 SP1 (ID: 361615):

When a generic class implements ISerializable and has a static delegate member that makes use of the generic type arguments, binary deserialization hangs (on a 32-bit system with Windows Server 2003) or throws an OutOfMemoryException (on a 64-bit system with Windows Server 2008).

This error occurs with .NET 3.5 SP1 and did not occur with .NET 3.5 without SP1.

The solution is to install KB957543 hot fix.

Community
  • 1
  • 1
Mike Atlas
  • 8,193
  • 4
  • 46
  • 62
  • 2
    Did the installation of the fix solve your problem? Because binary serialization is unrelated to XML Serialization, so I wouldn't think this KB would help. – John Saunders May 17 '10 at 19:56
  • Actually in my excitement I just rolled back the test machines to .NET 3.5 flat and the OOM went away as I hoped. I guess I need to test the hotfix itself now, then... – Mike Atlas May 17 '10 at 20:22
  • 1
    Yep, this wasn't the answer either, actually. I'll leave it here for posterity for a little while. – Mike Atlas May 18 '10 at 02:27
1

You haven't provided enough details to recreate your problem. But, the reader implements IDisposable and should be disposed of properly. Preferably by wrapping it in a using block. Most developers never run into a problem when they forget to dispose of something because the garbage collector will eventually clean up the mess. However, it isn't hard to code something that causes problems before the GC gets around to cleanup, or even prevents cleanup entirely.

public static T DeserializeXmlNode<T>(XmlNode node)
{
    XmlSerializer xs = new XmlSerializer(typeof(T));
    using(XmlNodeReader xr = new XmlNodeReader(node))
        return (T)xs.Deserialize(xr);
}
chilltemp
  • 8,854
  • 8
  • 41
  • 46
  • Definitely an oversight on my part there, but would that really blow through the available memory on the first, single call to it in a freshly loaded AppDomain? – Mike Atlas May 12 '10 at 23:16
  • 2
    No it would not. I'm leaning toward it being a problem with your target class, as suggested in others comments. Can you post the offending class? – chilltemp May 13 '10 at 16:13
1

Based on the documentation of the XmlSerializer class you should cache the XmlSerializers otherwise you can cause poor performance or a memory leak.

Hashtable serializers = new Hashtable();

// Use the constructor that takes a type and XmlRootAttribute.
XmlSerializer s = new XmlSerializer(typeof(MyClass), myRoot);

// Implement a method named GenerateKey that creates unique keys 
// for each instance of the XmlSerializer. The code should take 
// into account all parameters passed to the XmlSerializer 
// constructor.
object key = GenerateKey(typeof(MyClass), myRoot);

// Check the local cache for a matching serializer.
XmlSerializer ser = (XmlSerializer)serializers[key];
if (ser == null) 
{
    ser = new XmlSerializer(typeof(MyClass), myRoot);
    // Cache the serializer.
    serializers[key] = ser;
}
else
{
    // Use the serializer to serialize, or deserialize.
}
Wallace Breza
  • 4,898
  • 1
  • 28
  • 33
  • The MSDN link in my OP says that using the base constructor like I am using has built-in caching already. Caching it a second time on my own doesn't fix the problem I am seeing, anyways, as it blows the stack immediately on the first usage - no other serializers have been made at that point yet that would be filling up memory. – Mike Atlas May 13 '10 at 15:01