.NET Finalize() and Constructor Exceptions

The Finalize() method in .NET is a special method which allows performing cleanup operations when a managed object is being garbage collected. Overriding this method is usually done in order to release unmanaged resources owned by managed object, resources which the .NET garbage collector is unaware of.

The Finalize() Riddle

As most .NET developers are aware of, when a managed object which overrides the Finalize() method is marked for garbage collection, it is moved to the finalization queue where a dedicated thread executes the cleanup code and then the object can be released.

However, consider the following code sample which illustrates a managed object wrapping an unmanaged resource (in this case an unmanaged memory buffer):

class UnmanagedBuffer
{
    private readonly IntPtr _heapHandle;
    private readonly IntPtr _memoryHandle;

    public UnmanagedBuffer(int bufferSize)
    {
        if (bufferSize <= 0) throw new ArgumentOutOfRangeException("bufferSize");

        _heapHandle = GetProcessHeap();
        _memoryHandle = HeapAlloc(_heapHandle, HEAP_GENERATE_EXCEPTIONS,
                                  (UIntPtr) bufferSize);
    }

    ~UnmanagedBuffer()
    {
        Console.WriteLine("******************");
        Console.WriteLine("In the destructor!");
        Console.WriteLine("******************");

        HeapFree(_heapHandle, 0, _memoryHandle);
    }

    // ...
    // Methods removed for brevity
    // ...

    #region Interop

    private const uint HEAP_GENERATE_EXCEPTIONS = 0x00000004;

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetProcessHeap();

    [DllImport("kernel32.dll", SetLastError = false)]
    private static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags,
                                           UIntPtr dwBytes);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);

    #endregion
}

Is there anything wrong with this code? Do you think this code is safe to execute? Think about it for a minute or so before continuing on.

The answer – this code is not safe and does not handle its end-cases well.

Finalize() Will Get Called Even If The Constructor Throws an Exception

The above code hides a small but significant assumption – it assumes that the Finalize() method won’t get called if the constructor does not complete its operation. At first glance, this makes sense – the object was not constructed so there is nothing to finalize… Release this thought from your head! It is simply incorrect. Consider the following usage scenario:

static void Main()
{
    try
    {
        // The following call throws an exception
        var unmanagedResource = new UnmanagedBuffer(0);
    }
    catch (Exception ex)
    {
        Console.WriteLine("An exception was thrown!");
        Console.WriteLine(ex);
    }
    finally
    {
        // Force Garbage Collection
        GC.Collect();
        // Will trigger our Finalize() to execute
        GC.WaitForPendingFinalizers();               
        GC.Collect();
    }
}

While executing the above code the constructor will throw an ArgumentOutOfRangeException, however the Finalize() method would still get called! In our sample, this would result in an attempt to release an invalid memory handle which doesn’t cause too much harm but in other cases this could result in a corrupted state or an exception thrown on the Finalize thread. This, in turn, will cause your entire application to crash which is not so good…

Why is the Finalize() method being called?

The managed object was allocated when the new operator completed, and as of that moment the object exists! From the CLR point-of-view calling the constructor is just another method call to complete before providing the allocated object reference to your code. Now, if the constructor throws an exception then your code will not get its reference back but the allocation already occurred. As a result, the Finalize() method is scheduled to execute upon garbage collection and is eventually called.

Implementing Finalize() Correctly

We have two ways of fixing this issue:

  1. Unless you really have to, never directly reference a handle to an unmanaged resource. Instead, consider using a SafeHandle derived object (there are prewritten SafeHandle derived objects in the Microsoft.Win32.SafeHandles namespace). This would make your code clearer and more robust, saving you from ever needing to override Finalize() yourself. This is the preferred solution.
  2. If you do override Finalize(), consider end-cases in which not all fields were initialized correctly. In our case, this would result in the following code change:
~UnmanagedBuffer()
{
    Console.WriteLine("******************");
    Console.WriteLine("In the destructor!");
    Console.WriteLine("******************");

    // Check if the handle was initialized
    if (_heapHandle != IntPtr.Zero)
        HeapFree(_heapHandle, 0, _memoryHandle);
}

In this version, the Finalize() implementation first checks that the memory handle has indeed been allocated and only then releases it if required.

Have you implemented your Finalize() correctly?

Similar Posts

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *