releaseHandleFailed MDA
Note
This article is specific to .NET Framework. It doesn't apply to newer implementations of .NET, including .NET 6 and later versions.
The releaseHandleFailed
managed debugging assistant (MDA) is activated is to notify developers when the ReleaseHandle method of a class derived from SafeHandle or CriticalHandle returns false
.
Symptoms
Resource or memory leaks. If the ReleaseHandle method of the class deriving from SafeHandle or CriticalHandle fails, then the resource encapsulated by the class might not have been released or cleaned up.
Cause
Users must provide the implementation of the ReleaseHandle method if they create classes that derive from SafeHandle or CriticalHandle; thus, the circumstances are specific to the individual resource. However, the requirements are as follows:
SafeHandle and CriticalHandle types represent wrappers around vital process resources. A memory leak would make the process unusable over time.
The ReleaseHandle method must not fail to perform its function. Once the process acquires such a resource, ReleaseHandle is the only way to release it. Therefore, failure implies resource leaks.
Any failure that does occur during the execution of ReleaseHandle, impeding the release of the resource, is a bug in the implementation of the ReleaseHandle method itself. It is the responsibility of the programmer to ensure that the contract is fulfilled, even if that code calls code authored by someone else to perform its function.
Resolution
The code that uses the specific SafeHandle (or CriticalHandle) type that raised the MDA notification should be reviewed, looking for places where the raw handle value is extracted from the SafeHandle and copied elsewhere. This is the usual cause of failures within SafeHandle or CriticalHandle implementations, because the usage of the raw handle value is then no longer tracked by the runtime. If the raw handle copy is subsequently closed, it can cause a later ReleaseHandle call to fail because the close is attempted on the same handle, which is now invalid.
There are a number of ways in which incorrect handle duplication can occur:
Look for calls to the DangerousGetHandle method. Calls to this method should be exceedingly rare, and any that you find should be surrounded by calls to the DangerousAddRef and DangerousRelease methods. These latter methods specify the region of code in which the raw handle value may be safely used. Outside this region, or if the reference count is never incremented in the first place, the handle value can be invalidated at any time by a call to Dispose or Close on another thread. Once all uses of DangerousGetHandle have been tracked down, you should follow the path the raw handle takes to ensure it is not handed off to some component that will eventually call
CloseHandle
or another low-level native method that will release the handle.Ensure that the code that is used to initialize the SafeHandle with a valid raw handle value owns the handle. If you form a SafeHandle around a handle your code does not own without setting the
ownsHandle
parameter tofalse
in the base constructor, then both the SafeHandle and the real handle owner can try to close the handle, leading to an error in ReleaseHandle if the SafeHandle loses the race.When a SafeHandle is marshalled between application domains, confirm the SafeHandle derivation being used has been marked as serializable. In the rare cases where a class derived from SafeHandle has been made serializable, it should implement the ISerializable interface or use one of the other techniques for controlling the serialization and deserialization process manually. This is required because the default serialization action is to create a bitwise clone of the enclosed raw handle value, resulting in two SafeHandle instances thinking they own the same handle. Both will try to call ReleaseHandle on the same handle at some point. The second SafeHandle to do this will fail. The correct course of action when serializing a SafeHandle is to call the
DuplicateHandle
function or a similar function for your native handle type to make a distinct legal handle copy. If your handle type does not support this then the SafeHandle type wrapping it cannot be made serializable.It may be possible to track where a handle is being closed early, leading to a failure when the ReleaseHandle method is finally called, by placing a debugger breakpoint on the native routine used to release the handle, for example the
CloseHandle
function. This may not be possible for stress scenarios or even medium-sized functional tests due to the heavy traffic such routines often deal with. It may help to instrument the code that calls the native release method, in order to capture the identity of the caller, or possibly a full stack trace, and the value of the handle being released. The handle value can be compared with the value reported by this MDA.Note that some native handle types, such as all the Win32 handles that can be released via the
CloseHandle
function, share the same handle namespace. An erroneous release of one handle type can cause problems with another. For instance, accidentally closing a Win32 event handle twice might lead to an apparently unrelated file handle being closed prematurely. This happens when the handle is released and the handle value becomes available for use to track another resource, potentially of another type. If this happens and is followed by an erroneous second release, the handle of an unrelated thread might be invalidated.
Effect on the Runtime
This MDA has no effect on the CLR.
Output
A message indicating that a SafeHandle or a CriticalHandle failed to properly release the handle. For example:
"A SafeHandle or CriticalHandle of type 'MyBrokenSafeHandle'
failed to properly release the handle with value 0x0000BEEF. This
usually indicates that the handle was released incorrectly via
another means (such as extracting the handle using DangerousGetHandle
and closing it directly or building another SafeHandle around it."
Configuration
<mdaConfig>
<assistants>
<releaseHandleFailed/>
</assistants>
</mdaConfig>
Example
The following is a code example that can activate the releaseHandleFailed
MDA.
bool ReleaseHandle()
{
// Calling the Win32 CloseHandle function to release the
// native handle wrapped by this SafeHandle. This method returns
// false on failure, but should only fail if the input is invalid
// (which should not happen here). The method specifically must not
// fail simply because of lack of resources or other transient
// failures beyond the user’s control. That would make it unacceptable
// to call CloseHandle as part of the implementation of this method.
return CloseHandle(handle);
}