Some finalizer pitfalls

time to read 3 min | 451 words

The use of finalizer to ensure eventual release of resources is recommended, but one thing that is not discussed often is the implications of adding a finalizer.

No, I am not talking about the extra wait time before the class memory is released, this is fairly well known. I am talking about two major additional issues that don't get enough attention, in my opinion.

Specifically, invalid instances and threading.

An object that has a finalizer will may be executed in more than one thread. Luckily, the CLR ensure that our finalizer will not be able to run if there is any code that is using the class, so it is not as bad as it seems at first.

However, something that make absolute sense was a bit of a surprise to me, when I figured it out, finalizers will run for invalid instances as well. What do I mean by that? Invalid instance is an object whose ctor threw an exception. It is in invalid state.

But, before it threw an exception, it might have acquired resources that needs to be released, so it goes to the GC finalizer as well. In fact, since you can't call Dispose on such an object, it must go to the finalizers for cleanup.

Remember the invalid state bit part? This is important!

Your object is in invalid state, and it is asked to cleanup itself. If it can't do so even in invalid state, an exception will be thrown. This exception on the finalizer thread will kill the application.

Here is the proof of concept code:

public class BadClass : IDisposable
{
	FileStream fs;

	public BadClass(int i)
	{
		if (i%2 == 0)
			throw new InvalidDataException("evens are bad for your health");
		fs = new FileStream(Path.GetTempFileName(), FileMode.Create);
	}

	~BadClass()
	{
		fs.Dispose();
	}

	public void Dispose()
	{
		fs.Dispose();
		GC.SuppressFinalize(this);
	}
}

And the code to execute it:

for (int i = 0; i < 1000; i++)
{
	try
	{
		using (new BadClass(i))
		{

		}
	}
	catch (Exception)
	{
	}
}

Trying to figure out what is going on in this scenario is tough. Going back to the threaded aspect of it. What this means is that such an object is a time bomb, and it will blow up at some future date to kill your application, but you are not likely to know what exactly caused it to be this way.

In my situation, I figured it out by putting new StackTrace(true) in the constructor of the object, and breaking into the debugger on the finalizer.  But until it occurred to me to look for it there...