C # captura uma exceção de estouro de pilha

115

Eu tenho uma chamada recursiva para um método que lança uma exceção de estouro de pilha. A primeira chamada é cercada por um bloco try catch, mas a exceção não é detectada.

A exceção de estouro de pilha se comporta de maneira especial? Posso capturar / manipular a exceção adequadamente?

Não tenho certeza se relevante, mas informações adicionais:

  • a exceção não é lançada no tópico principal

  • o objeto onde o código está lançando a exceção é carregado manualmente por Assembly.LoadFrom (...). CreateInstance (...)

Totó
fonte
3
@RichardOD, claro que consertei o bug porque era um bug. No entanto, o problema pode aparecer de uma maneira diferente e eu quero lidar com isso
Toto
7
Concordo, um estouro de pilha é um erro sério que não pode ser detectado porque não deveria ser detectado. Em vez disso, corrija o código quebrado.
Ian Kemp,
11
@RichardOD: Se alguém deseja projetar, por exemplo, um analisador descendente recursivo e não impor limites artificiais de profundidade além daqueles realmente exigidos pela máquina host, como fazer isso? Se eu tivesse meu druthers, haveria uma exceção StackCritical que poderia ser capturada explicitamente, que seria acionada enquanto ainda houvesse um pouco de espaço restante na pilha; ele se desabilitaria até que fosse realmente lançado e não poderia ser capturado até que uma quantidade segura de espaço de pilha permanecesse.
supercat
2
Esta pergunta é útil - eu quero falhar em um teste de unidade se ocorrer uma exceção de estouro de pilha - mas o NUnit apenas move o teste para a categoria "ignorado" em vez de falhar como faria com outras exceções - eu preciso pegá-lo e faça um em Assert.Failvez disso. Então, sério - como vamos fazer isso?
BrainSlugs83

Respostas:

109

A partir do 2.0, uma exceção StackOverflow só pode ser detectada nas seguintes circunstâncias.

  1. O CLR está sendo executado em um ambiente hospedado * onde o host permite especificamente que as exceções StackOverflow sejam tratadas
  2. A exceção stackoverflow é lançada pelo código do usuário e não devido a uma situação real de estouro de pilha ( Referência )

* "ambiente hospedado" como em "meu código hospeda CLR e eu configuro as opções de CLR" e não "meu código é executado em hospedagem compartilhada"

JaredPar
fonte
27
Se ele não pode ser capturado em nenhum cenário relevante, por que existe o objeto StackoverflowException?
Manu
9
@Manu por pelo menos alguns motivos. 1) É que poderia ser capturado, mais ou menos, em 1.1 e, portanto, tinha um propósito. 2) Ele ainda pode ser detectado se você estiver hospedando o CLR, então ainda é um tipo de exceção válido
JaredPar
3
Se não puder ser detectado ... Por que o evento do Windows que explica o que aconteceu não inclui o rastreamento de pilha completo por padrão?
10
Como alguém pode permitir que StackOverflowExceptions seja manipulado em um ambiente hospedado? A razão pela qual estou perguntando é porque executo um ambiente hospedado e estou tendo exatamente esse problema, em que ele destrói todo o pool de aplicativos. Eu preferiria que ele abortasse o encadeamento, onde ele pudesse voltar ao topo, e eu pudesse registrar o erro e continuar sem ter todos os encadeamentos do apppool eliminados.
Brain2000,
Starting with 2.0 ..., Estou curioso, o que os está impedindo de pegar SO e como foi possível 1.1(você mencionou isso no seu comentário)?
M.kazem Akhgary
47

O jeito certo é consertar o estouro, mas ....

Você pode obter uma pilha maior: -

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

Você pode usar a propriedade System.Diagnostics.StackTrace FrameCount para contar os quadros usados ​​e lançar sua própria exceção quando um limite de quadro for atingido.

Ou você pode calcular o tamanho da pilha restante e lançar sua própria exceção quando cair abaixo de um limite: -

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

Basta pegar o queijo. ;)


fonte
47
Cheeseestá longe de ser específico. Eu iria parathrow new CheeseException("Gouda");
C.Evenhuis
13
@ C.Evenhuis Embora não haja dúvidas de que o Gouda é um queijo excepcional, deveria ser um RollingCheeseException ("Double Gloucester"). Veja cheese-rolling.co.uk
3
lol, 1) consertar não é possível porque sem pegá-lo você geralmente não sabe onde isso acontece 2) aumentar o tamanho da pilha é inútil com recursão infinita e 3) verificar a pilha no local certo é como o primeiro
Firo
2
mas sou intolerante à lactose
redoc
39

Na página do MSDN em StackOverflowException s:

Em versões anteriores do .NET Framework, seu aplicativo podia capturar um objeto StackOverflowException (por exemplo, para se recuperar de recursão ilimitada). No entanto, essa prática é atualmente desencorajada porque um código adicional significativo é necessário para capturar de forma confiável uma exceção de estouro de pilha e continuar a execução do programa.

A partir do .NET Framework versão 2.0, um objeto StackOverflowException não pode ser detectado por um bloco try-catch e o processo correspondente é encerrado por padrão. Consequentemente, os usuários são aconselhados a escrever seu código para detectar e evitar um estouro de pilha. Por exemplo, se seu aplicativo depende da recursão, use um contador ou uma condição de estado para encerrar o loop recursivo. Observe que um aplicativo que hospeda o common language runtime (CLR) pode especificar que o CLR descarregue o domínio do aplicativo onde ocorre a exceção de estouro de pilha e deixe o processo correspondente continuar. Para obter mais informações, consulte ICLRPolicyManager Interface e Hosting the Common Language Runtime.

