15

GC.Collect appears to start the garbage collection in a background thread, and then return immediately. How can I run GC.Collect synchronously -- i.e., wait for the garbage collection to complete?

This is in the context of NUnit tests. I tried adding the gcConcurrent setting to my test assembly's app.config file, and I tried the same with nunit.exe.config. Neither had any effect -- when I debug, I can still see the finalizer being run on the "GC Finalizer Thread", rather than the thread that called GC.Collect (NUnit's "TestRunnerThread"), and both threads are running concurrently.

Background: I want my tests to fail if they leak (don't call Dispose on) a particular class. So I've added a finalizer to that class that sets a static wasLeaked flag; then my test TearDown calls GC.Collect() and then throws if wasLeaked is true. But it's not failing deterministically, because when it reads wasLeaked, the finalizer usually hasn't even been called yet. (It fails some later test instead, after the garbage collection finally finishes.)

Joe White
  • 94,807
  • 60
  • 220
  • 330

4 Answers4

15

Finalizers are run on a dedicated, high-priority background thread. From the background in your post, I gather that you can simply do

GC.Collect();
GC.WaitForPendingFinalizers();

The Collect() will schedule any non-rooted instances for finalization and then the thread will wait for the finalizer thread to complete.

Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
5

You can use GC.RegisterForFullGCNotification, trigger a full collection with GC.Collect(GC.MaxGeneration) and then the GC.WaitForFullGCComplete and GC.WaitForPendingFinalizers methods, but make sure to use this in your tests only, they should not be used for production code.

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • The docs say that WaitForFullGCApproach and WaitForFullGCComplete should always be used together. How would I wait for a GC approach when I'm explicitly firing the GC? Do you have a code sample that does this? – Joe White Apr 14 '09 at 19:06
  • Sorry for the late answer. There is a good explanation and sample here which should more or less apply to your code: http://msdn.microsoft.com/en-us/library/cc713687.aspx You may want to choose your approach notification limit so that you basically get notified right away. – Lucero Apr 14 '09 at 22:22
2

A easier/better way of doing this may be to use mocking and check an expectation that Dispose was called explicitly.

Example using RhinoMocks

public void SomeMethodTest()
{
     var disposable = MockRepository.GenerateMock<DisposableClass>();

     disposable.Expect( d => d.Dispose() );

     // use constructor injection to pass in mock `DisposableClass` object
     var classUnderTest = new ClassUnderTest( disposable ); 

     classUnderTest.SomeMethod();

     disposable.VerifyAllExpectations();
}

If the method needs to create and then dispose of the object, then I would use and inject a factory class that is able to create the mock object. Example below uses stub on factory as it's not what we are testing for in this test.

public void SomeMethod2Test()
{
     var factory = MockRepository.Stub<DisposableFactory>();
     var disposable = MockRepository.GenerateMock<DisposableClass>();

     factory.Stub( f => f.CreateDisposable() ).Return( disposable );         
     disposable.Expect( d => d.Dispose() );

     // use constructor injection to pass in mock factory
     var classUnderTest = new ClassUnderTest( factory ); 

     classUnderTest.SomeMethod();

     disposable.VerifyAllExpectations();
}
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • This is a nice approach to test deterministic release, but doesn't test whether the finalizer code works correctly. Therefore Joe may want to use both approaches, depending on what he wants to test exactly. – Lucero Apr 14 '09 at 18:36
  • I understood that it was to make sure that all of his classes called Dispose (3 paragraph). – tvanfosson Apr 14 '09 at 18:40
  • The Dispose pattern uses a protected (virtual) Dispose(bool disposing) method and is called by IDIsposable.Dispose with disposing=true and by the finalizer (if required) with disposing=false. Therefore, I wasn't sure what code path to Dispose was meant. – Lucero Apr 14 '09 at 18:46
  • If the purpose is to test that Disposed is called, this may be a good approach. But if you want to test that objects have become eligible for garbage collection it's no good knowing simply that they've been disposed. It's perfectly possible to hang on to references to disposed objects with no immediate ill effects (if nothing uses them), but doing so means a memory leak that could be a serious problem in a server. While I suspect memory profilers are our best tool against this, I applaud any effort to find ways to make it testable. :) – The Dag Jan 23 '13 at 16:29
2

Finalizers always run on a separate thread regardless of whether you're using a concurrent GC or not. If you want to ensure that finalizers have been run, try GC.WaitForPendingFinalizers instead.

kvb
  • 54,864
  • 2
  • 91
  • 133