Partager via


Meilleures pratiques en matière de fiabilité

Les règles de fiabilité suivantes sont orientées vers SQL Server ; toutefois, ils s’appliquent également à n’importe quelle application serveur basée sur l’hôte. Il est extrêmement important que les serveurs tels que SQL Server ne perdent pas de ressources et ne soient pas arrêtés. Toutefois, cela ne peut pas être effectué en écrivant du code de sauvegarde pour chaque méthode qui modifie l’état d’un objet. Le but n’est pas ici d’écrire du code managé totalement fiable et capable de récupérer des erreurs partout où elles se produisent avec du code réécrit. Ce serait une tâche intimidante avec peu de chances de succès. Le Common Language Runtime (CLR) ne peut pas fournir facilement des garanties suffisantes pour le code managé pour rendre l’écriture de code parfait possible. Notez que contrairement à ASP.NET, SQL Server n’utilise qu’un seul processus qui ne peut pas être recyclé sans mettre la base de données hors ligne pendant un temps inacceptablement long.

Compte tenu de l’insuffisance de garanties offertes et de l’exécution au sein d’un seul processus, la fiabilité est fondée sur la possibilité d’arrêter des threads ou de recycler des domaines d’application chaque fois que nécessaire et de prendre des mesures adéquates assurant l’absence de fuites de ressources de système d’exploitation telles que les handles ou la mémoire. Même avec cette contrainte de fiabilité plus simple, il existe toujours une exigence de fiabilité significative :

  • Il ne doit jamais y avoir de fuite de ressources du système d’exploitation.

  • Tous les verrous managés, sous toutes leurs formes, doivent être identifiés pour le CLR.

  • L’état partagé entre les domaines d’application ne doit jamais être interrompu pour permettre au recyclage de AppDomain de se dérouler correctement.

Bien qu'il soit théoriquement possible d'écrire du code managé pour gérer les exceptions ThreadAbortException, StackOverflowException et OutOfMemoryException, il est déraisonnable de s'attendre à ce que les développeurs écrivent un code aussi robuste dans l'ensemble d'une application. Pour cette raison, les exceptions hors bande entraînent l’arrêt du thread en cours d’exécution ; et si le thread terminé modifiait l’état partagé, ce qui peut être déterminé par le fait que le thread détient un verrou, alors le AppDomain est déchargé. Lorsqu’une méthode qui modifie l’état partagé est arrêtée, l’état est endommagé, car il n’est pas possible d’écrire du code de back-out fiable pour les mises à jour à l’état partagé.

Dans .NET Framework version 2.0, le seul hôte nécessitant une fiabilité est SQL Server. Si votre assembly est exécuté sur SQL Server, vous devez effectuer le travail de fiabilité pour chaque partie de cet assembly, même s’il existe des fonctionnalités spécifiques désactivées lors de l’exécution dans la base de données. Cela est nécessaire, car le moteur d’analyse du code examine le code au niveau de l’assembly et ne peut pas différencier le code désactivé. Un autre élément à prendre en compte dans la programmation SQL Server est le fait que SQL Server exécute tout dans un seul processus et le recyclage de AppDomain est utilisé pour nettoyer toutes les ressources telles que la mémoire ou les handles du système d’exploitation.

Vous ne pouvez pas compter sur des finaliseurs, des destructeurs ou des blocs try/finally pour du code réécrit, Ils pourraient être interrompus ou ne pas être appelés.

Les exceptions asynchrones peuvent être levées dans des emplacements inattendus, éventuellement à chaque instruction de machine : ThreadAbortException, StackOverflowException et OutOfMemoryException.

Les threads managés ne sont pas nécessairement des threads Win32 dans SQL ; ils peuvent être des fibres.

L’état partagé mutable de domaine à l’échelle du processus ou inter-applications est extrêmement difficile à modifier en toute sécurité et doit être évité dans la mesure du possible.

Les conditions hors mémoire ne sont pas rares dans SQL Server.

Si les bibliothèques hébergées dans SQL Server ne mettent pas à jour correctement leur état partagé, il existe une probabilité élevée que le code ne récupère pas tant que la base de données n’a pas été redémarrée. En outre, dans certains cas extrêmes, il est possible que le processus SQL Server échoue, ce qui entraîne le redémarrage de la base de données. Le redémarrage de la base de données peut réduire un site Web ou affecter les opérations de l’entreprise, ce qui nuit à la disponibilité. Une fuite lente des ressources du système d’exploitation telles que la mémoire ou les handles peut entraîner l’échec de l’allocation de handles par le serveur sans possibilité de récupération, ou potentiellement le serveur peut dégrader lentement les performances et réduire la disponibilité de l’application d’un client. Il est clair que nous voulons éviter ces scénarios.

