Writing Tests to Catch Memory Leaks in .NET

kick it on DotNetKicks.com

Although Microsoft will claim that it is "not possible to have a memory leak in managed code", most seasoned .NET developers will laugh at that statement.  It turns out that it is very easy to leak memory — just keep a referencing object around longer than the referenced object, and you can leak.  There at least two tools on the market that are designed specifically to seek out memory leaks of this kind (Scitech and ANTS).

The most common case of this happens with events in C#.  Take the following example:

   1: public class Observable
   2: {
   3:     public delegate void SomethingHappenedDelegate();
   4:     public event SomethingHappenedDelegate SomethingHappened;
   5:  
   6:     // Rest of the class
   7: }

   1: public class Observer 
   2: {
   3:     private readonly Observable _observable;
   4:  
   5:     public Observer(Observable observable)
   6:     {
   7:         _observable = observable;
   8:         _observable.SomethingHappened += UhOh_SomethingHappened;
   9:     }
  10:  
  11:     void UhOh_SomethingHappened()
  12:     {
  13:         // Handle the event
  14:     }
  15: }

In this example, the Observer class will hook the event on the Observable class during construction.  Because of the way that events work in C#, the Observable object has a reference to the Observer.  In the following example, the Observer will be alive (at least) as long as the Observable class is alive.  For this reason, the following method will cause a memory leak:

   1: public void LeakAnObserver()
   2: {
   3:     var observer = new Observer(_observable);
   4: }

In most cases, the Observer instance would be garbage collected as it went out of scope.  Instead, since it is kept alive through the event handler of the Observable, we leak memory.

There is a pretty easy way to solve this.  Simply unhook the event in the disposal event (actual "Dispose Pattern" removed for brevity).

   1: public class Observer : IDisposable
   2: {
   3:     // Existing code
   4:  
   5:     public void Dispose()
   6:     {
   7:         _observable.SomethingHappened -= UhOh_SomethingHappened;
   8:     }
   9: }

Great!  Now, as long as we dispose the Observer, all references will be removed and the object will get garbage collected.  Unfortunately, it is VERY easy to forget to call the Dispose method.  I want to write some tests to make sure that these objects are garbage collected.

This is a tall order to fill.  Having a reference to the object will cause it to stay alive.  How do you ask an object if it is alive without actually having a reference to the object?  This is where the WeakReference class comes in.  It is a magical class that keeps a reference to an object without the garbage collector knowing about the reference.  I wrote the following class to help me monitor and test if it still alive:

   1: public class LeakMonitor<T>
   2: {
   3:     private readonly WeakReference _reference;
   4:  
   5:     public LeakMonitor(T itemToWatch)
   6:     {
   7:         _reference = new WeakReference(itemToWatch);
   8:     }
   9:  
  10:     public bool ItemIsAlive()
  11:     {
  12:         GC.Collect();
  13:         GC.WaitForPendingFinalizers();
  14:  
  15:         return _reference.IsAlive;
  16:     }
  17:  
  18:     public T Item
  19:     {
  20:         get
  21:         {
  22:             return (T)_reference.Target;
  23:         }
  24:     }
  25: }

Here are two examples of tests that illustrate the use of LeakMonitor.  These are over-simplified unit test examples for this blog post, but you can see how this can be extended to integration and functional tests to verify that inner objects are not leaked.  Be creative!

   1: [TestFixture]
   2: public class MemTests
   3: {
   4:     private Observable _observable;
   5:  
   6:     [SetUp]
   7:     public virtual void SetUp()
   8:     {
   9:         _observable = new Observable();
  10:     }
  11:  
  12:     [Test]
  13:     public void Test_That_Observer_Leaks()
  14:     {
  15:         var monitor = new LeakMonitor<Observer>(LeakMemory());
  16:  
  17:         Assert.That(monitor.ItemIsAlive(), Is.True);
  18:     }
  19:  
  20:     [Test]
  21:     public void Test_That_Disposing_Observer_Does_Not_Leak()
  22:     {
  23:         var monitor = new LeakMonitor<Observer>(LeakMemory());
  24:         monitor.Item.Dispose();
  25:  
  26:         Assert.That(monitor.ItemIsAlive(), Is.False);
  27:     }
  28:  
  29:     private Observer LeakMemory()
  30:     {
  31:         return new Observer(_observable);
  32:     }
  33: }

Leave a Reply