Partager via


Assistant Débogage managé releaseHandleFailed

Mise à jour : novembre 2007

L'Assistant Débogage managé (MDA, Managed Debugging Assistant) releaseHandleFailed est activé pour avertir des développeurs que la méthode ReleaseHandle d'une classe dérivée de SafeHandle ou CriticalHandle retourne false.

Symptômes

Fuites de ressources ou de mémoire. Si la méthode ReleaseHandle de la classe dérivée de SafeHandle ou de CriticalHandle échoue, il est possible que la ressource encapsulée par la classe n'ait pas pu être libérée ou nettoyée.

Cause

Les utilisateurs doivent fournir l'implémentation de la méthode ReleaseHandle s'ils créent des classes qui dérivent de SafeHandle ou de CriticalHandle ; les circonstances sont donc spécifiques à la ressource individuelle. Toutefois, il existe certaines exigences :

  • Les types SafeHandle et CriticalHandle représentent des wrappers autour de ressources de processus essentielles. Une fuite de mémoire finirait par rendre le processus inutilisable.

  • La méthode ReleaseHandle ne doit pas échouer dans l'exécution de sa fonction. Une fois que le processus acquiert une telle ressource, ReleaseHandle est la seule façon de le libérer. Par conséquent, tout échec se traduit par des fuites de ressources.

  • Tout échec qui se produit pendant l'exécution de ReleaseHandle, empêchant la libération de la ressource, représente un bogue dans l'implémentation de la méthode ReleaseHandle elle-même. C'est au programmeur qu'il revient de s'assurer que le contrat est respecté, même si ce code appelle du code créé par un autre utilisateur pour exécuter sa fonction.

Résolution

Vous devez passer en revue le code utilisant le type SafeHandle (ou CriticalHandle) spécifique qui a déclenché la notification de l'Assistant Débogage managé et rechercher les régions où la valeur du handle brut est extraite de SafeHandle et copiée ailleurs. C'est la cause de la plupart des échecs dans les implémentations de SafeHandle ou de CriticalHandle, car le runtime n'effectue plus de suivi de l'utilisation de la valeur du handle brut. Si la copie du handle brut est fermée par la suite, cela peut provoquer l'échec d'un appel ultérieur à ReleaseHandle, car c'est le même handle, désormais non valide, qui fait l'objet d'une tentative de fermeture.

Une duplication de handle incorrecte peut se produire dans plusieurs cas :

  • Recherchez des appels à la méthode DangerousGetHandle. Les appels à cette méthode doivent être extrêmement rares et, si vous en trouvez, ils doivent être entourés d'appels aux méthodes DangerousAddRef et DangerousRelease. Ces dernières spécifient la région de code dans laquelle la valeur du handle brut peut être utilisée en toute sécurité. En dehors de cette région, ou si le décompte de références n'est jamais incrémenté, la valeur du handle peut être invalidée à tout moment par un appel à Dispose ou à Close sur un autre thread. Dès que toutes les utilisations de DangerousGetHandle ont été localisées, vous devez suivre le chemin d'accès emprunté par le handle brut pour vous assurer qu'il n'est pas remis à un composant quelconque qui finira par appeler CloseHandle ou une autre méthode native de niveau inférieur qui libérera le handle.

  • Assurez-vous que le code utilisé pour initialiser SafeHandle avec une valeur de handle brut valide est propriétaire du handle. Si vous formez un SafeHandle autour d'un handle que votre code ne possède pas sans affecter au paramètre ownsHandle la valeur false dans le constructeur de base, alors SafeHandle et le véritable propriétaire du handle peuvent tenter de fermer le handle, ce qui se traduit par une erreur dans ReleaseHandle si SafeHandle n'y accède pas en premier.

  • Lors du marshaling de SafeHandle entre des domaines d'application, vérifiez si la dérivation de SafeHandle utilisée a été marquée comme étant sérialisable. Dans les rares cas où une classe dérivée de SafeHandle est devenue sérialisable, elle doit implémenter l'interface ISerializable ou utiliser l'une des autres techniques de contrôle manuel du processus de sérialisation et de désérialisation. C'est indispensable dans la mesure où l'action de sérialisation par défaut consiste à créer un clone de bits de la valeur du handle brut incluse et se traduit donc par la présence de deux instances de SafeHandle qui se considèrent toutes deux propriétaires du même handle. Toutes deux essaieront d'appeler ReleaseHandle sur le même handle à un moment donné. Le deuxième SafeHandle à le faire échouera. Lorsque vous sérialisez SafeHandle, la solution appropriée consiste à appeler la fonction DuplicateHandle ou une fonction similaire pour votre type de handle natif afin de réaliser une copie de handle légale distincte. Si votre type de handle ne prend pas en charge cette opération, le type SafeHandle qui l'encapsule ne peut pas devenir sérialisable.

  • Il est parfois possible d'identifier la région où un handle est fermé de façon anticipée (ce qui se traduit par un échec lorsque la méthode ReleaseHandle est finalement appelée) en plaçant un point d'arrêt de débogueur sur la routine native utilisée pour libérer le handle, par exemple la fonction CloseHandle. Ce n'est pas toujours possible dans les scénarios de tests de contraintes, voire même de tests fonctionnels de taille moyenne, en raison du trafic important généralement associé à de telles routines. Il peut être utile d'instrumenter le code qui appelle la méthode de libération native, afin de capturer l'identité de l'appelant, ou éventuellement une trace de la pile complète ainsi que la valeur du handle libéré. La valeur du handle peut être comparée à la valeur transmise par cet Assistant Débogage managé.

  • Notez que certains types de handle natifs, et notamment tous les handles Win32 qui peuvent être libérés via la fonction CloseHandle, partagent le même espace de noms de handles. Une libération incorrecte d'un type de handle peut provoquer des problèmes avec un autre. Par exemple, fermer accidentellement deux fois un handle d'événement Win32 peut provoquer la fermeture prématurée d'un handle de fichier apparemment non lié. Cela se produit lorsque le handle est libéré et que la valeur du handle est à nouveau disponible pour effectuer un suivi d'une autre ressource, parfois d'un autre type. Si c'est le cas et qu'elle est suivie d'une deuxième libération erronée, le handle d'un thread non lié peut être invalidé.

Effet sur le runtime

Ce MDA n'a aucun effet sur le CLR.

Sortie

Message indiquant qu'un SafeHandle ou un CriticalHandle n'est pas parvenu à libérer correctement le handle. Par exemple :

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

Configuration

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

Exemple

À titre d'exemple, le code suivant peut activer l'Assistant Débogage managé 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);
}

Voir aussi

Concepts

Diagnostic d'erreurs avec les Assistants de débogage managés

Vue d'ensemble du marshaling d'interopérabilité

Référence

MarshalAsAttribute

Autres ressources

Interopérabilité