C # captura una excepción de desbordamiento de pila

115

Tengo una llamada recursiva a un método que arroja una excepción de desbordamiento de pila. La primera llamada está rodeada por un bloque try catch pero no se detecta la excepción.

¿La excepción de desbordamiento de pila se comporta de una manera especial? ¿Puedo detectar / manejar la excepción correctamente?

No estoy seguro si es relevante, pero hay información adicional:

  • la excepción no se lanza en el hilo principal

  • el objeto donde el código está lanzando la excepción es cargado manualmente por Assembly.LoadFrom (...). CreateInstance (...)

Toto
fuente
3
@RichardOD, seguro que solucioné el error porque era un error. Sin embargo, el problema puede aparecer de una manera diferente y quiero manejarlo
Toto
7
De acuerdo, un desbordamiento de pila es un error grave que no se puede detectar porque no debería detectarse. En su lugar, arregle el código roto.
Ian Kemp
11
@RichardOD: Si uno quiere diseñar, por ejemplo, un analizador sintáctico de descenso recursivo y no imponer límites artificiales a la profundidad más allá de los realmente requeridos por la máquina host, ¿cómo debería hacerlo? Si tuviera mis druthers, habría una excepción StackCritical que podría detectarse explícitamente, que se dispararía mientras aún quedaba un poco de espacio en la pila; se inhabilitaría a sí mismo hasta que realmente se lanzara, y luego no podría ser capturado hasta que quedara una cantidad segura de espacio en la pila.
supercat
2
Esta pregunta es útil: quiero fallar una prueba unitaria si se produce una excepción de desbordamiento de pila, pero NUnit simplemente mueve la prueba a la categoría "ignorada" en lugar de fallar como lo haría con otras excepciones; necesito detectarla y haz una Assert.Failen su lugar. Muy en serio, ¿cómo lo hacemos?
BrainSlugs83

Respuestas:

109

A partir de 2.0, una excepción de StackOverflow solo se puede detectar en las siguientes circunstancias.

  1. El CLR se ejecuta en un entorno alojado * donde el host permite específicamente que se manejen las excepciones de StackOverflow
  2. La excepción de desbordamiento de pila es lanzada por el código de usuario y no debido a una situación de desbordamiento de pila real ( Referencia )

* "entorno alojado" como en "mi código aloja CLR y configuro las opciones de CLR" y no "mi código se ejecuta en alojamiento compartido"

JaredPar
fuente
27
Si no se puede capturar en ningún escenario relevante, ¿por qué existe el objeto StackoverflowException?
Manu
9
@Manu por al menos un par de razones. 1) Es que podría ser atrapado, en cierto modo, en 1.1 y, por lo tanto, tenía un propósito. 2) Todavía se puede detectar si está alojando el CLR, por lo que sigue siendo un tipo de excepción válido
JaredPar
3
Si no se puede detectar ... ¿Por qué el evento de Windows que explica lo sucedido no incluye el seguimiento de la pila completa de forma predeterminada?
10
¿Cómo se puede permitir que StackOverflowExceptions se maneje en un entorno alojado? La razón por la que pregunto es porque ejecuto un entorno alojado y tengo este problema exacto, donde destruye todo el grupo de aplicaciones. Preferiría que abortara el hilo, donde se puede desenrollar hasta la parte superior, y luego puedo registrar el error y continuar sin que se eliminen todos los hilos de la agrupación de aplicaciones.
Brain2000
Starting with 2.0 ..., Tengo curiosidad, ¿qué les impide captar SO y cómo fue posible 1.1(lo mencionaste en tu comentario)?
M.kazem Akhgary
47

La forma correcta es arreglar el desbordamiento, pero ...

Puedes darte una pila más grande: -

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

Puede usar la propiedad System.Diagnostics.StackTrace FrameCount para contar los marcos que ha usado y lanzar su propia excepción cuando se alcanza un límite de marcos.

O puede calcular el tamaño de la pila restante y lanzar su propia excepción cuando cae por debajo de un umbral: -

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();
    }
}

Solo coge el queso. ;)


fuente
47
Cheeseestá lejos de ser específico. Yo iría porthrow new CheeseException("Gouda");
C.Evenhuis
13
@ C.Evenhuis Si bien no hay duda de que Gouda es un queso excepcional, debería ser una RollingCheeseException ("Double Gloucester"). Ver cheese-rolling.co.uk
3
lol, 1) la reparación no es posible porque sin detectarlo, a menudo no sabes dónde sucede 2) aumentar el tamaño de la pila es inútil con una recursión interminable ym 3) comprobar la pila en la ubicación correcta es como la primera vez
Firo
2
pero soy intolerante a la lactosa
redoc
39

Desde la página de MSDN en StackOverflowException s:

En versiones anteriores de .NET Framework, su aplicación podía detectar un objeto StackOverflowException (por ejemplo, para recuperarse de una recursividad ilimitada). Sin embargo, esa práctica se desaconseja actualmente porque se requiere un código adicional significativo para detectar de manera confiable una excepción de desbordamiento de pila y continuar la ejecución del programa.

A partir de la versión 2.0 de .NET Framework, un bloque try-catch no puede capturar un objeto StackOverflowException y el proceso correspondiente finaliza de forma predeterminada. En consecuencia, se recomienda a los usuarios que escriban su código para detectar y evitar un desbordamiento de pila. Por ejemplo, si su aplicación depende de la recursividad, utilice un contador o una condición de estado para terminar el ciclo recursivo. Tenga en cuenta que una aplicación que aloja Common Language Runtime (CLR) puede especificar que CLR descargue el dominio de la aplicación donde se produce la excepción de desbordamiento de pila y deje que continúe el proceso correspondiente. Para obtener más información, consulte Interfaz ICLRPolicyManager y Alojamiento de Common Language Runtime.

