C # przechwyć wyjątek przepełnienia stosu

116

Mam rekurencyjne wywołanie metody, która zgłasza wyjątek przepełnienia stosu. Pierwsze wywołanie jest otoczone blokiem try catch, ale wyjątek nie jest przechwytywany.

Czy wyjątek przepełnienia stosu zachowuje się w specjalny sposób? Czy mogę prawidłowo złapać / obsłużyć wyjątek?

Nie wiem, czy to istotne, ale dodatkowe informacje:

  • wyjątek nie jest zgłaszany w głównym wątku

  • obiekt, w którym kod rzuca wyjątek, jest ładowany ręcznie przez Assembly.LoadFrom (...). CreateInstance (...)

Toto
źródło
3
@RichardOD, na pewno naprawię błąd, ponieważ to był błąd. Jednak problem może pojawić się w inny sposób i chcę się nim zająć
Toto
7
Zgoda, przepełnienie stosu to poważny błąd, którego nie można wykryć, ponieważ nie powinno się go wykryć. Zamiast tego napraw uszkodzony kod.
Ian Kemp
11
@RichardOD: Jeśli ktoś chce zaprojektować np. Parser rekurencyjno-zstępujący i nie narzucać sztucznych ograniczeń głębokości poza te, które są faktycznie wymagane przez maszynę hosta, jak należy się do tego zabrać? Gdybym miał wolną rękę, byłby wyjątek StackCritical, który mógłby zostać jawnie przechwycony i odpalony, gdy pozostało jeszcze trochę miejsca na stos; wyłączałby się, dopóki nie zostałby faktycznie rzucony, a następnie nie można go było złapać, dopóki nie pozostanie bezpieczna ilość miejsca w stosie.
supercat
3
To pytanie jest przydatne - chcę oblać test jednostkowy, jeśli wystąpi wyjątek przepełnienia stosu - ale NUnit po prostu przenosi test do kategorii „ignorowane” zamiast kończyć się niepowodzeniem, tak jak w przypadku innych wyjątków - muszę go przechwycić i zrób Assert.Failzamiast tego. A więc poważnie - jak to zrobimy?
BrainSlugs83,

Odpowiedzi:

109

Począwszy od wersji 2,0, wyjątek StackOverflow można wykryć tylko w następujących okolicznościach.

  1. Środowisko CLR jest uruchamiane w środowisku hostowanym *, w którym host wyraźnie zezwala na obsługę wyjątków StackOverflow
  2. Wyjątek przepełnienia stosu jest generowany przez kod użytkownika, a nie z powodu rzeczywistej sytuacji przepełnienia stosu ( odniesienie )

* „środowisko hostowane” na przykład „mój kod obsługuje środowisko CLR i konfiguruję opcje środowiska CLR”, a nie „mój kod działa na hostingu współdzielonym”

JaredPar
źródło
28
Jeśli nie można go przechwycić w żadnym odpowiednim scebario, dlaczego istnieje obiekt StackoverflowException?
Manu
9
@Manu przynajmniej z kilku powodów. 1) Czy to może być złapane, w pewnym sensie, w 1.1 i stąd miało jakiś cel. 2) Nadal można go przechwycić, jeśli hostujesz CLR, więc nadal jest to prawidłowy typ wyjątku
JaredPar
3
Jeśli nie można go przechwycić ... Dlaczego zdarzenie systemu Windows wyjaśniające, co się stało, nie zawiera domyślnie pełnego śladu stosu?
11
Jak można zezwolić na obsługę wyjątków StackOverflowExceptions w środowisku hostowanym? Pytam o to, że korzystam z hostowanego środowiska i mam dokładnie ten problem, w którym niszczy on całą pulę aplikacji. Wolałbym raczej, aby przerwał wątek, gdzie może rozwinąć się z powrotem do góry, a następnie mogę zarejestrować błąd i kontynuować bez zabicia wszystkich wątków Apppool.
Brain2000
Starting with 2.0 ..., Jestem ciekaw, co powstrzymuje ich przed złapaniem SO i jak to było możliwe 1.1(wspomniałeś o tym w komentarzu)?
M.kazem Akhgary
47

Właściwy sposób to naprawić przelew, ale ....

Możesz dać sobie większy stack: -

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

Możesz użyć właściwości System.Diagnostics.StackTrace FrameCount, aby policzyć użyte klatki i zgłosić własny wyjątek po osiągnięciu limitu klatek.

