MDA releaseHandleFailed

Observação

Este artigo é específico para aplicativos .NET Framework. Ele não se aplica a implementações mais recentes do .NET, incluindo o .NET 6 e versões posteriores.

O MDA (Assistente de Depuração Gerenciado) de releaseHandleFailed é ativado é notificar os desenvolvedores quando o método ReleaseHandle de uma classe derivada de SafeHandle ou CriticalHandle retorna false.

Sintomas

Perdas de memória ou de recursos. Se o método ReleaseHandle da classe derivada de SafeHandle ou CriticalHandle falhar, então o recurso encapsulado pela classe talvez não tenha sido liberado ou limpo.

Causa

Os usuários devem fornecer a implementação do método ReleaseHandle se eles criarem classes que derivam de SafeHandle ou CriticalHandle; assim, as circunstâncias são específicas do recurso individual. No entanto, os requisitos de configuração são os seguintes:

  • Os tipos SafeHandle e CriticalHandle representam wrappers em torno de recursos vitais do processo. Uma perda de memória inutilizaria o processo ao longo do tempo.

  • O método ReleaseHandle não deve falhar ao executar sua função. Depois que o processo adquire um recurso desse tipo, ReleaseHandle é a única maneira de liberá-lo. Portanto, a falha implica em perda de recursos.

  • Qualquer falha que ocorra durante a execução de ReleaseHandle, impedindo a liberação do recurso, é um bug na implementação do método ReleaseHandle em si. É responsabilidade do programador garantir que o contrato seja atendido, mesmo que esse código, para executar sua função, chame código criado por outra pessoa.

Resolução

O código que usa o tipo SafeHandle específico (ou CriticalHandle) que gerou a notificação de MDA deve ser revisado, procurando os locais em que o valor do identificador bruto é extraído do SafeHandle e copiado em outro lugar. Essa é a causa comum de falhas em implementações de SafeHandle ou CriticalHandle, porque o uso do valor do identificador bruto não é mais controlado pelo runtime. Se a cópia de identificador bruto subsequentemente for fechada, isso poderá causar falha em uma chamada ReleaseHandle posterior porque a tentativa de fechar ocorrerá no mesmo identificador, que será então inválido.

Há várias maneiras em que a duplicação de identificador incorreto pode ocorrer:

  • Procure por chamadas para o método DangerousGetHandle. Chamadas para esse método deverão ser muito raras e qualquer uma que você encontrar deverá ficar entre chamadas para os métodos DangerousAddRef e DangerousRelease. Esses métodos especificam a região do código na qual o valor do identificador bruto pode ser usado com segurança. Fora dessa região ou se a contagem de referência nunca chegar a ser incrementada, o valor do identificador poderá ser invalidado a qualquer momento por uma chamada para Dispose ou Close em outro thread. Uma vez que todos os usos de DangerousGetHandle tiverem sido identificados, você deverá percorrer o caminho seguido pelo identificador bruto para assegurar que ele não seja cedido a nenhum componente que, eventualmente, chame CloseHandle ou outro método nativo de baixo nível que libere o identificador.

  • Verifique se o código usado para inicializar o SafeHandle com um valor de identificador bruto válido é proprietário do identificador. Se você formar um SafeHandle em torno de um identificador que não for propriedade de seu código sem configurar o parâmetro ownsHandle como false no construtor base, então ambos o SafeHandle e o proprietário do identificador real poderão tentar fechar o identificador, levando a um erro em ReleaseHandle se o SafeHandle perder a corrida.

  • Quando um SafeHandle tem um marshalling entre domínios de aplicativo, confirme que a derivação SafeHandle sendo usada foi marcada como serializável. Nos raros casos em que uma classe derivada de SafeHandle foi tornada serializável, ela deve implementar a interface ISerializable ou usar uma das outras técnicas para controlar o processo de serialização e desserialização manualmente. Isso é necessário porque a ação de serialização padrão é criar um clone bit a bit do valor de identificador bruto anexado, o que resulta em duas instâncias SafeHandle pensando que são proprietárias do mesmo identificador. Ambas tentarão chamar ReleaseHandle no mesmo identificador em algum momento. O segundo SafeHandle a fazer isso falhará. A maneira correta de serializar um SafeHandle é chamar a função DuplicateHandle ou uma função semelhante para seu tipo de identificador nativo fazer uma cópia de identificador legal distinta. Se o tipo de identificador não dá suporte a isso, então o tipo SafeHandle encapsulando-o não pode ser transformado em serializável.

  • É possível acompanhar casos em que um identificador está sendo fechado precocemente, o que leva a uma falha quando o método ReleaseHandle finalmente é chamado, colocando-se um ponto de interrupção do depurador na rotina nativa usada para liberar o identificador, por exemplo, a função CloseHandle. Isso talvez não seja possível para cenários de estresse ou até mesmo testes funcionais de tamanho médio em razão do tráfego intenso com que essas rotinas costumam lidar. Talvez seja útil instrumentar o código que chama o método de liberação nativo para capturar a identidade do chamador ou, possivelmente, um rastreamento de pilha completo e o valor do identificador sendo liberado. O valor do identificador pode ser comparado com o valor indicado por esse MDA.

  • Observe que alguns tipos de identificador nativo que podem ser liberados por meio da função CloseHandle, tais como Win32, compartilham o mesmo namespace de identificador. Uma liberação incorreta de um tipo de identificador pode causar problemas com outro. Por exemplo, fechar acidentalmente um identificador de evento Win32 duas vezes pode resultar no fechamento prematuro de um identificador de arquivo aparentemente não relacionado. Isso ocorre quando o identificador é liberado e o valor dele se torna disponível para ser usado para acompanhar outro recurso, potencialmente de outro tipo. Se isso ocorre e é seguido por uma segunda liberação errônea, o identificador de um thread não relacionado pode invalidado.

Efeito sobre o runtime

Esse MDA não tem efeito sobre o CLR.

Saída

Uma mensagem indicando que um SafeHandle ou CriticalHandle falhou em liberar o identificador. Por exemplo:

"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."

Configuração

<mdaConfig>
  <assistants>
    <releaseHandleFailed/>
  </assistants>
</mdaConfig>

Exemplo

A seguir está um exemplo de código que pode ativar o 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);
}

Confira também