Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

CoqBlog

.NET is good :-)
{ Blog de coq }

Actualités

XmlReader / XmlWriter initialisés sur Stream, TextWriter, TextReader, ... : attention à la fermeture

S'il y a bien une chose qu'il faut faire quand on utilise une classe dont le suffixe est "Reader" ou "Writer" en l'initialisant avec une instance d'un type dérivé de Stream, TextWriter ou TextReader (etc...), c'est vérifier son comportement vis-à-vis de cette instance.
Et en particulier celui lors de la fermeture de ce Reader/Writer, effectuée en général par un appel explicite à une méthode Close ou Dispose (ou un appel implicite à Dispose au moyen d'un bloc using).

Il ne faut surtout pas tenter de deviner : si la documentation n'est pas extrèmement claire, Reflector et/ou Reference Source sont nos amis.
Car si on devine mal on peut se retrouver, dans le cas de XmlReader / XmlWriter, avec un bug qui ne sera pas forcément détecté durant la phase de test mais plutôt durant la phase de production et surtout durant les phases d'évolution / maintenance (encore plus loin dans le cycle de vie).

Avis aux lecteurs qui passeraient ici quelques temps après publication :
Ce post se place dans le contexte de .NET 4.0 RTM.
Bien qu'un changement de ce comportement sur les classes existant dans le Framework .NET à ce jour soit peu probable (ce serait un changement brisant avec pas mal de conséquences), nous n'en sommes pas à l'abri : donc vérifiez, comme il le faut toujours, que les informations exposées ci-dessous sont toujours valides (et n'hésitez pas, si vous le désirez, à laisser un commentaire si ce n'est plus le cas ou si j'ai fait une erreur dès maintenant).

 

 

Le problème

Pour illustrer un peu le problème, prenons l'exemple de 2 méthodes qui ont l'air très similaires mais dont l'une pose un léger problème : elle laisse une instance de FileStream ouverte derrière elle, et donc un handle de fichier.

private static void DoSomethingWithStreamReader(String filePath)
{
    using (StreamReader reader = new StreamReader(
        File.OpenRead(filePath)))
    {
        // ...
    }
}

// ATTENTION : Cette méthode ne ferme pas le flux ouvert sur le fichier.
// WARNING : this method does not close the opened file stream.
private static void DoSomethingWithXmlReader(String filePath)
{
    using (XmlReader reader = XmlReader.Create(
        File.OpenRead(filePath)))
    {
        // ...
    }
}

Cette méthode pose d'ailleurs le même souci :

// ATTENTION : Cette méthode ne ferme pas le flux ouvert sur le fichier.
// WARNING : this method does not close the opened file stream.
private static void DoSomethingWithXmlReaderByStreamReader(String filePath)
{
    using (XmlReader reader = XmlReader.Create(
        new StreamReader(filePath)))
    {
        // ...
    }
}

 

Ce simple code, avec placement d'un point d'arrêt sur l'appel de Console.WriteLine, devrait permettre de constater le fait que le fichier n'est pas fermé :

static void Main(string[] args)
{
    String doSomethingWithStreamReaderFilePath = @"D:\Temp\DoSomethingWithStreamReader.xml";
    String doSomethingWithXmlReaderFilePath = @"D:\Temp\DoSomethingWithXmlReader.xml";

    DoSomethingWithStreamReader(doSomethingWithStreamReaderFilePath);
    DoSomethingWithXmlReader(doSomethingWithXmlReaderFilePath);

    Console.WriteLine("Press a key to quit...");
    Console.ReadKey(true);
}


En utilisant Process Explorer par exemple :

 Capture de la fenêtre de Process Explorer montrant le handle de fichier non fermé

 

 

Les conséquences

Pas de fuite extrêmement grave ici : la finalisation de l'instance de la classe dérivée de Stream entrainera la fermeture des handles, et ce bien avant la fermeture du processus.
Mais ça reste tout de même un problème, surtout que sur ce genre de fuite il se peut très bien qu'aucun problème ne soit constaté avant longtemps : il suffit que l'application ne réutilise pas le fichier, ou alors qu'il ne lui faille qu'un accès partagé en lecture.

Il se peut même que le problème ne soit jamais constaté dans le cas d'une application réutilisant le fichier en accès exclusif : si la finalisation intervient toujours avant cette réutilisation, le problème ne sera probablement jamais levé.

Jusqu'au jour où un changement survient dans l'application ou son environnement :

  • la charge de la machine hébergeant l'application diminue, entrainant un stress moins fort sur le Garbage Collector
  • l'application est déplacée vers une machine avec une configuration bien plus confortable
  • le code est migré vers une nouvelle version de .NET, pour laquelle le Garbage Collector ne fonctionne plus exactement de la même manière
  • le code est modifié : un traitement long et/ou gourmand qui se trouvait entre les 2 accès au fichier est supprimé / optimisé
  • le code est modifié : quelqu'un introduit un second traitement sur le fichier là où il n'y en avait auparavant qu'un seul
  • des traitements parallèles s'exécutant dans le même processus sont arrêtés
  • ...

 

 

Pourquoi le flux reste ouvert

Contrairement à d'autres classes comme StreamReader/StreamWriter les implémentations de XmlReader et XmlWriter ne partent pas du principe que, lorsque nous appelons leur méthode Close ou Dispose, nous n'avons plus besoin du flux que nous leur avions affecté.
Ces classes ne considèrent pas qu'elle deviennent propriétaires des flux.

C'est à nous d'effectuer la fermeture du flux au bon moment, ou de demander au code de XmlReader/XmlWriter de le faire pour nous en le spécifiant à la création :

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: mercredi 6 octobre 2010 19:15 par coq
Classé sous : , ,

Commentaires

Rui a dit :

Toi il t'a fait quelque chose le XmlReader non?

Merci, super info en tout cas.

# octobre 6, 2010 22:31

coq a dit :

De rien :-)

Oui, il m'a causé un de ces sympathiques "Le fichier est en cours d'utilisation par un autre processus" un peu improbable.

Mais sur ce coup là c'est surtout moi qui me suis tiré dans le pied en assumant que le comportement de tous les *Reader/*Writer était le même, du coup j'ai perdu du temps avant qu'on ne pense à ça pour le problème de fond :-/

# octobre 6, 2010 23:17
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01