Note
この記事は .NET Framework に固有のものです。 .NET 6 以降のバージョンを含む、.NET の新しい実装には適用されません。
マネージド デバッグ アシスタント (MDA) がアクティブ化releaseHandleFailedは、SafeHandleから派生したクラスのReleaseHandle メソッドがfalseを返したときに開発者 CriticalHandleに通知することです。
Symptoms
リソースまたはメモリ リーク。 SafeHandleまたはCriticalHandleから派生するクラスのReleaseHandle メソッドが失敗した場合、クラスによってカプセル化されたリソースが解放またはクリーンアップされていない可能性があります。
原因
ユーザーは、SafeHandleまたはCriticalHandleから派生するクラスを作成する場合、ReleaseHandle メソッドの実装を提供する必要があります。したがって、状況は個々のリソースに固有です。 ただし、要件は次のとおりです。
SafeHandle および CriticalHandle 型は、重要なプロセス リソースのラッパーを表します。 メモリ リークにより、プロセスは時間の経過と同時に使用できなくなります。
ReleaseHandle メソッドは、その関数の実行に失敗してはなりません。 プロセスがそのようなリソースを取得したら、 ReleaseHandle を解放する唯一の方法です。 したがって、障害はリソース リークを意味します。
リソースの解放を妨げる ReleaseHandleの実行中に発生するエラーは、 ReleaseHandle メソッド自体の実装におけるバグです。 そのコードが他のユーザーによって作成されたコードを呼び出して関数を実行する場合でも、コントラクトが確実に満たされるようにするのはプログラマの責任です。
Resolution
MDA 通知を発生させた特定の SafeHandle (または CriticalHandle) 型を使用するコードを確認し、生のハンドル値が SafeHandle から抽出され、別の場所にコピーされる場所を探す必要があります。 これは、生のハンドル値の使用がランタイムによって追跡されなくなったため、 SafeHandle または CriticalHandle 実装内の通常のエラーの原因です。 生ハンドルのコピーが後で閉じられると、後で ReleaseHandle 呼び出しが失敗する可能性があります。この呼び出しは、同じハンドルで終了が試みられ、現在は無効です。
ハンドルの重複が正しくない場合は、いくつかの方法があります。
DangerousGetHandle メソッドの呼び出しを探します。 このメソッドの呼び出しは非常にまれで、見つけたものはすべて、 DangerousAddRef メソッドと DangerousRelease メソッドの呼び出しで囲む必要があります。 これらの後者のメソッドは、生のハンドル値を安全に使用できるコードの領域を指定します。 この領域の外側、または最初に参照カウントがインクリメントされない場合は、別のスレッドで 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 回閉じると、明らかに関連のないファイル ハンドルが途中で閉じられる可能性があります。 これは、ハンドルが解放され、ハンドル値が別のリソース (別の種類の可能性がある) を追跡するために使用できるようになると発生します。 これが発生し、その後に誤った 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>
Example
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);
}
こちらも参照ください
.NET