Règles de bonnes pratiques

L’introduction s’est concentrée sur ce que la révision du code managé, qui s’exécute dans le serveur, devrait détecter pour augmenter la stabilité et la fiabilité du framework. Toutes ces vérifications sont bonnes pratiques en général et un must absolu sur le serveur.

Face à une contrainte de verrou ou de ressource morte, SQL Server abandonnera un thread ou supprimera un AppDomain. Lorsque cela se produit, seul le code de sauvegarde dans une région d’exécution contrainte (CER) est garanti pour être exécuté.

Utiliser SafeHandle pour éviter les fuites de ressources

Dans le cas d’un AppDomain déchargement, vous ne pouvez pas dépendre de l'exécution des blocs ou des finaliseurs finally. Il est donc important d’abstraire tout accès aux ressources du système d’exploitation via la classe SafeHandle plutôt que IntPtr, HandleRef ou des classes similaires. Cela permet au CLR de suivre et de fermer les handles que vous utilisez même dans le cas de destruction AppDomain. SafeHandle utilise un finaliseur critique que le CLR exécute toujours.

Le handle du système d’exploitation est stocké dans le handle sécurisé à partir du moment où il est créé jusqu’au moment où il est libéré. Il n’existe aucune fenêtre pendant laquelle une ThreadAbortException peut se produire et provoquer une fuite du handle. De plus, l’appel de code non managé effectue un décompte de références du handle, ce qui permet d’effectuer un suivi précis de la durée de vie du handle. Vous évitez ainsi de rencontrer un problème de sécurité liée à une condition de concurrence critique entre Dispose et une méthode utilisant le handle.

La plupart des classes qui ont actuellement un finaliseur pour nettoyer simplement un handle de système d’exploitation n’auront plus besoin du finaliseur. Au lieu de cela, le finaliseur est sur la classe dérivée SafeHandle.

Notez que SafeHandle ce n’est pas un remplacement pour IDisposable.Dispose. Il existe toujours des avantages potentiels en matière de contention de ressources et de performances pour supprimer explicitement les ressources du système d’exploitation. Il suffit de réaliser que les finally blocs qui suppriment explicitement les ressources peuvent ne pas s’exécuter à la fin.

SafeHandle vous permet d’implémenter votre propre ReleaseHandle méthode pour effectuer le travail de libération du handle, par exemple en passant l’état à une routine de libération de handle du système d’exploitation ou en libérant un ensemble de handles dans une boucle. Le CLR garantit l’exécution de cette méthode. Il incombe à l’auteur de l’implémentation de ReleaseHandle de garantir la libération du handle dans toutes les circonstances. Si ce n’est pas le cas,une fuite du handle se produit, souvent associée à la fuite des ressources natives qui lui sont associées. Par conséquent, il est essentiel de structurer SafeHandle des classes dérivées de telle sorte que l’implémentation ReleaseHandle ne nécessite pas l’allocation de ressources qui peuvent ne pas être disponibles au moment de l’appel. Notez qu’il est permis d’appeler des méthodes qui peuvent échouer dans l’implémentation, ReleaseHandle à condition que votre code puisse gérer ces échecs et terminer le contrat pour libérer le handle natif. À des fins de débogage, ReleaseHandle a une Boolean valeur de retour qui peut être définie false si une erreur catastrophique est rencontrée, ce qui empêche la libération de la ressource. Cela active le MDA releaseHandleFailed , s’il est activé, pour faciliter l’identification du problème. Il n’affecte le runtime d’aucune autre façon ; la méthode ReleaseHandle n’est plus appelée pour la même ressource et, en conséquence, une fuite du handle se produit.

SafeHandle n’est pas approprié dans certains contextes. Étant donné que la ReleaseHandle méthode peut être exécutée sur un GC thread finaliseur, tous les handles requis pour être libérés sur un thread particulier ne doivent pas être encapsulés dans un SafeHandle.

Les wrappers appelables au moment de l'exécution (RCWs) peuvent être nettoyés par le CLR sans code supplémentaire. Pour le code qui utilise l’appel de plateforme et traite un objet COM comme un IUnknown* ou un IntPtr, le code doit être réécrit pour utiliser un RCW. SafeHandle peut ne pas être adéquat pour ce scénario en raison de la possibilité d’un rappel dans le code managé par une méthode de libération non managée.