Lub możesz obliczyć rozmiar pozostałego stosu i zgłosić własny wyjątek, gdy spadnie poniżej progu: -

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

Po prostu złap ser. ;)


źródło
47
Cheesenie jest konkretna. Pójdę zathrow new CheeseException("Gouda");
C.Evenhuis
13
@ C.Evenhuis Chociaż nie ma wątpliwości, że Gouda jest wyjątkowym serem, powinien to być wyjątek RollingCheeseException („Double Gloucester”), naprawdę zobacz ser-rolling.co.uk
3
lol, 1) naprawianie nie jest możliwe, ponieważ bez złapania go często nie wiesz, gdzie to się dzieje 2) zwiększanie rozmiaru stosu jest bezużyteczne przy niekończącej się rekurencji i m 3) sprawdzanie stosu we właściwej lokalizacji jest takie jak pierwsze
Firo
2
ale nie
toleruję
39

Ze strony MSDN na StackOverflowException s:

W poprzednich wersjach programu .NET Framework aplikacja mogła przechwytywać obiekt StackOverflowException (na przykład w celu odzyskania po nieograniczonej rekursji). Jednak ta praktyka jest obecnie odradzana, ponieważ do niezawodnego wychwycenia wyjątku przepełnienia stosu i kontynuowania wykonywania programu wymagany jest znaczny dodatkowy kod.

Począwszy od .NET Framework w wersji 2.0, obiekt StackOverflowException nie może zostać przechwycony przez blok try-catch, a odpowiadający mu proces jest domyślnie przerywany. W związku z tym zaleca się użytkownikom pisanie kodu w celu wykrywania i zapobiegania przepełnieniu stosu. Na przykład, jeśli aplikacja zależy od rekurencji, użyj licznika lub warunku stanu, aby zakończyć pętlę cykliczną. Należy zauważyć, że aplikacja obsługująca środowisko uruchomieniowe języka wspólnego (CLR) może określić, że środowisko CLR zwalnia domenę aplikacji, w której występuje wyjątek przepełnienia stosu, i umożliwia kontynuowanie odpowiedniego procesu. Aby uzyskać więcej informacji, zobacz Interfejs ICLRPolicyManager i Hosting the Common Language Runtime.

Damien_The_Unbeliever
źródło
23

Jak już powiedziało kilku użytkowników, nie można złapać wyjątku. Jeśli jednak starasz się dowiedzieć, gdzie to się dzieje, możesz skonfigurować Visual Studio tak, aby pękało, gdy zostanie rzucone.

Aby to zrobić, musisz otworzyć Ustawienia wyjątków z menu „Debuguj”. W starszych wersjach programu Visual Studio jest to „Debuguj” - „Wyjątki”; w nowszych wersjach znajduje się w „Debuguj” - „Windows” - „Ustawienia wyjątków”.

Po otwarciu ustawień rozwiń „Wyjątki środowiska uruchomieniowego języka wspólnego”, rozwiń „System”, przewiń w dół i zaznacz „System.StackOverflowException”. Następnie możesz spojrzeć na stos wywołań i poszukać powtarzającego się wzorca wywołań. To powinno dać ci wyobrażenie o tym, gdzie szukać, aby naprawić kod, który powoduje przepełnienie stosu.

Szymon
źródło
1
Gdzie jest debugowanie - wyjątki w VS 2015?
FrenkyB
1
Debugowanie - Windows - Ustawienia wyjątków
Simon
15

Jak wspomniano powyżej kilka razy, nie jest możliwe przechwycenie wyjątku StackOverflowException, który został zgłoszony przez system z powodu uszkodzonego stanu procesu. Ale jest sposób, aby zauważyć wyjątek jako wydarzenie:

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

Począwszy od .NET Framework w wersji 4, to zdarzenie nie jest zgłaszane w przypadku wyjątków, które uszkadzają stan procesu, takich jak przepełnienia stosu lub naruszenia zasad dostępu, chyba że program obsługi zdarzeń jest krytyczny dla bezpieczeństwa i ma atrybut HandleProcessCorruptedStateExceptionsAttribute.

Niemniej jednak Twoja aplikacja zakończy działanie po wyjściu z funkcji zdarzenia (BARDZO brudnym obejściem było ponowne uruchomienie aplikacji w ramach tego zdarzenia, haha, nie zrobiłem tego i nigdy tego nie zrobisz). Ale wystarczy do logowania!