Damien_The_Unbeliever
fonte
23

Como vários usuários já disseram, você não pode pegar a exceção. No entanto, se você está lutando para descobrir onde está acontecendo, você pode configurar o visual studio para quebrar quando for lançado.

Para fazer isso, você precisa abrir as Configurações de exceção no menu 'Depurar'. Em versões mais antigas do Visual Studio, isso está em 'Debug' - 'Exceptions'; nas versões mais recentes, está em 'Debug' - 'Windows' - 'Configurações de exceção'.

Depois de abrir as configurações, expanda 'Common Language Runtime Exceptions', expanda 'System', role para baixo e marque 'System.StackOverflowException'. Em seguida, você pode examinar a pilha de chamadas e procurar o padrão de repetição das chamadas. Isso deve dar uma ideia de onde procurar para consertar o código que está causando o estouro da pilha.

Simon
fonte
1
Onde está Debug - exceções no VS 2015?
FrenkyB
1
Depurar - Windows - Configurações de exceção
Simon
15

Conforme mencionado acima várias vezes, não é possível capturar uma StackOverflowException que foi gerada pelo sistema devido a um estado de processo corrompido. Mas há uma maneira de perceber a exceção como um evento:

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

A partir do .NET Framework versão 4, esse evento não é gerado para exceções que corrompem o estado do processo, como estouro de pilha ou violações de acesso, a menos que o manipulador de eventos seja crítico para a segurança e tenha o atributo HandleProcessCorruptedStateExceptionsAttribute.

No entanto, seu aplicativo será encerrado após sair da função de evento (uma solução MUITO suja, era reiniciar o aplicativo dentro deste evento haha, não fiz isso e nunca farei). Mas é bom o suficiente para registrar!

No .NET Framework versões 1.0 e 1.1, uma exceção não tratada que ocorre em um thread diferente do thread do aplicativo principal é capturada pelo tempo de execução e, portanto, não causa o encerramento do aplicativo. Portanto, é possível que o evento UnhandledException seja gerado sem que o aplicativo seja encerrado. A partir do .NET Framework versão 2.0, este backstop para exceções não tratadas em threads filho foi removido, porque o efeito cumulativo de tais falhas silenciosas incluía degradação de desempenho, dados corrompidos e travamentos, todos difíceis de depurar. Para obter mais informações, incluindo uma lista de casos em que o tempo de execução não termina, consulte Exceções em threads gerenciados.

FooBarTheLittle
fonte
6

Sim, no CLR 2.0, o estouro de pilha é considerado uma situação irrecuperável. Portanto, o tempo de execução ainda encerra o processo.

Para obter detalhes, consulte a documentação http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Brian Rasmussen
fonte
No CLR 2.0, a StackOverflowExceptionencerra o processo por padrão.
Brian Rasmussen
Não. Você pode capturar OOM e, em alguns casos, pode fazer sentido fazê-lo. Não sei o que quer dizer com desaparecimento do fio. Se um thread tiver uma exceção não tratada, o CLR encerrará o processo. Se o seu tópico completar seu método, ele será limpo.
Brian Rasmussen
5

Você não pode. O CLR não vai deixar você. Um estouro de pilha é um erro fatal e não pode ser recuperado.

Matthew Scharley
fonte
Então, como fazer um Teste de Unidade falhar para essa exceção se, em vez de ser capturável, ele travar o executor do teste de unidade?
BrainSlugs83
1
@ BrainSlugs83. Você não sabe, porque é uma ideia boba. Por que você está testando se o seu código falha com uma StackOverflowException mesmo assim? O que acontece se o CLR mudar para que possa lidar com uma pilha mais profunda? O que acontece se você chamar sua função de unidade testada em algum lugar que já tenha uma pilha profundamente aninhada? Parece algo que não pode ser testado. Se você estiver tentando lançá-lo manualmente, escolha uma exceção melhor para a tarefa.
Matthew Scharley de
5

Você não pode, como a maioria das postagens está explicando, deixe-me adicionar outra área:

Em muitos sites você encontrará pessoas dizendo que a maneira de evitar isso é usar um AppDomain diferente, portanto, se isso acontecer, o domínio será descarregado. Isso é absolutamente errado (a menos que você hospede seu CLR), pois o comportamento padrão do CLR irá gerar um evento KillProcess, derrubando seu AppDomain padrão.

Sem problema de feno
fonte
3

É impossível, e por um bom motivo (por exemplo, pense em todos aqueles catch (Exception) {} ao redor).

Se você deseja continuar a execução após o estouro da pilha, execute o código perigoso em um AppDomain diferente. As políticas CLR podem ser definidas para encerrar o AppDomain atual em estouro sem afetar o domínio original.

Eu sou um
fonte
2
As instruções "catch" não seriam realmente um problema, pois no momento em que uma instrução catch pudesse ser executada, o sistema teria revertido os efeitos de tudo o que havia tentado usar muito espaço na pilha. Não há razão para capturar exceções de estouro de pilha ser perigoso. O motivo pelo qual essas exceções não podem ser capturadas é que permitir que sejam capturadas com segurança exigiria a adição de alguma sobrecarga extra a todo o código que usa a pilha, mesmo se ela não estourar.
supercat
4
Em algum momento, a afirmação não é bem pensada. Se você não consegue capturar o Stackoverflow, talvez nunca saiba ONDE isso aconteceu em um ambiente de produção.
Offler de