Damien_The_Unbeliever
fuente
23

Como ya han dicho varios usuarios, no se puede detectar la excepción. Sin embargo, si tiene dificultades para averiguar dónde está sucediendo, es posible que desee configurar Visual Studio para que se rompa cuando se lance.

Para hacer eso, debe abrir Configuración de excepción desde el menú 'Depurar'. En versiones anteriores de Visual Studio, esto se encuentra en 'Depurar' - 'Excepciones'; en las versiones más recientes, está en 'Depurar' - 'Windows' - 'Configuración de excepciones'.

Una vez que tenga la configuración abierta, expanda 'Common Language Runtime Exceptions', expanda 'System', desplácese hacia abajo y marque 'System.StackOverflowException'. Luego, puede mirar la pila de llamadas y buscar el patrón repetido de llamadas. Eso debería darle una idea de dónde buscar para corregir el código que está causando el desbordamiento de la pila.

Simón
fuente
1
¿Dónde está la depuración - Excepciones en VS 2015?
FrenkyB
1
Depuración - Windows - Configuración de excepciones
Simon
15

Como se mencionó anteriormente varias veces, no es posible detectar una StackOverflowException que el sistema generó debido a un estado de proceso dañado. Pero hay una forma de notar la excepción como un evento:

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

A partir de .NET Framework versión 4, este evento no se genera para excepciones que corrompen el estado del proceso, como desbordamientos de pila o violaciones de acceso, a menos que el controlador de eventos sea crítico para la seguridad y tenga el atributo HandleProcessCorruptedStateExceptionsAttribute.

Sin embargo, su aplicación terminará después de salir de la función de evento (una solución MUY sucia fue reiniciar la aplicación dentro de este evento, jaja, no lo he hecho y nunca lo hará). ¡Pero es lo suficientemente bueno para registrar!

En las versiones 1.0 y 1.1 de .NET Framework, una excepción no controlada que ocurre en un subproceso que no es el subproceso principal de la aplicación es detectada por el tiempo de ejecución y, por lo tanto, no hace que la aplicación finalice. Por tanto, es posible que se genere el evento UnhandledException sin que la aplicación finalice. A partir de .NET Framework versión 2.0, se eliminó este respaldo para excepciones no controladas en subprocesos secundarios, porque el efecto acumulativo de tales fallas silenciosas incluía degradación del rendimiento, datos corruptos y bloqueos, todos los cuales eran difíciles de depurar. Para obtener más información, incluida una lista de casos en los que el tiempo de ejecución no termina, consulte Excepciones en subprocesos administrados.

FooBarTheLittle
fuente
6

Sí, desde el desbordamiento de pila CLR 2.0 se considera una situación no recuperable. Entonces, el tiempo de ejecución aún cierra el proceso.

Para obtener más información, consulte la documentación http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Brian Rasmussen
fuente
Desde CLR 2.0 a StackOverflowExceptiontermina el proceso por defecto.
Brian Rasmussen
No. Puede detectar OOM y, en algunos casos, podría tener sentido hacerlo. No sé a qué te refieres con la desaparición del hilo. Si un hilo tiene una excepción no controlada, CLR terminará el proceso. Si su hilo completa su método, se limpiará.
Brian Rasmussen
5

No puedes. El CLR no te dejará. Un desbordamiento de pila es un error fatal y no se puede recuperar.

Matthew Scharley
fuente
Entonces, ¿cómo se hace que una prueba unitaria falle para esta excepción si, en lugar de ser detectable, bloquea el corredor de la prueba unitaria?
BrainSlugs83
1
@ BrainSlugs83. No lo haces, porque es una idea tonta. ¿Por qué está probando si su código falla con una StackOverflowException de todos modos? ¿Qué sucede si el CLR cambia para que pueda manejar una pila más profunda? ¿Qué sucede si llama a su función de prueba de unidad en algún lugar que ya tiene una pila profundamente anidada? Parece algo que no se puede probar. Si está intentando lanzarlo manualmente, elija una mejor excepción para la tarea.
Matthew Scharley
5

No puedes, como explican la mayoría de las publicaciones, déjame agregar otra área:

En muchos sitios web, encontrará personas que dicen que la forma de evitar esto es utilizando un AppDomain diferente, por lo que si esto sucede, el dominio se descargará. Eso es absolutamente incorrecto (a menos que aloje su CLR) ya que el comportamiento predeterminado del CLR generará un evento KillProcess, reduciendo su AppDomain predeterminado.

No hay problema de heno
fuente
3

Es imposible, y por una buena razón (por un lado, piense en todas las capturas (Excepción) {} alrededor).

Si desea continuar con la ejecución después del desbordamiento de la pila, ejecute código peligroso en un AppDomain diferente. Las políticas CLR se pueden configurar para terminar el AppDomain actual en caso de desbordamiento sin afectar el dominio original.

ima
fuente
2
Las declaraciones "catch" no serían realmente un problema, ya que para cuando una declaración catch pudiera ejecutarse, el sistema habría revertido los efectos de cualquier cosa que hubiera intentado usar dos espacios de pila. No hay razón para que la captura de excepciones de desbordamiento de pila tenga que ser peligrosa. La razón por la que no se pueden detectar tales excepciones es que permitir que se atrapen de manera segura requeriría agregar una sobrecarga adicional a todo el código que usa la pila, incluso si no se desborda.
supercat
4
En algún momento la afirmación no está bien pensada. Si no puede detectar el Stackoverflow, tal vez nunca sepa DÓNDE sucedió en un entorno de producción.
Offler