Règle d’analyse du code

Permet SafeHandle d’encapsuler les ressources du système d’exploitation. N'utilisez pas HandleRef ou de champs de type IntPtr.

Assurez-vous que les finaliseurs n’ont pas à s’exécuter pour empêcher la fuite des ressources du système d’exploitation

Passez en revue attentivement vos finaliseurs pour vous assurer que même s’ils ne s’exécutent pas, une ressource de système d’exploitation critique n’est pas divulguée. Contrairement à un déchargement normal AppDomain lorsque l’application s’exécute dans un état stable ou lorsqu’un serveur tel que SQL Server s’arrête, les objets ne sont pas finalisés lors d’un déchargement brusque AppDomain . Assurez-vous que les ressources ne sont pas divulguées dans le cas d’un déchargement brusque, car la correction d’une application ne peut pas être garantie, mais l’intégrité du serveur doit être maintenue en ne fuite pas de ressources. Permet SafeHandle de libérer toutes les ressources du système d’exploitation.

Assurez-vous que les clauses finally n’ont pas besoin d’être exécutées afin d'éviter la fuite des ressources du système d’exploitation

Comme rien ne garantit que les clauses finally puissent s’exécuter en dehors de régions d’exécution limitée, les développeurs de bibliothèque ne peuvent pas compter sur le code d’un bloc finally pour libérer des ressources non managées. L’utilisation SafeHandle est la solution recommandée.

Règle d’analyse du code

Permet SafeHandle de nettoyer les ressources du système d’exploitation au lieu de Finalize. N’utilisez IntPtrpas ; utilisez SafeHandle pour encapsuler les ressources. Si la clause finally doit être exécutée, placez-la dans une région d’exécution limitée.

Tous les verrous doivent passer par le code de verrouillage managé existant

Le CLR doit savoir quand le code est dans un verrou afin de savoir démanteler le AppDomain plutôt que d'interrompre simplement le thread. L’abandon du thread peut être dangereux, car les données exploitées par le thread peuvent être laissées dans un état incohérent. Par conséquent, l’ensemble AppDomain doit être recyclé. Les conséquences de l’échec de l’identification d’un verrou peuvent être des interblocages ou des résultats incorrects. Utilisez les méthodes BeginCriticalRegion et EndCriticalRegion identifiez les régions de verrouillage. Il s’agit de méthodes statiques sur la Thread classe qui s’appliquent uniquement au thread actuel, ce qui permet d’empêcher un thread de modifier le nombre de verrous d’un autre thread.

Enter et Exit ont cette notification CLR intégrée, il est donc recommandé de les utiliser, ainsi que l’utilisation de l’instruction lock, qui utilise ces méthodes.

D'autres mécanismes de verrouillage, tels que les verrous de rotation et AutoResetEvent, doivent appeler ces méthodes pour informer le CLR qu'une section critique est en train d'être entrée. Ces méthodes ne prennent pas de verrous ; ils informent le CLR que le code s’exécute dans une section critique et que l’abandon du thread peut laisser l’état partagé incohérent. Si vous avez défini votre propre type de verrou, tel qu’une classe personnalisée ReaderWriterLock , utilisez ces méthodes de nombre de verrous.

Règle d’analyse du code

Marquez et identifiez tous les verrous à l’aide BeginCriticalRegion et EndCriticalRegion. N’utilisez CompareExchangepas , Incrementet Decrement dans une boucle. N’effectuez pas d’appel de plateforme aux variantes Win32 de ces méthodes. N’utilisez Sleep pas dans une boucle. N’utilisez pas de champs volatiles.

Le code de nettoyage doit être dans un bloc finally ou catch, mais ne doit pas suivre un bloc catch

Le code de nettoyage ne doit jamais suivre un catch bloc ; il doit se trouver dans un finally bloc ou dans le catch bloc lui-même. Il devrait s’agir d’une bonne pratique normale. Un finally bloc est généralement préféré, car il exécute le même code à la fois lorsqu’une exception est levée et lorsque la fin du try bloc est normalement rencontrée. En cas de levée inattendue d’une exception, par exemple, le ThreadAbortExceptioncode de nettoyage ne s’exécutera pas. Toutes les ressources non managées que vous nettoyez dans un finally doit idéalement être encapsulée dans un SafeHandle pour empêcher les fuites. Notez que le mot clé C# using peut être utilisé efficacement pour supprimer des objets, y compris des handles.

