releaseHandleFailed MDA
啟動 releaseHandleFailed Managed 偵錯助理 (MDA) 是為了通知開發人員,衍生自 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 方法的呼叫所包圍。 後者的方法會指定可安心使用原始控制代碼值的程式碼區域, 而在這個區域之外,或是一開始就不會遞增參考計數時,任何時間呼叫另一個執行緒上的 Dispose 或 Close 可能會讓此控制代碼值失效。 一旦已經追蹤到 DangerousGetHandle 的所有使用情形時,您應該遵循原始控制代碼所採用的路徑,以確保它不會交給最後會呼叫 CloseHandle 的某個元件,或是另一個將會釋放此控制代碼的低階原生方法。
確保此程式碼是用來以主控控制代碼的有效原始控制代碼值初始化 SafeHandle; 如果您在程式碼未主控的控制代碼周圍組成 SafeHandle,而沒有在基底建構函式中將 ownsHandle 參數設定為 false,則 SafeHandle 和真正的控制代碼擁有人都可嘗試關閉此控制代碼,如此當 SafeHandle 輸掉競爭時,會在 ReleaseHandle 中產生錯誤。
在應用程式定義域之間封送處理 SafeHandle 時,要確認所使用的 SafeHandle 衍生已經標記為可序列化。 在衍生自 SafeHandle 的類別已經變成可序列化的罕見情況下,它應該實作 ISerializable 介面或使用其他的一個方法,以手動方式控制序列化和還原序列化的處理程序。 這要的處理是必要的,因為預設序列化動作是要建立封入原始控制代碼值的位元 (Bitwise) 複製品,好讓兩個 SafeHandle 執行個體認為其擁有相同控制代碼。 這兩個執行個體都會嘗試在某個時間點於相同控制代碼上呼叫 ReleaseHandle; 第二個執行這個動作的 SafeHandle 將會失敗。 序列化 SafeHandle 時的正確動作程序,是針對原生控制代碼型別呼叫 DuplicateHandle 函式或類似的函式,以製作不同且有效的控制代碼複本。 如果控制代碼型別不支援這樣的處理,則無法將包裝它的 SafeHandle 型別變成可序列化。
您可以將偵錯工具中斷點放置在用來釋放控制代碼的原生常式上 (例如 CloseHandle 函式),以追蹤早期關閉控制代碼並在最後呼叫 ReleaseHandle 方法時造成失敗的地方。 有壓力的情況或甚至是中型的功能測試可能無法這麼做,因為這類常式經常要處理沈重的流量。 這樣可能有助於檢測呼叫原生釋放方法的程式碼,以便能擷取呼叫端的識別或是可能的已滿堆疊追蹤,以及所釋放的控制代碼值; 此控制代碼值可以與這個 MDA 所報告的值相比較。
請注意,某些原生控制代碼型別 (例如,所有可以透過 CloseHandle 函式釋放的 Win32 控制代碼) 可以共用相同的控制代碼命名空間, 而錯誤釋放一個控制代碼型別可能會造成另一個控制代碼型別的問題。 例如,不小心關閉 Win32 事件控制代碼兩次可能會造成一個明顯無關的檔案控制代碼過早被關閉。 當釋放此控制代碼,且此控制代碼值可用來追蹤另一個資源 (可能是另一個型別的資源) 時,就會發生這個狀況; 如果發生這個狀況時,接著又產生第二次的錯誤釋放,則無關的執行緒之控制代碼可能會失效。
對執行階段的影響
這個 MDA 對 CLR 無效。
Output
一則訊息,表示 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);
}