A cautionary tale on using C# destructors

It is well documented in the .NET community, that destructors, although available as a language construct, should be used with caution especially if its usage is to cleanup unmanaged resources.  The CLR’s garbage collector is responsible for releasing memory used by managed objects only.  It is, therefore, not aware of any native resources that may have been loaded by the application process, hence the need to explicitly dispose of these resources.

The object class, which is base or parent class of every object in .NET has a protected and virtual method named Finalize.   When a C# destructor is implemented in a class, this code is converted by the C# compiler to an overriding Finalize implementation.  During garbage collection, every instance of such an object which as an overridden Finalize implementation is added to an internal finalization queue. At some nondeterministic point, the garbage collector invokes the Finalize method of each object in the finalization queue giving it a chance to release its unmanaged resources.

For example, suppose you created a class called DirectoryReplicationService with the following constructs:


public class DirectoryReplicationService : IDirectoryReplicationService, IDisposable
{
[DllImport("DRSAPIHelper.dll", CallingConvention = CallingConvention.Winapi,
CharSet = CharSet.Unicode, EntryPoint = "DRSAPI_Bind", PreserveSig = true)]
public static extern Int32 DRSAPI_Bind(
string param1,
string param2,
out IntPtr phHelper);

[DllImport("DRSAPIHelper.dll", CallingConvention = CallingConvention.Winapi,
CharSet = CharSet.Unicode, EntryPoint = "DRSAPI_Unbind", PreserveSig = true)]
public static extern Int32 DRSAPI_Unbind(
IntPtr phHelper);

private readonly IntPtr _hHelper = IntPtr.Zero;

public DirectoryReplicationService(
String param1,
String param2,
)
{
int hRes = DRSAPI_Bind(param1, param2, out _hHelper);
if (!hRes.Equals(0))
{
Logger.Error("Failed to bind. " + hRes.ToString("X8"), this);
throw (new Exception("Failed to bind"));
}
}

~DirectoryReplicationService()
{
int hRes = DRSAPI_Unbind(_hHelper);

if (!hRes.Equals(0))
{
logger.Debug("Failed to unbind. " + hRes.ToString("X8"));
throw (new Exception("Failed to unbind"));
}

The line DRSAPI_Unbind in the destructor invokes the native resource _hHelper, which is responsible for releasing its own resources.  When compiled, the destuctor code is replaced by


protected override Finalize()
{
int hRes = DRSAPI_Unbind(_hHelper);

if (!hRes.Equals(0))
{
logger.Debug("Failed to unbind. " + hRes.ToString("X8"));
throw (new Exception("Failed to unbind"));
}

There are fundamentally two problems here, which will be manifested as a random crash in the application:

  1. Relying on the Finalize method to release native resources, which is called in a non-deterministic fashion, could cause a memory violation, especially if there are background threads in the application that could potentially create additional instances of the DirectoryReplicationService.
  2. Throwing an exception in a destructor is not recommended as this could effectively cause an application crash.  MSDN states:

If Finalize or an override of Finalize throws an exception, and the runtime is not hosted by an application that overrides the default policy, the runtime terminates the process and no activetry/finally blocks or finalizers are executed. This behavior ensures process integrity if the finalizer cannot free or destroy resources.

So, what do we do?

As recommended, the best way to rid of native resources in a managed application is with proper implementation of the Dispose pattern.  Replacing the desctuctor with the following code:


public class DirectoryReplicationService

{

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

private void Dispose(bool disposing)
{
if (disposing)
{
int hRes = DRSAPI_Unbind(_hHelper);

if (!hRes.Equals(0))
{
Logger.Error("Failed to unbind. " + hRes.ToString("X8"), this);
}
}
}

}

ensures that any native resources are properly released in a deterministic fashion, thereby preventing opportunities for memory leaks and random crashes due to memory access violations.

This is what we observed in one of our production ready applications.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s