Bien que AppDomain le recyclage puisse nettoyer les ressources sur le thread finaliseur, il est toujours important de placer le code de nettoyage dans le bon endroit. Notez que si un thread reçoit une exception asynchrone sans contenir de verrou, le CLR tente de mettre fin au thread lui-même sans avoir à recycler le AppDomain. S’assurer que les ressources sont nettoyées plus tôt que plus tard aide à rendre davantage de ressources disponibles, et en gérant mieux la durée de vie. Si vous ne fermez pas explicitement un handle d’un fichier dans un chemin de code d’erreur quelconque puis attendez que le finaliseur SafeHandle le nettoie, à la prochaine exécution de votre code, il est possible que sa tentative d’accès au même fichier échoue si le finaliseur ne s’est pas encore exécuté. Pour cette raison, s’assurer que le code de nettoyage existe et fonctionne correctement aidera à récupérer des défaillances de manière plus propre et rapide, même s’il n’est pas strictement nécessaire.

Règle d’analyse du code

Le code de nettoyage après catch doit être dans un finally bloc. Placez les appels à supprimer dans un bloc finally. Les blocs catch doivent se terminer dans une clause throw ou rethrow. Bien qu’il y ait des exceptions, telles que le code qui détecte si une connexion réseau peut être établie, où vous pouvez obtenir un grand nombre d’exceptions, tout code nécessitant l’interception d’un certain nombre d’exceptions dans des circonstances normales doit indiquer que le code doit être testé pour voir s’il réussit.

Process-Wide L'état mutable partagé entre les domaines d’application doit être éliminé ou utilisé dans une région d’exécution contrainte

Comme décrit dans l’introduction, il peut être très difficile d’écrire du code managé qui surveille l’état partagé à l’échelle du processus entre les domaines d’application de manière fiable. L’état partagé au niveau du processus représente une structure de données quelconque partagée entre des domaines d’application, dans du code Win32, à l’intérieur du CLR ou dans du code managé à l’aide de la communication à distance. Tout état partagé mutable est très difficile à écrire correctement dans le code managé, et tout état partagé statique peut être effectué uniquement avec grand soin. Si vous avez un état partagé à l’échelle du processus ou à l’échelle de l’ordinateur, trouvez un moyen de l’éliminer ou de protéger l’état partagé à l’aide d’une région d’exécution contrainte (CER). Notez qu’une bibliothèque avec un état partagé qui n’est pas identifiée et corrigée peut provoquer la défaillance d’un hôte, tel que SQL Server, qui exige un déchargement propre de AppDomain.

Si le code utilise un objet COM, évitez de partager cet objet COM entre les domaines d’application.

Les verrous ne fonctionnent pas à l’échelle du processus ou entre les domaines d’application.

Auparavant, on utilisait Enter et l’instruction lock pour créer des verrous de processus globaux. Cela se produit, par exemple, au moment d’un verrouillage sur des classes agiles AppDomain, telles que des instances de Type provenant d’assemblys non partagés, des objets Thread, des chaînes internées et certaines chaînes partagées entre des domaines d’application à l’aide de la communication à distance. Ces verrous n'affectent plus l'ensemble du processus. Pour identifier la présence d’un verrou de domaine interapplication à l’échelle du processus, déterminez si le code au sein du verrou utilise une ressource externe persistante, telle qu’un fichier sur le disque ou éventuellement une base de données.

Notez que l'utilisation d’un verrou dans un AppDomain peut entraîner des problèmes si le code protégé utilise une ressource externe, car ce code peut s’exécuter simultanément sur plusieurs domaines applicatifs. Il peut s’agir d’un problème lors de l’écriture dans un fichier journal ou de la liaison à un socket pour l’ensemble du processus. Ces modifications signifient qu’il n’existe aucun moyen simple, à l’aide du code managé, d’obtenir un verrou global de processus, autre qu’utiliser une instance nommée Mutex ou Semaphore. Créez du code qui ne s'exécute pas simultanément dans deux domaines d'application, ou utilisez les classes Mutex ou Semaphore. Si le code existant ne peut pas être modifié, n’utilisez pas de mutex nommé Win32 pour effectuer cette synchronisation, car l’exécution en mode fibre signifie que vous ne pouvez pas garantir que le même thread de système d’exploitation acquiert et libère un mutex. Vous devez utiliser la classe managée Mutex, un nommé ManualResetEvent, AutoResetEvent ou Semaphore pour synchroniser le verrou de code d'une manière que le CLR reconnaisse au lieu de synchroniser le verrou à l’aide de code non managé.

