Welcome to GASP Sign in | Join | Help

Hugo Batista

<wsa:RelatesTo>
Software Engineering
</wsa:RelatesTo>

News


  • All information or content in this site is provided "AS IS" with no warranties, and confer no rights. The views expressed by me do not necessarily reflect the official policy, position, or opinions of the company where I work.





Memory Leak inside Crystal Reports XI

[UPDATE - 23-02-2006] - Some folks asked me how did I drilled down into the problem. Here are some pictures that illustrate how.

If you've read some last posts (1)(2), you may wonder what kind of library am I using. It's Crystal Reports XI, from Business Objects.

Today, I finally confirmed my suspect for a memory leak inside crystal managed runtime, in the ReportDocument class. The problem can be reproduced by the following code:

using( ReportDocument doc = new ReportDocument())
{
    doc.Load("yourreport.rpt");
}

If you run this code, you think that probably you will dispose doc instance, and after the dispose call, no reference count remains to the object. Wrong.
I said wrong, because a reference counts remains as 2. If you inspect the Heap allocation, using CLR profiler, DumpHeap or MemProfiler, you will see that two eventhandlers keep a reference to doc instance: AppDomain.CurrentDomain.DomainUnload and AppDomain.CurrentDomain.ProcessExit event.

What can cause this ? Simple: Inside the load method, Crystal reports subscribe to these two events, with a code that may be similar to this:

AppDomain.CurrentDomain.DomainUnload += new EventHandler(this.method);
AppDomain.CurrentDomain.ProcessExit += new EventHandler(this.method);

Since CurrentDomain is static, both references last forever. When I first found this, I thought I was not correctly using Crystal API, and maybe I was missing something, and maybe unsubscription was occurring inside Dispose and under some conditions. So after investigating a while, I found that ReportDocument subscribes to the event, and never unsubscribe it. Unless your AppDomain unloads, or your process ends, your doc instance will never be garbage collected.
Apparently, this is done so that Crystal can close any file handles before exiting your application. I may understand the intention, but i cannot agree with the approach.

Here's some pictures that demonstrate the leak, using MemProfiler:

After disposing ReportDocument, and EVEN forcing a garbage collection, we can check that we still keep a live instance of ReportDocument in the heap. Why is that ?

Drilling down into the instance, and checking how many references count are kept to this instance, we can see that this instance is being referenced by two other object instances.

And drilling down to who are them, we found both event handlers. Damn!

For those unknowing all facts, Crystal Reports managed runtime is just a wrapper to the unmanaged crystal engine. This increases the problem a little, since you may also keep some references to unmanaged code.

How can we solve the problem ?

Easy.
Fortunately, ReportDocument class is not sealed, so you may derive it. To clean all references, we simply have to unsubscribe both events, inside our class destructor and dispose method. We can just implement Dispose Pattern, and clean resources.

This was supposed to be easy, if you had access to the method which was passed to both events, and we could do something like:

AppDomain.CurrentDomain.DomainUnload -= new EventHandler(this.method);
AppDomain.CurrentDomain.ProcessExit -= new EventHandler(this.method);

 The problem arises when we realize that this method is private. If this.method is private, you can't access it. So to clean our events, we'll have to iterate the event delegate invocation list, and then check for a target like our object.
Another problem, is the fact of not being able to call GetInvocationList for these events (I couldn't find why ...). we'll have to do some tricks, and get the invocation list by reflection. Code will look like this:

Delegate domainUnloadDelegate = (Delegate) typeof(AppDomain).GetField("DomainUnload", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(AppDomain.CurrentDomain);

Delegate[] invocationList = domainUnloadDelegate.GetInvocationList();
for(short i=0;i{
 if (invocationList[i].Target!=null && invocationList[i].Target.Equals(this))
    AppDomain.CurrentDomain.DomainUnload -= (EventHandler) invocationList[i];
}

Delegate processExitDelegate = (Delegate) typeof(AppDomain).GetField("ProcessExit",BindingFlags.Instance | BindingFlags.NonPublic).GetValue(AppDomain.CurrentDomain);

invocationList = processExitDelegate.GetInvocationList();
for(short i=0;i if (invocationList[i].Target!=null && invocationList[i].Target.Equals(this))
    AppDomain.CurrentDomain.ProcessExit -= (EventHandler) invocationList[i]; 
}

The thing here, is to try to find any reference to our object being disposed, and if found, unsubscribe for the event. This is done by firstly obtaining the delegate of both events, by reflecting in the type AppDomain. Then, we'll get the delegate value, and we'll call GetInvocationList, to obtain an array of delegates that would be call if our event fires. Now, we'll just iterate the list, and check if the event target is our object instance. If it is, we'll unsubscribe the event.

If we follow this code, and after rechecking heap allocation, we can see that our object counts no references to it, and that way, it will be garbage collected.

As I said in my previous post, the moral is: keep current that CLR is not the cure for bad programming habits, and when some documents may refer that CLR avoids memory leaks, it should meant that it HELPS to avoid memory leaks, but it can't help one to correctly use it.

BTW, I will personally do this recommendation to Business Objects ;)

Final derived class code, can be seen here

Posted: Wednesday, February 22, 2006 12:24 AM by Hugo Pais Batista

Comments

fabiodrs said:

Hi Hugo, "I NEED YOUR HELP"! lol
I'm with the same problem with Crystal.
Can u tell me how can I get this "fix" that you received from IBM?

Thanks in advance.

Fábio Demarchi
fabiodemarchi@gmail.com (it's msn too)
# May 22, 2006 9:18 PM

fabiodrs said:

Hi Hugo, "I NEED YOUR HELP"! lol
I'm with the same problem with Crystal.
Can u tell me how can I get this "fix" that you received from IBM?

Thanks in advance.

Fábio Demarchi
fabiodemarchi@gmail.com (it's msn too)
# May 22, 2006 9:19 PM

Hugo Batista said:

I've received a few mails about&amp;nbsp;my post about crystal memory leaks&amp;nbsp;(original version with pictures...
# May 23, 2006 10:56 PM

fullerd said:

This fix didn't work for me, at least not at first.  I put the code in place, and used mem profiler to check it out and the new CrystalReportDocument class that I created wasn't disposing.

This wasn't an issue before I installed Service Pack 2 for Crysal 11.  So I uninstalled Crystal and reinstalled it, not bothering to install any service packs this time.  Ran again using MemProfiler and what do you know, the objects are being disposed of properly now!  I've given it out to the client who was having troubles, and they're humming along nicely now.

I was still running the fix mentioned in this post after reinstalling Crystal, so I don't know whether you simply need to have Crystal installed without SP2, or if you need to have the fix in place as well.

Hopefully this is useful for anyone who isn't having any luck getting rid of the memory leaks.

Regards,
Dan
# July 10, 2006 4:41 AM

Hugo Batista said:

I've received a few mails about my post on crystal memory leaks ( original version with pictures available

# March 22, 2007 12:42 AM
Anonymous comments are disabled