W programie .NET Framework w wersjach 1.0 i 1.1 nieobsługiwany wyjątek występujący w wątku innym niż główny wątek aplikacji jest przechwytywany przez środowisko wykonawcze i dlatego nie powoduje zatrzymania aplikacji. W związku z tym zdarzenie UnhandledException może zostać zgłoszone bez przerywania aplikacji. Począwszy od .NET Framework w wersji 2.0, ten mechanizm ochronny dla nieobsługiwanych wyjątków w wątkach podrzędnych został usunięty, ponieważ skumulowany efekt takich cichych awarii obejmował obniżenie wydajności, uszkodzone dane i zawieszanie się, z których wszystkie były trudne do debugowania. Aby uzyskać więcej informacji, w tym listę przypadków, w których środowisko wykonawcze nie kończy się, zobacz Wyjątki w zarządzanych wątkach.

FooBarTheLittle
źródło
6

Tak, przepełnienie stosu CLR 2.0 jest uważane za sytuację, której nie można odzyskać. Tak więc środowisko wykonawcze nadal zamyka proces.

Szczegółowe informacje można znaleźć w dokumentacji http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Brian Rasmussen
źródło
Od środowiska CLR 2.0 a StackOverflowExceptiondomyślnie kończy proces.
Brian Rasmussen
Nie. Możesz złapać OOM iw niektórych przypadkach może to mieć sens. Nie wiem, co masz na myśli mówiąc o znikaniu nici. Jeśli wątek ma nieobsługiwany wyjątek, środowisko CLR zakończy proces. Jeśli twój wątek zakończy swoją metodę, zostanie wyczyszczony.
Brian Rasmussen
5

Nie możesz. CLR ci nie pozwoli. Przepełnienie stosu jest błędem krytycznym i nie można go naprawić.

Matthew Scharley
źródło
Jak więc sprawić, by test jednostkowy zakończył się niepowodzeniem dla tego wyjątku, jeśli zamiast być wykrywalnym, powoduje awarię modułu uruchamiającego testy jednostkowe?
BrainSlugs83,
1
@ BrainSlugs83. Nie, bo to głupi pomysł. Dlaczego przeprowadzasz testy, jeśli Twój kod i tak kończy się niepowodzeniem z wyjątkiem StackOverflowException? Co się stanie, jeśli środowisko CLR zmieni się, aby obsłużyć głębszy stos? Co się stanie, jeśli wywołasz funkcję testowaną przez jednostkę w miejscu, które ma już głęboko zagnieżdżony stos? Wygląda na to, że nie można tego przetestować. Jeśli próbujesz rzucić go ręcznie, wybierz lepszy wyjątek dla zadania.
Matthew Scharley,
5

Nie możesz, jak wyjaśnia większość postów, pozwól mi dodać kolejny obszar:

Na wielu stronach internetowych można znaleźć ludzi, którzy mówią, że sposobem na uniknięcie tego jest użycie innej domeny AppDomain, więc jeśli tak się stanie, domena zostanie rozładowana. Jest to absolutnie błędne (chyba że hostujesz swoje CLR), ponieważ domyślne zachowanie CLR spowoduje zgłoszenie zdarzenia KillProcess, obniżając domyślną AppDomain.

Nie ma problemu
źródło
3

To niemożliwe i nie bez powodu (po pierwsze, pomyśl o tych wszystkich złapanych (wyjątkach) {} wokół).

Jeśli chcesz kontynuować wykonywanie po przepełnieniu stosu, uruchom niebezpieczny kod w innej domenie aplikacji. Zasady CLR można ustawić tak, aby przerywały bieżącą domenę AppDomain w przypadku przepełnienia bez wpływu na oryginalną domenę.

ima
źródło
2
Instrukcje „catch” nie byłyby tak naprawdę problemem, ponieważ do czasu wykonania instrukcji catch system cofnąłby skutki tego, co próbowało wykorzystać dwa razy więcej miejsca na stosie. Nie ma powodu, dla którego łapanie wyjątków przepełnienia stosu musiałoby być niebezpieczne. Powodem, dla którego nie można wychwycić takich wyjątków, jest to, że umożliwienie ich bezpiecznego przechwycenia wymagałoby dodania dodatkowego obciążenia do całego kodu, który używa stosu, nawet jeśli nie przepełnia.
supercat
4
W pewnym momencie stwierdzenie nie jest dobrze przemyślane. Jeśli nie możesz złapać Stackoverflow, być może nigdy nie wiesz, GDZIE wydarzyło się to w środowisku produkcyjnym.
Offler