Éviter le verrouillage(typeof(MyType))

Les objets privés et publics Type dans les assemblys partagés avec une seule copie du code partagé entre tous les domaines d’application présentent également des problèmes. Pour les assemblys partagés, il n’existe qu’une seule instance d’un Type processus, ce qui signifie que plusieurs domaines d’application partagent exactement la même Type instance. Prendre un verrou sur une instance Type affecte tout le processus, pas seulement le AppDomain. Si un AppDomain thread prend un verrou sur un Type objet puis est interrompu brusquement, il ne libérera pas le verrou. Ce verrou peut alors entraîner l’interblocage d’autres domaines d’application.

Une bonne façon de prendre des verrous dans des méthodes statiques implique l’ajout d’un objet de synchronisation interne statique au code. Cela peut être initialisé dans le constructeur de classe s’il est présent, mais sinon, il peut être initialisé comme ceci :

private static Object s_InternalSyncObject;
private static Object InternalSyncObject
{
    get
    {
        if (s_InternalSyncObject == null)
        {
            Object o = new Object();
            Interlocked.CompareExchange(
                ref s_InternalSyncObject, o, null);
        }
        return s_InternalSyncObject;
    }
}

Ensuite, lors de la prise d’un verrou, utilisez la InternalSyncObject propriété pour obtenir un objet sur lequel verrouiller. Vous n’avez pas besoin d’utiliser la propriété si vous avez initialisé l’objet de synchronisation interne dans votre constructeur de classe. Le code d’initialisation du verrouillage de vérification double doit ressembler à cet exemple :

public static MyClass SingletonProperty
{
    get
    {
        if (s_SingletonProperty == null)
        {
            lock(InternalSyncObject)
            {
                // Do not use lock(typeof(MyClass))
                if (s_SingletonProperty == null)
                {
                    MyClass tmp = new MyClass(…);
                    // Do all initialization before publishing
                    s_SingletonProperty = tmp;
                }
            }
        }
        return s_SingletonProperty;
    }
}

Remarque à propos de lock(this)

Il est généralement acceptable de prendre un verrou sur un objet individuel accessible publiquement. Toutefois, si l’objet est un objet singleton qui peut provoquer un blocage de sous-système entier, envisagez également d’utiliser le modèle de conception ci-dessus. Par exemple, un verrou sur l'objet SecurityManager pourrait provoquer un interblocage au sein de AppDomain, rendant l'ensemble AppDomain inutilisable. Il est recommandé de ne pas prendre de verrou sur un objet accessible publiquement de ce type. Toutefois, un verrou sur une collection ou un tableau individuel ne doit généralement pas présenter de problème.

Règle d’analyse du code

N’acquérez pas de verrous sur des types qui peuvent être utilisés entre domaines d’application ou qui ne possèdent pas une identité forte. N'appelez pas Enter sur un Type, MethodInfo, PropertyInfo, String, ValueType, Thread ou tout objet dérivé de MarshalByRefObject.

Supprimez les appels à GC.KeepAlive

Une quantité importante de code existant n’utilise KeepAlive pas quand il doit ou l’utilise lorsqu’il n’est pas approprié. Après la conversion à SafeHandle, les classes n’ont pas besoin d’appeler la méthode KeepAlive, en supposant qu’elles n’ont pas de finaliseur, mais s’appuient sur SafeHandle pour finaliser les handles du système d’exploitation. Même si le coût de performance de la conservation d’un appel à KeepAlive peut être négligeable, la perception qu’un appel à KeepAlive est nécessaire ou suffisant pour résoudre un problème de durée de vie qui pourrait ne plus exister rend le code plus difficile à gérer. Toutefois, durant l’utilisation des wrappers RCW COM Interop, KeepAlive est encore requis par le code.

Règle d’analyse du code

Supprimez KeepAlive.

Utiliser l’attribut HostProtection

Le HostProtectionAttribute (HPA) fournit l’utilisation d’actions de sécurité déclaratives pour déterminer les exigences de protection de l’hôte, ce qui permet à l’hôte d’empêcher même le code entièrement approuvé d’appeler certaines méthodes qui sont inappropriées pour l’hôte donné, comme Exit ou Show pour SQL Server.

