releaseHandleFailed MDA

更新 : 2007 年 11 月

releaseHandleFailed マネージ デバッグ アシスタント (MDA: Managed Debugging Assistant) は、SafeHandle または CriticalHandle から派生したクラスの ReleaseHandle メソッドが false を返した場合に、これを開発者に通知するためにアクティブになります。

症状

リソースのリークまたはメモリ リーク。SafeHandle または CriticalHandle から派生したクラスの ReleaseHandle メソッドが失敗した場合、そのクラスによってカプセル化されたリソースは、解放またはクリーンアップされていません。

原因

SafeHandle または CriticalHandle から派生したクラスを作成する場合、ユーザーは ReleaseHandle メソッドの実装を提供する必要があります。したがって、この状況は個々のリソースに固有のものです。ただし、次のような要件があります。

  • SafeHandle 型と CriticalHandle 型は、重要なプロセス リソースのラッパーを表します。メモリ リークが発生すると、プロセスはやがて使用できなくなります。

  • ReleaseHandle メソッドは、関数の実行に失敗しないようにする必要があります。解放またはクリーンアップされていないリソースをプロセスが取得した場合、ReleaseHandle しかそのリソースを解放することはできません。したがって、エラーはリソースのリークを意味します。

  • ReleaseHandle の実行時にエラーが発生し、リソースを解放できない場合、ReleaseHandle メソッド自体の実装にバグがあります。プログラマは、そのコードが他のプログラマによって作成されたコードを呼び出して関数を実行する場合でも、コントラクトが確実に満たされるようにする必要があります。

解決策

MDA 通知を発生させた SafeHandle (または CriticalHandle) 型を使用するコードを見直し、未処理のハンドル値が SafeHandle から抽出され、別の場所にコピーされた箇所を調べる必要があります。未処理のハンドル値の使用状況は、ランタイムによって追跡されなくなるため、これは SafeHandle または CriticalHandle の実装で発生するエラーの通常の原因です。未処理のハンドルのコピーをその後閉じた場合に、同じハンドルに対して閉じる処理が試みられますが、これは無効であるため、以降の ReleaseHandle 呼び出しが失敗する原因となる可能性があります。

不正なハンドルの複製は、次のようなさまざまな状況で発生する可能性があります。

  • DangerousGetHandle メソッドの呼び出しを探します。このメソッドの呼び出しはきわめてまれであり、見つかった場合は、DangerousAddRef メソッドと DangerousRelease メソッドの呼び出しによって囲まれています。この 2 つのメソッドは、未処理のハンドル値を安全に使用できるコードの領域を指定します。この領域外、または参照カウントが最初の場所でインクリメントされない場合は、別のスレッドで Dispose または Close を呼び出すことでハンドル値をいつでも無効にできます。DangerousGetHandle を使用している場所をすべて突き止めたら、未処理のハンドルが受け取るパスに従って、最終的に CloseHandle を呼び出すコンポーネント、またはハンドルを解放する下位の別のネイティブ メソッドに渡されないようにする必要があります。

  • 有効な未処理のハンドル値で SafeHandle を初期化するために使用するコードが、ハンドルを所有していることを確認します。基本コンストラクタで ownsHandle パラメータに false を設定せずに、コードが所有していないハンドルの SafeHandle を作成した場合、SafeHandle の所有者と実際のハンドルの所有者の両方が、ハンドルを閉じようとする可能性があります。これは、SafeHandle が競合を失った場合に ReleaseHandle のエラーの原因となります。

  • SafeHandle をアプリケーション ドメイン間でマーシャリングするときには、使用する SafeHandle の派生クラスがシリアル化可能としてマークされていることを確認します。SafeHandle から派生したクラスがシリアル化可能になっていることはまれですが、この場合、ISerializable インターフェイスを実装するか、シリアル化と逆シリアル化のプロセスを手動で制御する他の技法を使用する必要があります。これが必要となる理由は、既定のシリアル化処理では、囲まれた未処理のハンドル値のビットごとの複製が作成されるため、2 つの SafeHandle インスタンスが同じハンドルを所有すると考えられるからです。ある時点で、両方のインスタンスが同じハンドルで ReleaseHandle を呼び出そうとします。これを実行する 2 つ目の SafeHandle は失敗します。SafeHandle をシリアル化する場合の適切な処理は、DuplicateHandle 関数、またはネイティブ ハンドルの型が明確に区別された有効なハンドルのコピーを作成するための同様の関数を呼び出すことです。ハンドルの型がこれをサポートしていない場合、その型をラップする SafeHandle 型をシリアル化可能にすることはできません。

  • ハンドルを早期に閉じると、ハンドルを解放するために使用するネイティブ ルーチン (CloseHandle 関数など) にデバッガのブレークポイントを配置して最終的に ReleaseHandle メソッドが呼び出されたときに、エラーの原因となるため、ハンドルを閉じた場所を追跡できることがあります。これは、このようなルーチンが処理することの多い大量のトラフィックに起因するストレスのシナリオや中規模の機能テストではできないことがあります。この追跡は、呼び出し元の ID (場合によっては完全なスタック トレース) と、解放されるハンドルの値をキャプチャするために、ネイティブな解放メソッドを呼び出すコードをインストルメントする際に役立つ場合があります。ハンドル値をこの MDA によって報告された値と比較できます。

  • CloseHandle 関数によって解放できるすべての Win32 ハンドルなど、一部のネイティブ ハンドルの型はハンドルの同じ名前空間を共有します。あるハンドルの型を誤って解放すると、別のハンドルの型で問題が生じる可能性があります。たとえば、Win32 イベント ハンドルを誤って 2 回閉じた場合、明らかに無関係なファイル ハンドルが早期に閉じられる原因になる可能性があります。これは、ハンドルが解放され、ハンドル値が別の種類である可能性がある別のリソースの追跡に使用できるようになったときに発生します。この状況が発生した後にハンドルが誤ってもう一度解放されると、無関係なスレッドのハンドルが無効になる可能性があります。

ランタイムへの影響

この MDA は、CLR に影響ありません。

出力

SafeHandle または CriticalHandle がハンドルを適切に解放できなかったことを示すメッセージ。この例を次に示します。

"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

その他の技術情報

相互運用性