releaseHandleFailed MDA
releaseHandleFailed 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을 초기화하는 데 사용된 코드가 해당 핸들을 소유하는지 확인합니다. 기본 생성자에서 ownsHandle 매개 변수를 false로 설정하지 않고 코드가 소유하지 않은 핸들 주위에 SafeHandle을 형성하면 SafeHandle 및 실제 핸들 소유자는 핸들을 닫으려고 할 수 있으므로 SafeHandle이 경합 상태를 잃는 경우 ReleaseHandle에 오류가 발생합니다.
SafeHandle이 응용 프로그램 도메인 사이에서 마샬링되면 사용 중인 SafeHandle 파생이 serializable로 표시되었는지 확인합니다. 드문 경우지만 SafeHandle에서 파생된 클래스를 serializable로 만들 경우 ISerializable 인터페이스를 구현하거나 serialization 및 deserialization 프로세스를 수동으로 제어하는 다른 방법 중 하나를 사용해야 합니다. 기본 serialization 작업은 포함된 원시 핸들 값의 비트 복제를 만들어 동일한 핸들을 소유하는 것으로 생각되는 두 개의 SafeHandle 인스턴스를 만드는 것이므로 이 기술이 필요합니다. 두 인스턴스는 모두 어느 시점에 동일한 핸들에 대해 ReleaseHandle을 호출하려고 합니다. 이 작업을 수행하는 두 번째 SafeHandle은 실패합니다. SafeHandle을 serialize할 때 올바른 동작 과정은 DuplicateHandle 함수나 네이티브 핸들 형식의 비슷한 함수를 호출하여 별도의 올바른 핸들 복사본을 만드는 것입니다. 핸들 형식이 이 핸들을 지원하지 않으면 핸들을 래핑한 SafeHandle 형식을 serializable로 만들 수 없습니다.
핸들을 해제하는 데 사용되는 네이티브 루틴(예: CloseHandle 함수)에 디버거 중단점을 배치하면 핸들이 일찍 닫혀 결국 ReleaseHandle 메서드가 마지막으로 호출될 때 오류가 발생하는 위치를 추적할 수 있습니다. 해당 루틴이 특별히 많은 트래픽을 처리해야 하는 경우가 많으므로 이 방법은 스트레스 시나리오나 중간 규모 성능 테스트에는 적용할 수 없습니다. 네이티브 해제 메서드를 호출하는 코드를 계측하여 호출자 ID나 전체 스택 추적 및 해제되는 핸들 값을 쉽게 캡처할 수 있습니다. 핸들 값을 이 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);
}