HpA affecte uniquement les applications non managées qui hébergent le Common Language Runtime et implémentent la protection de l’hôte, comme SQL Server. En cas d’application, l’action de sécurité entraîne la création d’une demande de liaison en fonction des ressources hôtes exposées par la classe ou la méthode. Si le code est exécuté dans une application cliente ou sur un serveur qui n’est pas protégé par l’hôte, l’attribut « évapore » ; elle n’est pas détectée et n’est donc pas appliquée.

Importante

L’objectif de cet attribut est d’appliquer des instructions de modèle de programmation spécifiques à l’hôte, et non un comportement de sécurité. Bien qu’une demande de liaison soit utilisée pour vérifier la conformité aux exigences du modèle de programmation, elle HostProtectionAttribute n’est pas une autorisation de sécurité.

Si l’hôte n’a pas de configuration requise pour le modèle de programmation, les demandes de liaison ne se produisent pas.

Cet attribut identifie les éléments suivants :

  • Méthodes ou classes qui ne correspondent pas au modèle de programmation hôte, mais qui sont sinon bénignes.

  • Les méthodes ou classes qui ne correspondent pas au modèle de programmation hôte et peuvent entraîner une déstabilisation du code utilisateur géré par le serveur.

  • Les méthodes ou classes qui ne correspondent pas au modèle de programmation hôte et peuvent entraîner une déstabilisation du processus serveur lui-même.

Remarque

Si vous créez une bibliothèque de classes qui doit être appelée par des applications qui peuvent s’exécuter dans un environnement protégé par l’hôte, vous devez appliquer cet attribut aux membres qui exposent HostProtectionResource des catégories de ressources. Les membres de la bibliothèque de classes .NET Framework avec cet attribut entraînent uniquement la vérification de l’appelant immédiat. Votre membre de bibliothèque doit également demander une vérification de son appelant immédiat de la même manière.

Veuillez trouver plus d’informations sur HPA dans HostProtectionAttribute.

Règle d’analyse du code

Pour SQL Server, toutes les méthodes utilisées pour introduire la synchronisation ou le fil d'exécution doivent être identifiées avec HPA. Cela inclut les méthodes qui partagent l’état, sont synchronisées ou gèrent des processus externes. Les HostProtectionResource valeurs qui affectent SQL Server sont SharedState, Synchronizationet ExternalProcessMgmt. Toutefois, toute méthode qui expose n’importe quelle HostProtectionResource méthode doit être identifiée par un HPA, pas seulement celles qui utilisent des ressources affectant SQL.

Ne pas bloquer indéfiniment dans le code non managé

Le blocage dans le code non managé au lieu du code managé peut entraîner une attaque par déni de service, car le CLR n’est pas en mesure d’abandonner le thread. Un thread bloqué empêche le CLR de décharger le AppDomain, au moins sans effectuer d’opérations extrêmement dangereuses. Le blocage à l’aide d’une primitive de synchronisation Windows est un exemple clair d’un élément que nous ne pouvons pas autoriser. Le blocage lors d'un appel à ReadFile sur un socket doit être évité si possible — idéalement, l'API Windows devrait fournir un mécanisme permettant qu'une telle opération se termine automatiquement.

Toute méthode qui appelle en mode natif doit idéalement utiliser un appel Win32 avec un délai d’expiration raisonnable et fini. Si l’utilisateur est autorisé à spécifier le délai d’expiration, l’utilisateur ne doit pas être autorisé à spécifier un délai d’attente infini sans autorisation de sécurité spécifique. À titre de consigne, si une méthode bloque pendant plus de 10 secondes, vous devez utiliser une version qui prend en charge les délais d’expiration ou vous avez besoin d'un support CLR supplémentaire.

Voici quelques exemples d’API problématiques. Les canaux (à la fois anonymes et nommés) peuvent être créés avec un délai d’attente ; toutefois, le code doit vérifier qu’il n’appelle jamais CreateNamedPipe ni WaitNamedPipe avec NMPWAIT_WAIT_FOREVER. En outre, il peut y avoir un blocage inattendu même si un délai d’expiration est spécifié. L’appel WriteFile sur un canal anonyme bloque jusqu’à ce que tous les octets soient écrits, ce qui signifie que la mémoire tampon contient des données non lues, l’appel WriteFile bloque jusqu’à ce que le lecteur ait libéré de l’espace dans la mémoire tampon du canal. Les sockets doivent toujours utiliser une API qui respecte un mécanisme de délai d’expiration.

