releaseHandleFailed MDA

当从 SafeHandleCriticalHandle 派生的类的 ReleaseHandle 方法返回 false 时,将激活 releaseHandleFailed 托管调试助手 (MDA) 以通知开发人员。

症状

资源或内存泄露。 如果从 SafeHandleCriticalHandle 派生的类的 ReleaseHandle 方法失败,则该类封装的资源可能未被释放或清理。

原因

如果用户创建从 SafeHandleCriticalHandle 派生的类,则他们必须提供 ReleaseHandle 方法的实现;因此,这些情况特定于单独的资源。 不过,有如下要求:

  • SafeHandleCriticalHandle 类型表示重要的进程资源的包装。 内存泄露将使进程随时间推移而不可用。

  • ReleaseHandle 方法在执行其功能时一定不能失败。 一旦进程获取这样的资源,ReleaseHandle 就是释放该资源的唯一途径。 因此,失败就意味着资源泄露。

  • ReleaseHandle 的执行期间发生的任何失败(从而阻止资源的释放)都是 ReleaseHandle 方法自身的实现中的 bug。 确保该协定得到满足是程序员的责任 -- 即使该代码调用其他人创作的代码执行其功能也如此。

解决方法

应该检查使用特定 SafeHandle(或 CriticalHandle)类型(该类型引发 MDA 通知)的代码,查找从 SafeHandle 提取出原始句柄值并复制到其他位置的地方。 这通常就是 SafeHandleCriticalHandle 实现中失败的原因,因为运行时随后不再跟踪原始句柄值的使用。 如果随后关闭原始句柄副本,它可能导致后来的 ReleaseHandle 调用失败,因为该关闭操作尝试在相同句柄上进行,而该句柄现在已无效。

存在许多可能出现错误的句柄复制的方式:

  • 查找对 DangerousGetHandle 方法的调用。 对此方法的调用应该极其罕见,并且如果找到任何对此方法的调用,它的前后应该是对 DangerousAddRefDangerousRelease 方法的调用。 这后两个方法指定能够安全使用原始句柄值的代码区域。 在此区域之外,或者如果引用计数从起初起就从未增加,可在任何时候在另一个线程上调用 DisposeClose 方法使该句柄值失效。 一旦 DangerousGetHandle 的所有使用都得到跟踪,就应该遵循原始句柄所经历的路径以确保未将它移交给某个组件(该组件最终将调用 CloseHandle 或另一个将释放该句柄的底层本机方法)。

  • 确保用于使用有效的原始句柄值初始化 SafeHandle 的代码拥有该句柄。 如果围绕您的代码未拥有的句柄组成 SafeHandle 而不在基构造函数中将 ownsHandle 参数设置为 false,则 SafeHandle 和实际句柄拥有者都可以尝试关闭该句柄,从而在 SafeHandle 争用失败时导致在 ReleaseHandle 中发生错误。

  • 当在应用程序域之间对 SafeHandle 进行封送处理时,请确认正在使用的 SafeHandle 派生已被标记为可序列化的。 在极少数情况下,从 SafeHandle 派生的类已设置为可序列化的,此时应该实现 ISerializable 接口,或使用其他某种技术手动控制序列化和反序列化过程。 这是必需的,因为默认的序列化操作是创建所包含的原始句柄值的按位复本,从而导致两个 SafeHandle 实例认为它们拥有相同句柄。 两者都将在某个时候尝试在相同句柄上调用 ReleaseHandle。 发出此调用的第二个 SafeHandle 将失败。 在序列化 SafeHandle 时的正确操作过程是调用 DuplicateHandle 函数或类似函数,以便本机句柄类型创建一个不同的合法句柄副本。 如果您的句柄类型不支持此操作,则不能将包装它的 SafeHandle 类型设置为可序列化的。

  • 通过在用于释放句柄的本机例程(例如 CloseHandle 函数)上设置一个调试器断点,跟踪句柄被提前关闭(从而在最终调用 ReleaseHandle 方法时导致失败)的位置也许是可能的。 由于此类例程通常处理繁重的流量,这对压力方案甚或中等规模功能测试可能不可行。 它可能有助于检测调用本机释放方法的代码,以便捕获调用方的标识,或者可能捕获完整的跟踪堆栈和正在被释放的句柄的值。 可以将该句柄值与此 MDA 报告的值进行比较。

  • 注意有些本机句柄类型(例如能够通过 CloseHandle 函数释放的所有 Win32 句柄)共享相同的句柄命名空间。 错误释放一个句柄类型可能导致另一个句柄类型出问题。 例如,偶然关闭一个 Win32 句柄两次可能导致过早关闭一个明显不相关的文件句柄。 这发生在句柄已释放并且该句柄值可用于跟踪另一个资源(可能为另一种类型)的时候。 如果发生这种情况,并且接着进行第二次错误释放,则某个不相关线程的句柄可能失效。

对运行时的影响

此 MDA 对 CLR 无任何影响。

Output

指示 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>

示例

下面是可激活 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);
}

请参见

参考

MarshalAsAttribute

概念

使用托管调试助手诊断错误

互操作封送处理

其他资源

互操作性