注释
本文特定于 .NET Framework。 它不适用于 .NET 的较新版本实现,包括 .NET 6 及更高版本。
激活releaseHandleFailed托管调试助手(MDA)是在派生SafeHandle自或CriticalHandle返回false类的方法时ReleaseHandle通知开发人员。
症状
资源或内存泄漏。 ReleaseHandle如果派生自SafeHandle或CriticalHandle失败的类的方法,则由该类封装的资源可能尚未释放或清理。
原因
如果用户创建派生自SafeHandle或CriticalHandle派生的类,则用户必须提供该方法的ReleaseHandle实现;因此,情况特定于单个资源。 但是,要求如下:
SafeHandle 和 CriticalHandle 类型表示重要进程资源的包装器。 内存泄漏会使进程在一段时间内不可用。
该方法 ReleaseHandle 不能执行其函数。 进程获取此类资源后, ReleaseHandle 是释放它的唯一方法。 因此,失败意味着资源泄漏。
执行期间 ReleaseHandle发生的任何失败(妨碍资源的释放)都是方法本身的 ReleaseHandle 实现中的 bug。 程序员有责任确保合同得到履行,即使该代码调用其他人创作的代码来执行其函数。
解决方案
应查看使用引发 MDA 通知的特定 SafeHandle 类型(或 CriticalHandle)类型的代码,查找从 SafeHandle 其他位置提取原始句柄值的位置。 这是导致内部或CriticalHandle实现失败SafeHandle的常见原因,因为运行时不再跟踪原始句柄值的使用情况。 如果原始句柄副本随后关闭,则可能会导致以后 ReleaseHandle 调用失败,因为同一句柄上尝试关闭,该句柄现在无效。
有多种方法可以发生不正确的处理重复:
查找对方法的 DangerousGetHandle 调用。 对此方法的调用应该极其罕见,并且你发现的任何调用都应被调用 DangerousAddRef 和 DangerousRelease 方法所包围。 这些后一种方法指定可以安全地使用原始句柄值的代码区域。 在此区域之外,或者如果引用计数从不在第一位递增,则随时可以通过调用 Dispose 或其他 Close 线程来使句柄值失效。 跟踪所有用途 DangerousGetHandle 后,应遵循原始句柄采用的路径,以确保不会将其移交给最终调用
CloseHandle的一些组件,或者将释放句柄的另一个低级别本机方法。确保用于使用有效原始句柄值初始化 SafeHandle 的代码拥有句柄。 如果在SafeHandle基本构造函数中将参数设置为
ownsHandlefalse基构造函数SafeHandle中没有代码拥有的句柄,则实际句柄所有者都可以尝试关闭句柄,从而导致ReleaseHandle丢失争用时SafeHandle出错。在应用程序域之间封送 a SafeHandle 时,确认正在使用的 SafeHandle 派生已标记为可序列化。 在派生自 SafeHandle 的类已进行序列化的极少数情况下,它应实现 ISerializable 接口或使用其他方法之一手动控制序列化和反序列化过程。 这是必需的,因为默认序列化操作是创建封闭的原始句柄值的按位克隆,导致两 SafeHandle 个实例认为它们拥有相同的句柄。 两者都会尝试在某些时候调用 ReleaseHandle 同一句柄。 执行此操作的第二个 SafeHandle 操作将失败。 序列化 a SafeHandle 时的正确操作过程是调用
DuplicateHandle本机句柄类型的函数或类似函数,以创建不同的法律句柄副本。 如果句柄类型不支持此类型, SafeHandle 则无法将其包装为可序列化的类型。通过将调试器断点放置在用于释放句柄的本机例程(例如
CloseHandle函数)上,可以跟踪句柄提前关闭的位置,从而在最终调用该方法时ReleaseHandle导致失败。 对于压力方案,甚至中等大小的功能测试,这种情况可能是不可能的,因为此类例程经常处理大量流量。 它可能有助于检测调用本机发布方法的代码,以便捕获调用方的身份,或者可能是完整的堆栈跟踪以及要释放的句柄的值。 句柄值可以与此 MDA 报告的值进行比较。请注意,某些本机句柄类型(例如所有可通过函数释放的
CloseHandleWin32 句柄)共享相同的句柄命名空间。 错误释放一种句柄类型可能会导致另一种处理类型出现问题。 例如,意外关闭 Win32 事件句柄两次可能会导致明显无关的文件句柄过早关闭。 当释放句柄并且句柄值可用于跟踪另一个资源(可能为另一种类型)时,将发生这种情况。 如果发生这种情况并随后出现错误的第二个版本,则不相关的线程的句柄可能会失效。
对运行时的影响
此 MDA 对 CLR 没有影响。
输出
指示无法SafeHandleCriticalHandle正确释放句柄的消息。 例如:
"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."
配置
<mdaConfig>
<assistants>
<releaseHandleFailed/>
</assistants>
</mdaConfig>
Example
下面是可以激活 MDA 的 releaseHandleFailed 代码示例。
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);
}