C # intercepte une exception de débordement de pile

116

J'ai un appel récursif à une méthode qui lève une exception de débordement de pile. Le premier appel est entouré d'un bloc try catch mais l'exception n'est pas interceptée.

L'exception de débordement de pile se comporte-t-elle d'une manière spéciale? Puis-je intercepter / gérer l'exception correctement?

Je ne sais pas si cela est pertinent, mais des informations supplémentaires:

  • l'exception n'est pas lancée dans le thread principal

  • l'objet dans lequel le code lève l'exception est chargé manuellement par Assembly.LoadFrom (...). CreateInstance (...)

Toto
la source
3
@RichardOD, bien sûr que je corrige le bogue parce que c'était un bogue. Cependant, le problème peut apparaître d'une manière différente et je veux le gérer
Toto
7
D'accord, un débordement de pile est une erreur grave qui ne peut pas être détectée car elle ne doit pas l' être. Corrigez le code cassé à la place.
Ian Kemp
11
@RichardOD: Si l'on veut concevoir par exemple un analyseur de descente récursive et ne pas imposer de limites artificielles de profondeur au-delà de celles réellement requises par la machine hôte, comment s'y prendre? Si j'avais mes druthers, il y aurait une exception StackCritical qui pourrait être explicitement interceptée, qui serait déclenchée alors qu'il restait encore un peu d'espace de pile; il se désactiverait jusqu'à ce qu'il soit réellement lancé, et ne pourrait alors pas être attrapé jusqu'à ce qu'il reste une quantité sûre d'espace de pile.
supercat du
3
Cette question est utile - je veux échouer un test unitaire si une exception de dépassement de capacité de pile se produit - mais NUnit déplace simplement le test dans la catégorie "ignoré" au lieu de l'échouer comme il le ferait avec d'autres exceptions - je dois l'attraper et faites un à la Assert.Failplace. Si sérieusement - comment y parvenir?
BrainSlugs83

Réponses:

109

À partir de 2.0, une exception StackOverflow ne peut être interceptée que dans les circonstances suivantes.

  1. Le CLR est exécuté dans un environnement hébergé * où l'hôte permet spécifiquement la gestion des exceptions StackOverflow
  2. L'exception de stackoverflow est levée par le code utilisateur et non en raison d'une situation réelle de dépassement de capacité de la pile ( référence )

* "environnement hébergé" comme dans "mon code héberge CLR et je configure les options de CLR" et non "mon code fonctionne sur un hébergement partagé"

JaredPar
la source
28
S'il ne peut être intercepté dans aucun scebario pertinent, pourquoi l'objet StackoverflowException existe-t-il?
Manu
9
@Manu pour au moins deux raisons. 1) Est-ce qu'il pouvait être capturé, en quelque sorte, dans 1.1 et avait donc un but. 2) Il peut toujours être intercepté si vous hébergez le CLR, il s'agit donc toujours d'un type d'exception valide
JaredPar
3
S'il ne peut pas être intercepté ... Pourquoi l'événement windows expliquant ce qui s'est passé n'inclut-il pas la trace complète de la pile par défaut?
11
Comment peut-on permettre à StackOverflowExceptions d'être géré dans un environnement hébergé? La raison pour laquelle je demande est que j'exécute un environnement hébergé et que j'ai exactement ce problème, où il détruit l'ensemble du pool d'applications. Je préférerais de beaucoup qu'il abandonne le thread, où il peut se dérouler vers le haut, et je peux ensuite enregistrer l'erreur et continuer sans que tous les threads de l'apppool soient tués.
Brain2000
Starting with 2.0 ..., Je suis curieux, qu'est-ce qui les empêche d'attraper SO et comment cela a-t-il été possible 1.1(vous l'avez mentionné dans votre commentaire)?
M.kazem Akhgary
47

La bonne façon est de corriger le débordement, mais ...

Vous pouvez vous donner un plus gros stack: -

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

Vous pouvez utiliser la propriété System.Diagnostics.StackTrace FrameCount pour compter les images que vous avez utilisées et lever votre propre exception lorsqu'une limite d'images est atteinte.

Ou, vous pouvez calculer la taille de la pile restante et lancer votre propre exception lorsqu'elle tombe en dessous d'un seuil: -

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

Attrapez juste le fromage. ;)