Règle d’analyse du code

Le blocage sans délai d’expiration dans le code non managé est une attaque par déni de service. N’effectuez pas d’appels d’invocation de plateforme à WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjects, et MsgWaitForMultipleObjectsEx. N’utilisez pas NMPWAIT_WAIT_FOREVER.

Identifier les fonctionnalités de STA-Dependent

Identifiez le code qui utilise les threads cloisonnés (STA) de COM. Les STAs sont désactivés dans le processus de SQL Server. Les fonctionnalités qui dépendent CoInitialize, telles que les compteurs de performances ou le Presse-papiers, doivent être désactivées dans SQL Server.

Assurez-vous que les finaliseurs ne présentent pas de problèmes de synchronisation

Plusieurs threads finaliseurs peuvent exister dans les versions futures du .NET Framework, ce qui signifie que les finaliseurs pour différentes instances du même type s’exécutent simultanément. Ils ne doivent pas être complètement thread-safe ; le récupérateur de mémoire garantit qu’un seul thread à la fois exécute le finaliseur pour une instance d’objet donnée. Toutefois, les finaliseurs doivent être codés pour éviter les conditions de concurrence et les blocages lors de l’exécution simultanément sur plusieurs instances d’objet différentes. Pendant l’utilisation d’un état externe, par exemple l’écriture dans un fichier journal, dans un finaliseur, les problèmes de threading doivent être gérés. Ne vous fiez pas à la finalisation pour assurer la sécurité des threads. N’utilisez pas le stockage local de thread, géré ou natif, pour stocker l’état sur le thread finaliseur.

Règle d’analyse du code

Les finaliseurs doivent être exempts de problèmes de synchronisation. N’utilisez pas d’état mutable statique dans un finaliseur.

Éviter la mémoire non managée si possible

Il peut y avoir des fuites de mémoire non managée, comme pour un handle de système d’exploitation. Si possible, essayez d’utiliser la mémoire sur la pile à l’aide de stackalloc ou d’un objet managé épinglé (par exemple, avec l’instruction fixed) ou un GCHandle utilisant un tableau d’octets (byte[]). GC finit par nettoyer ceux-ci. Toutefois, si vous devez allouer de la mémoire non managée, envisagez d’utiliser une classe qui dérive de SafeHandle pour encapsuler l’allocation de mémoire.

Notez qu’il existe au moins un cas où SafeHandle n’est pas adéquat. Pour les appels de méthode COM qui allouent ou libèrent de la mémoire, il est courant qu’une DLL alloue de la mémoire via CoTaskMemAlloc une autre DLL libère cette mémoire avec CoTaskMemFree. L'utilisation de SafeHandle dans ces emplacements serait inappropriée, car elle tenterait de lier la durée de vie de la mémoire non managée à la durée de vie du SafeHandle au lieu de permettre à l'autre DLL de contrôler la durée de vie de la mémoire.

Examinez toutes les utilisations de catch(Exception)

Les blocs catch qui interceptent toutes les exceptions au lieu d’une exception spécifique interceptent désormais également les exceptions asynchrones. Examinez chaque bloc catch(Exception), en recherchant une libération de ressource peu importante ou du code réécrit qui peut être ignoré, ainsi qu’un comportement éventuellement incorrect dans le bloc catch lui-même pour gérer un ThreadAbortException, StackOverflowException ou OutOfMemoryException. Notez qu'il est possible que ce code soit en train de journaliser ou de faire des hypothèses selon lesquelles il ne peut voir que certaines exceptions, ou que chaque fois qu'une exception se produit, elle échoue pour une raison bien précise. Ces hypothèses peuvent avoir besoin d’être mises à jour pour inclure ThreadAbortException.

Envisagez de changer tous les emplacements qui interceptent toutes les exceptions pour qu’ils n’interceptent plus qu’un type spécifique d’exception dont vous prévoyez la levée, par exemple une exception FormatException provenant des méthodes de mise en forme de chaînes. Cela empêche l’exécution du bloc catch sur des exceptions inattendues et permet de s’assurer que le code ne masque pas les bogues en interceptant des exceptions inattendues. En règle générale, vous ne gérez jamais une exception dans le code de la bibliothèque (le code qui vous oblige à intercepter une exception peut indiquer un défaut de conception dans le code que vous appelez). Dans certains cas, vous pouvez intercepter une exception et lever un autre type d’exception pour fournir davantage de données. Utilisez des exceptions imbriquées dans ce cas, stockant la cause réelle de l’échec dans la InnerException propriété de la nouvelle exception.

