Condividi tramite


releaseHandleFailed (MDA)

Nota

Questo articolo è specifico per .NET Framework. Non si applica alle implementazioni più recenti di .NET, incluse .NET 6 e versioni successive.

L'assistente al debug gestito releaseHandleFailed viene attivato per notificare agli sviluppatori quando il metodo ReleaseHandle di una classe derivata da SafeHandle o CriticalHandle restituisce false.

Sintomi

Perdita di risorse o di memoria Se si verifica un errore nel metodo ReleaseHandle della classe che deriva da SafeHandle o CriticalHandle, è possibile che la risorsa incapsulata dalla classe non sia stata rilasciata o eliminata.

Causa

Se si creano classi che derivano da SafeHandle o CriticalHandle, è necessario fornire l'implementazione del metodo ReleaseHandle. Di conseguenza, le problematiche sono specifiche della singola risorsa. È necessario, tuttavia, che siano rispettati i seguenti requisiti:

  • I tipi SafeHandle e CriticalHandle rappresentano wrapper per risorse vitali di un processo. Una perdita di memoria può rendere il processo inusabile.

  • È necessario che non si verifichi un errore durante l'esecuzione del metodo ReleaseHandle. Una volta che il processo acquisisce una risorsa di questo tipo, per rilasciarla è necessario usare il metodo ReleaseHandle. Un errore del metodo implica quindi una perdita di risorse.

  • Qualsiasi errore che si verifica durante l'esecuzione del metodo ReleaseHandle, impedendo il rilascio della risorsa, costituisce un bug nell'implementazione del metodo ReleaseHandle stesso. È compito del programmatore assicurare che il contratto venga rispettato, anche se durante l'esecuzione viene chiamato codice creato da altri utenti.

Risoluzione

È necessario esaminare il codice che usa lo specifico tipo SafeHandle (o CriticalHandle) che ha generato la notifica dell'assistente al debug gestito per individuare i punti in cui il valore dell'handle non elaborato viene estratto da SafeHandle e copiato in un'altra posizione. Questa è la causa principale degli errori nelle implementazioni di SafeHandle o CriticalHandle, poiché l'uso del valore dell'handle non elaborato non viene più controllato da Common Language Runtime. Se successivamente la copia dell'handle non elaborato viene chiusa, può verificarsi un errore in una successiva chiamata a ReleaseHandle poiché la chiusura viene tentata sullo stesso handle, ormai non più valido.

Esistono alcune situazioni in cui può verificarsi la duplicazione di un handle non corretto:

  • Individuare le chiamate al metodo DangerousGetHandle. Le chiamate a questo metodo dovrebbero essere molto rare e quelle eventualmente presenti dovrebbero essere circondate da chiamate ai metodi DangerousAddRef e DangerousRelease. Questi ultimi specificano l'area di codice in cui il valore dell'handle non elaborato può essere usato in maniera sicura. Al di fuori di quest'area, o se il conteggio dei riferimenti non viene mai incrementato nella prima posizione, il valore dell'handle può essere invalidato in qualsiasi momento da una chiamata a Dispose o Close su un altro thread. Una volta individuati tutti gli usi di DangerousGetHandle, è necessario seguire il percorso dell'handle non elaborato per verificare che non venga passato a qualche componente che chiamerà CloseHandle o un altro metodo nativo di basso livello che causerà il rilascio dell'handle.

  • Verificare che l'handle non elaborato appartenga al codice usato per inizializzare SafeHandle con un valore di handle valido. Se si definisce un oggetto SafeHandle intorno a un handle che non appartiene al codice senza impostare il parametro ownsHandle su false nel costruttore base, sia SafeHandle che il vero proprietario dell'handle possono tentare di chiudere l'handle, generando quindi un errore in ReleaseHandle se SafeHandle non riesce a chiudere l'handle prima del proprietario.

  • Quando un SafeHandle oggetto viene sottoposto a marshalling tra domini applicazione, verificare che la SafeHandle derivazione usata sia stata contrassegnata come serializzabile. Nei rari casi in cui una classe derivata da SafeHandle sia stata resa serializzabile, tale classe deve implementare l'interfaccia ISerializable o usare una delle altre tecniche che consentono di controllare manualmente il processo di serializzazione e deserializzazione. Questa operazione è necessaria poiché l'azione di serializzazione predefinita consiste nel creare un clone bit per bit del valore dell'handle non elaborato incluso e questo fa sì che due istanze di SafeHandle ritengano di essere proprietarie dello stesso handle. A un certo punto entrambe tenteranno di chiamare ReleaseHandle sullo stesso handle, ma ovviamente la seconda istanza di SafeHandle non riuscirà ad eseguire questa operazione. La procedura corretta da seguire quando si serializza un oggetto SafeHandle consiste nel chiamare la funzione DuplicateHandle o una funzione simile per il tipo di handle nativo in modo da creare una copia valida distinta dell'handle. Se il tipo di handle non supporta questa funzione, il tipo SafeHandle che lo include non può essere reso serializzabile.

  • È possibile individuare il punto in cui un handle viene chiuso anticipatamente, causando un errore al momento della chiamata finale al metodo ReleaseHandle, inserendo un punto di interruzione sulla routine nativa usata per rilasciare l'handle, ad esempio la funzione CloseHandle. Questo potrebbe non essere possibile in situazioni di sovraccarico o anche nel caso di test funzionali di medie dimensioni a causa del volume elevato di traffico spesso associato a tali routine. In questo caso può essere utile instrumentare il codice che chiama il metodo di rilascio nativo, allo scopo di catturare l'identità del chiamante, o eventualmente eseguire una traccia dello stack completa, in modo da ottenere il valore dell'handle rilasciato, che può essere quindi confrontato con il valore indicato da questo assistente al debug gestito.

  • Alcuni tipi di handle nativi, ad esempio tutti gli handle Win32 che possono essere rilasciati mediante la funzione CloseHandle, condividono lo stesso spazio dei nomi dell'handle. Il rilascio errato di un tipo di handle può causare problemi con un altro handle. Se ad esempio si chiude involontariamente due volte un handle di evento Win32, è possibile che venga chiuso prematuramente un handle di file apparentemente non correlato. Questo problema si verifica quando l'handle viene rilasciato e il valore dell'handle può essere usato per controllare un'altra risorsa, eventualmente di un altro tipo. Se in questa situazione viene eseguito per errore un secondo rilascio, è possibile che venga invalidato l'handle di un thread non correlato.

Effetto sull'ambiente di esecuzione

L'assistente al debug gestito non ha alcun effetto su CLR.

Output

Un messaggio indicante che un oggetto SafeHandle o CriticalHandle non è riuscito a rilasciare correttamente l'handle. Ad esempio:

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

Impostazione

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

Esempio

i seguito è riportato un esempio di codice che può attivare l'assistente al debug gestito 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);
}

Vedi anche