la source
47
Cheeseest loin d'être spécifique. J'irais pourthrow new CheeseException("Gouda");
C.Evenhuis
13
@ C.Evenhuis Bien qu'il ne fasse aucun doute que le Gouda est un fromage exceptionnel, il devrait être une RollingCheeseException ("Double Gloucester") vraiment voir cheese-rolling.co.uk
3
lol, 1) la correction n'est pas possible car sans l'attraper, vous ne savez souvent pas où cela se passe 2) augmenter la taille de la pile est inutile avec une récursion sans fin et m 3) vérifier la pile au bon endroit est comme le premier
Firo
2
mais je suis intolérant au lactose
redoc
39

À partir de la page MSDN sur StackOverflowException s:

Dans les versions antérieures du .NET Framework, votre application pouvait intercepter un objet StackOverflowException (par exemple, pour récupérer d'une récursivité illimitée). Cependant, cette pratique est actuellement déconseillée car un code supplémentaire important est nécessaire pour intercepter de manière fiable une exception de débordement de pile et poursuivre l'exécution du programme.

À partir du .NET Framework version 2.0, un objet StackOverflowException ne peut pas être intercepté par un bloc try-catch et le processus correspondant est arrêté par défaut. Par conséquent, il est conseillé aux utilisateurs d'écrire leur code pour détecter et empêcher un débordement de pile. Par exemple, si votre application dépend de la récursivité, utilisez un compteur ou une condition d'état pour terminer la boucle récursive. Notez qu'une application qui héberge le Common Language Runtime (CLR) peut spécifier que le CLR décharge le domaine d'application où l'exception de débordement de pile se produit et laisse le processus correspondant se poursuivre. Pour plus d'informations, consultez ICLRPolicyManager Interface et Hosting the Common Language Runtime.

Damien_The_Unbeliever
la source
23

Comme plusieurs utilisateurs l'ont déjà dit, vous ne pouvez pas attraper l'exception. Cependant, si vous avez du mal à savoir où cela se passe, vous pouvez configurer Visual Studio pour qu'il s'arrête lorsqu'il est lancé.

Pour ce faire, vous devez ouvrir les paramètres d'exception dans le menu «Déboguer». Dans les anciennes versions de Visual Studio, il s'agit de «Debug» - «Exceptions»; dans les versions plus récentes, il se trouve dans «Déboguer» - «Windows» - «Paramètres d'exception».

Une fois que vous avez ouvert les paramètres, développez 'Common Language Runtime Exceptions', développez 'System', faites défiler vers le bas et cochez 'System.StackOverflowException'. Ensuite, vous pouvez consulter la pile d'appels et rechercher le modèle de répétition des appels. Cela devrait vous donner une idée de l'endroit où chercher pour corriger le code à l'origine du débordement de pile.

Simon
la source
1
Où est Debug - Exceptions dans VS 2015?
FrenkyB
1
Débogage - Windows - Paramètres d'exception
Simon
15

Comme mentionné ci-dessus à plusieurs reprises, il n'est pas possible d'attraper une StackOverflowException qui a été déclenchée par le système en raison d'un état de processus corrompu. Mais il existe un moyen de remarquer l'exception en tant qu'événement:

http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception.aspx

À partir de .NET Framework version 4, cet événement n'est pas déclenché pour les exceptions qui endommagent l'état du processus, telles que les débordements de pile ou les violations d'accès, sauf si le gestionnaire d'événements est critique pour la sécurité et possède l'attribut HandleProcessCorruptedStateExceptionsAttribute.

Néanmoins, votre application se terminera après avoir quitté la fonction événementielle (une solution de contournement TRÈS sale, était de redémarrer l'application dans cet événement haha, je ne l'ai pas fait et ne le fera jamais). Mais c'est assez bon pour la journalisation!

Dans les versions 1.0 et 1.1 du .NET Framework, une exception non gérée qui se produit dans un thread autre que le thread d'application principal est interceptée par le runtime et n'entraîne donc pas l'arrêt de l'application. Ainsi, il est possible que l'événement UnhandledException soit déclenché sans l'arrêt de l'application. À partir de .NET Framework version 2.0, ce backstop pour les exceptions non gérées dans les threads enfants a été supprimé, car l'effet cumulatif de ces échecs silencieux comprenait une dégradation des performances, des données corrompues et des blocages, tous difficiles à déboguer. Pour plus d'informations, y compris une liste des cas dans lesquels le runtime ne s'arrête pas, consultez Exceptions dans les threads gérés.

FooBarTheLittle
la source
6

Oui à partir du débordement de pile CLR 2.0 est considéré comme une situation non récupérable. Ainsi, le runtime a toujours arrêté le processus.

Pour plus de détails, consultez la documentation http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Brian Rasmussen
la source
À partir de CLR 2.0, a StackOverflowExceptiontermine le processus par défaut.
Brian Rasmussen
Non. Vous pouvez intercepter MOO et dans certains cas, il peut être judicieux de le faire. Je ne sais pas ce que vous entendez par disparition du fil. Si un thread a une exception non gérée, le CLR mettra fin au processus. Si votre thread termine sa méthode, il sera nettoyé.
Brian Rasmussen le
5

Vous ne pouvez pas. Le CLR ne vous laissera pas. Un débordement de pile est une erreur fatale et ne peut pas être récupéré.

Matthew Scharley
la source
Alors, comment faire échouer un test unitaire pour cette exception si au lieu d'être capturable, il plante le lanceur de test unitaire à la place?
BrainSlugs83
1
@ BrainSlugs83. Vous ne le faites pas, car c'est une idée idiote. Pourquoi testez-vous si votre code échoue avec une exception StackOverflowException de toute façon? Que se passe-t-il si le CLR change pour pouvoir gérer une pile plus profonde? Que se passe-t-il si vous appelez votre fonction testée unitaire quelque part qui a déjà une pile profondément imbriquée? Cela semble être quelque chose qui ne peut pas être testé. Si vous essayez de le lancer manuellement, choisissez une meilleure exception pour la tâche.
Matthew Scharley
5

Vous ne pouvez pas, comme la plupart des articles l'expliquent, laissez-moi ajouter un autre domaine:

Sur de nombreux sites Web, vous trouverez des gens disant que le moyen d'éviter cela consiste à utiliser un autre AppDomain, donc si cela se produit, le domaine sera déchargé. C'est absolument faux (sauf si vous hébergez votre CLR) car le comportement par défaut du CLR déclenchera un événement KillProcess, faisant tomber votre AppDomain par défaut.

Pas de problème de foin
la source
3

C'est impossible, et pour une bonne raison (par exemple, pensez à tous ces catch (Exception) {} autour).

Si vous souhaitez continuer l'exécution après un débordement de pile, exécutez du code dangereux dans un autre AppDomain. Les stratégies CLR peuvent être définies pour mettre fin à AppDomain actuel en cas de dépassement de capacité sans affecter le domaine d'origine.

ima
la source
2
Les instructions "catch" ne seraient pas vraiment un problème, car au moment où une instruction catch pourrait s'exécuter, le système aurait annulé les effets de tout ce qui avait essayé d'utiliser deux beaucoup d'espace de pile. Il n'y a aucune raison d'attraper des exceptions de dépassement de pile devrait être dangereux. La raison pour laquelle ces exceptions ne peuvent pas être interceptées est que leur permettre d'être interceptées en toute sécurité nécessiterait d'ajouter une surcharge supplémentaire à tout le code qui utilise la pile, même s'il ne déborde pas.
supercat
4
À un moment donné, la déclaration n'est pas bien pensée. Si vous ne pouvez pas attraper le Stackoverflow, vous ne saurez peut-être jamais O cela s'est produit dans un environnement de production.
Offler