Règle d’analyse du code

Passez en revue tous les blocs catch dans le code managé qui interceptent tous les objets ou interceptent toutes les exceptions. En C#, cela signifie marquer les deux catch{} et catch(Exception){}. Envisagez de rendre le type d’exception très spécifique ou de passer en revue le code pour s’assurer qu’il n’agit pas de manière incorrecte s’il intercepte un type d’exception inattendu.

Ne supposez pas qu’un thread managé est un thread Win32 : il s’agit d’une fibre

L’utilisation du stockage local de threads managés fonctionne, mais vous ne devez pas utiliser de stockage local de threads non managé ni supposer que le code s’exécutera à nouveau sur le thread actuel du système d’exploitation. Ne modifiez pas les paramètres comme les paramètres régionaux du thread. N’appelez pas InitializeCriticalSection ou CreateMutex via un appel de code non managé, car ils exigent que le thread du système d’exploitation qui est verrouillé puisse aussi être déverrouillé. Comme cela ne sera pas le cas lors de l’utilisation de fibres, les sections critiques Win32 et les mutex ne peuvent pas être utilisés directement dans SQL. Notez que la classe managée Mutex ne gère pas ces problèmes d’affinité de thread.

Vous pouvez utiliser sans risque la plupart des éléments d’état sur un objet Thread managé, y compris le stockage local des threads managés et la culture actuelle de l’interface utilisateur du thread. Vous pouvez également utiliser le ThreadStaticAttributeparamètre , qui rend la valeur d’une variable statique existante accessible uniquement par le thread managé actuel (il s’agit d’une autre façon d’effectuer un stockage local fibre dans le CLR). Pour des raisons de modèle de programmation, vous ne pouvez pas modifier la culture actuelle d’un thread lors de l’exécution dans SQL.

Règle d’analyse du code

SQL Server s’exécute en mode fibre ; n’utilisez pas le stockage local de thread. Évitez les appels de code non managé à TlsAlloc, TlsFree, TlsGetValue et TlsSetValue.

Laissez SQL Server gérer l’emprunt d’identité

Étant donné que l’emprunt d’identité fonctionne au niveau du thread et que SQL peut s’exécuter en mode fibre, le code managé ne doit pas emprunter l’identité des utilisateurs et ne doit pas appeler RevertToSelf.

Règle d’analyse du code

Laissez SQL Server gérer l’emprunt d’identité. N’utilisez pas RevertToSelf, ImpersonateAnonymousToken, DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, ImpersonateSelf, RpcImpersonateClient, RpcRevertToSelf, RpcRevertToSelfEx ou SetThreadToken.

N’appelez pas Thread::Suspend

La possibilité de suspendre un thread peut apparaître une opération simple, mais elle peut entraîner des interblocages. Si un thread contenant un verrou est suspendu par un deuxième thread, puis que le deuxième thread tente de prendre le même verrou, un interblocage se produit. Suspend peut interférer avec la sécurité, le chargement de classe, la communication à distance et la réflexion.

Règle d’analyse du code

N’appelez pas Suspend. Envisagez plutôt d’utiliser une primitive de synchronisation réelle, telle qu’une Semaphore ou ManualResetEvent .

Protéger les opérations critiques avec des régions d’exécution limitées et des contrats de fiabilité

Lors de l’exécution d’une opération complexe qui met à jour un état partagé ou qui doit réussir complètement ou échouer complètement, assurez-vous qu’elle est protégée par une région d’exécution contrainte (CER). Cela garantit que le code s’exécute dans tous les cas, même en cas d'interruption brutale de thread ou de déchargement brutal AppDomain.

Un CER est un bloc particulier try/finally immédiatement précédé d’un appel à PrepareConstrainedRegions.

Cette action indique au compilateur juste-à-temps de préparer tout le code dans le bloc finally avant d’exécuter le bloc try. Cela garantit que le code du bloc final est généré et s’exécute dans tous les cas. Il n’est pas rare dans un CER d’avoir un bloc vide try . L’utilisation d’une région d’exécution limitée protège des abandons de threads asynchrones et des exceptions de mémoire insuffisante. Consultez ExecuteCodeWithGuaranteedCleanup pour obtenir une forme de région d’exécution limitée qui gère en plus des dépassements de la capacité de la pile pour du code très profond.

Voir aussi