releaseHandleFailed MDA

Помощник по отладке управляемого кода (MDA) releaseHandleFailed активируется для уведомления разработчиков, когда метод ReleaseHandle класса, производного от SafeHandle или CriticalHandle, возвращает значение false.

Симптомы

Утечки памяти или ресурсов Если метод ReleaseHandle класса, производного от SafeHandle или CriticalHandle завершается с ошибкой, то ресурс, инкапсулированный этим классом, может быть не освобожден или не очищен.

Причина

Пользователи должны предоставить реализацию метода ReleaseHandle, если они создают классы, производные от SafeHandle или CriticalHandle; таким образом, эти обстоятельства характерны для отдельных ресурсов. Однако действуют следующие требования.

  • Типы SafeHandle и CriticalHandle представляют оболочки важных ресурсов процессов. Утечка памяти может со временем сделать процесс непригодным для использования.

  • Метод ReleaseHandle должен работать без сбоев для выполнения своих функций. Когда процесс запрашивает такой ресурс, метод ReleaseHandle является единственным способом для его освобождения. Таким образом, ошибка подразумевает утечку ресурсов.

  • Все ошибки, возникающие при выполнении метода ReleaseHandle и препятствующие освобождению ресурса, представляют ошибку в реализации самого метода ReleaseHandle. Программист должен убедиться, что контракт будет выполнен, даже если этот код вызывает код, созданный другим разработчиком для выполнения своих функций.

Решение

Код, использующий конкретный тип SafeHandle (или CriticalHandle), вызвавший уведомление MDA, следует пересмотреть, найти места, где необработанное значение дескриптора извлекается из SafeHandle и копируется в другом месте. Это обычная причина сбоев в реализации SafeHandle или CriticalHandle, так как использование необработанного значения дескриптора далее не отслеживается средой выполнения. Если копия необработанного дескриптора впоследствии закрывается, это может привести к сбою последующего вызова метода ReleaseHandle, поскольку попытка закрытия выполняется в том же дескрипторе, который теперь является недопустимым.

Существует несколько путей, в которых может возникнуть дублирование неверного дескриптора.

  • Поиск вызовов метода DangerousGetHandle. Вызовы этого метода должны быть крайне редки, и любой такой найденный вызов должны быть окружен вызовами методов DangerousAddRef и DangerousRelease. Эти последующие методы определяют участок кода, в котором можно безопасно использовать необработанное значение дескриптора. Вне этой области или в случае если число ссылок вообще никогда не увеличивается, значение дескриптора может стать недействительным в любое время путем вызова метода Dispose или Close в другом потоке. Если все случаи использования метода DangerousGetHandle отслеживаются, необходимо следовать пути, к которому прибегает необработанный дескриптор, чтобы гарантировать, что он не передан в компонент, который в конечном итоге вызывает CloseHandle или другой низкоуровневый собственный метод, который будет освобождать этот дескриптор.

  • Убедитесь, что код, который используется для инициализации SafeHandle с использованием допустимого необработанного значения дескриптора, владеет этим дескриптором. При создании SafeHandle вокруг дескриптора, который не принадлежит коду без установки параметра ownsHandle в значение false в конструкторе базового класса, и SafeHandle, и реальный владелец дескриптора могут пытаться закрыть этот дескриптор, что приводит к ошибке в методе ReleaseHandle, если SafeHandle теряет состояние гонки.

  • SafeHandle При маршале между доменами приложений убедитесь, что используемый SafeHandle производный элемент помечен как сериализуемый. В редких случаях, когда класс, производный от SafeHandle, был сделан сериализуемым, он должен реализовывать интерфейс ISerializable или использовать один из других методов для управления процессом сериализации и десериализации вручную. Это необходимо, поскольку действие сериализации по умолчанию заключается в создании побитового клона включенного необработанного значения дескриптора, в результате чего два экземпляра SafeHandle думают, что они владеют одним и тем же дескриптором. Оба будут пытаться вызвать метод ReleaseHandle в том же дескрипторе в определенный момент. Второму SafeHandle это сделать не удастся. Правильный способ действий при сериализации SafeHandle заключается в вызове функции DuplicateHandle или аналогичной функции для вашего собственного типа дескриптора, чтобы создать другую копию действительного дескриптора. Если ваш тип дескриптора не поддерживает такое действие, то обертывающий его тип SafeHandle нельзя сделать сериализуемым.

  • Можно отслеживать, где дескриптор закрывается раньше, что приводит к возникновению ошибки при последнем вызове метода ReleaseHandle, установив точку останова отладчика в исходной программе, используемой для освобождения дескриптора, например в функции CloseHandle. Это может оказаться невозможным в сценариях нагрузочных тестов или даже в функциональных тестах среднего размера из-за большого объема трафика, с которым часто сталкиваются подобные программы. Может помочь инструментирование кода, который вызывает собственный метод освобождения, чтобы записывать удостоверение вызывающего объекта или возможно полную трассировку стека и значение дескриптора. Значение дескриптора можно сравнивать со значением, предоставленным MDA.

  • Обратите внимание, что некоторые типы собственных дескрипторов, например все дескрипторы Win32, которые могут быть освобождены с помощью функции CloseHandle, совместно используют одно пространство имен дескрипторов. Ошибочное освобождение одного типа дескриптора может вызвать проблемы с другим. Например, случайное закрытие дескриптора события Win32 дважды может привести к вероятному преждевременному закрытию несвязанного дескриптора файла. Это происходит, когда дескриптор освобождается и значение дескриптора становится доступным для использования в целях отслеживания другого ресурса, возможно другого типа. Если это происходит и приводит к ошибочному повторному освобождению, дескриптор несвязанного потока может стать недействительным.

Влияние на среду выполнения

Этот помощник отладки управляемого кода не оказывает никакого влияния на среду 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>  

Пример

Ниже приведен пример кода, который может активировать MDA releaseHandleFailed.

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);  
}  

См. также раздел