Как предотвратить и / или обработать исключение StackOverflowException?

Я хотел бы либо предотвратить, либо обработать StackOverflowException, который я получаю от вызова метода XslCompiledTransform.Transform в Xsl Editor, который я пишу. Проблема, похоже, в том, что пользователь может написать Xsl script, который является бесконечно рекурсивным, и он просто взрывается при вызове метода Transform. (То есть проблема заключается не только в типичной программной ошибке, которая обычно является причиной такого исключения.)

Есть ли способ определить и / или ограничить допустимое количество рекурсий? Или какие-то другие идеи, чтобы этот код не взорвался на мне?


person JohnnyM    schedule 15.10.2008    source источник
comment
Награда, мягко говоря, проблемная. См. meta.stackoverflow.com/questions/296486/ для обсуждения.   -  person forsvarir    schedule 06.06.2015
comment
Существует тонкая разница между перехватом исключения StackOverflowException, которое генерирует ваш код, и исключения, генерируемого средой выполнения. Совершенно нормально справиться с переполнением стека, которое вы вызываете. Однако обработка версии во время выполнения сильно отличается.   -  person tripleee    schedule 11.06.2015


Ответы (10)


От Microsoft:

Начиная с .NET Framework версии 2.0, объект StackOverflowException не может быть перехвачен блоком try-catch, и соответствующий процесс завершается по умолчанию. Следовательно, пользователям рекомендуется писать свой код для обнаружения и предотвращения переполнения стека. Например, если ваше приложение зависит от рекурсии, используйте счетчик или условие состояния для завершения рекурсивного цикла.

Я предполагаю, что исключение происходит внутри внутреннего метода .NET, а не в вашем коде.

Вы можете сделать пару вещей.

  • Напишите код, который проверяет xsl на бесконечную рекурсию и уведомляет пользователя перед применением преобразования (тьфу).
  • Загрузите код XslTransform в отдельный процесс (Hacky, но меньше работы).

Вы можете использовать класс Process для загрузки сборки, которая применит преобразование в отдельный процесс и предупредит пользователя о сбое, если он умирает, без остановки вашего основного приложения.

РЕДАКТИРОВАТЬ: Я только что протестировал, вот как это сделать:

MainProcess:

// This is just an example, obviously you'll want to pass args to this.
Process p1 = new Process();
p1.StartInfo.FileName = "ApplyTransform.exe";
p1.StartInfo.UseShellExecute = false;
p1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

p1.Start();
p1.WaitForExit();

if (p1.ExitCode == 1)    
   Console.WriteLine("StackOverflow was thrown");

Процесс ApplyTransform:

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        throw new StackOverflowException();
    }

    // We trap this, we can't save the process, 
    // but we can prevent the "ILLEGAL OPERATION" window 
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.IsTerminating)
        {
            Environment.Exit(1);
        }
    }
}
person FlySwat    schedule 15.10.2008
comment
Сначала я не понял, что вы имели в виду, но после того, как я добавил рекурсивный цикл в свой тестовый код, я понял, что вы имеете в виду. Вы не можете перехватить UnhandledException в этом случае ... Однако разделение его на отдельный процесс предотвратит смерть приложения, сделав это лишь незначительным неудобством для пользователя. - person JaredPar; 16.10.2008
comment
Похоже, что в моем случае проще всего пройти через это, пока я не найду его, что я и сделал. Совсем не удовлетворительно, но вот оно. В любом случае, это лучшая попытка ответить на мой вопрос; отсюда и награда. - person FlySwat; 16.10.2008

ПРИМЕЧАНИЕ. Вопрос в награде @WilliamJockusch отличается от исходного вопроса.

Этот ответ касается StackOverflow в общем случае сторонних библиотек и того, что вы можете / не можете с ними делать. Если вы ищете особый случай с XslTransform, см. Принятый ответ.


Переполнение стека происходит из-за того, что данные в стеке превышают определенный предел (в байтах). Подробную информацию о том, как работает это обнаружение, можно найти здесь.

Мне интересно, есть ли общий способ отследить StackOverflowExceptions. Другими словами, предположим, что где-то в моем коде есть бесконечная рекурсия, но я понятия не имею, где. Я хочу отследить это каким-нибудь способом, который проще, чем проходить через код повсюду, пока я не увижу, как это происходит. Меня не волнует, насколько это хакерское.

Как я упоминал в ссылке, обнаружение переполнения стека из статического анализа кода потребует решения проблемы остановки, которая является неразрешимой. Теперь, когда мы установили, что серебряной пули не существует, я могу показать вам несколько уловок, которые, как мне кажется, помогают отследить проблему.

Я думаю, что этот вопрос можно интерпретировать по-разному, и, поскольку мне немного скучно :-), я разобью его на разные варианты.

Обнаружение переполнения стека в тестовой среде

В основном проблема здесь в том, что у вас есть (ограниченная) тестовая среда и вы хотите обнаружить переполнение стека в (расширенной) производственной среде.

Вместо того, чтобы определять сам SO, я решаю эту проблему, используя тот факт, что глубина стека может быть установлена. Отладчик предоставит вам всю необходимую информацию. Большинство языков позволяют указать размер стека или максимальную глубину рекурсии.

В основном я пытаюсь заставить SO сделать глубину стека как можно меньшей. Если он не переполняется, я всегда могу сделать его больше (= в данном случае более безопасным) для производственной среды. В тот момент, когда вы получаете переполнение стека, вы можете вручную решить, является ли он «действительным» или нет.

Для этого передайте размер стека (в нашем случае небольшое значение) в параметр Thread и посмотрите, что произойдет. Размер стека по умолчанию в .NET составляет 1 МБ, мы будем использовать гораздо меньшее значение:

class StackOverflowDetector
{
    static int Recur()
    {
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        int depth = 1 + Recur();
    }

    static void Main(string[] args)
    {
        Thread t = new Thread(Start, 1);
        t.Start();
        t.Join();
        Console.WriteLine();
        Console.ReadLine();
    }
}

Примечание. Мы также будем использовать этот код ниже.

Как только он переполнится, вы можете установить для него большее значение, пока не получите SO, который имеет смысл.

Создание исключений перед вами SO

StackOverflowException не ловится. Это означает, что вы мало что можете сделать, когда это произойдет. Итак, если вы считаете, что в вашем коде что-то обязательно пойдет не так, в некоторых случаях вы можете сделать собственное исключение. Единственное, что вам нужно для этого, - это текущая глубина стека; нет необходимости в счетчике, вы можете использовать реальные значения из .NET:

class StackOverflowDetector
{
    static void CheckStackDepth()
    {
        if (new StackTrace().FrameCount > 10) // some arbitrary limit
        {
            throw new StackOverflowException("Bad thread.");
        }
    }

    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
        Console.WriteLine();
        Console.ReadLine();
    }
}

Обратите внимание, что этот подход также работает, если вы имеете дело со сторонними компонентами, которые используют механизм обратного вызова. Единственное, что требуется, - это возможность перехватывать некоторые вызовы в трассировке стека.

Обнаружение в отдельной ветке

Вы явно предложили это, так что вот это.

Вы можете попробовать обнаружить SO в отдельном потоке ... но это, вероятно, не принесет вам никакой пользы. Переполнение стека может произойти быстро даже до переключения контекста. Это означает, что этот механизм совсем ненадежен ... Я бы не рекомендовал его использовать. Хотя сборка была интересной, так что вот код :-)

class StackOverflowDetector
{
    static int Recur()
    {
        Thread.Sleep(1); // simulate that we're actually doing something :-)
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
    }

    static void Main(string[] args)
    {
        // Prepare the execution thread
        Thread t = new Thread(Start);
        t.Priority = ThreadPriority.Lowest;

        // Create the watch thread
        Thread watcher = new Thread(Watcher);
        watcher.Priority = ThreadPriority.Highest;
        watcher.Start(t);

        // Start the execution thread
        t.Start();
        t.Join();

        watcher.Abort();
        Console.WriteLine();
        Console.ReadLine();
    }

    private static void Watcher(object o)
    {
        Thread towatch = (Thread)o;

        while (true)
        {
            if (towatch.ThreadState == System.Threading.ThreadState.Running)
            {
                towatch.Suspend();
                var frames = new System.Diagnostics.StackTrace(towatch, false);
                if (frames.FrameCount > 20)
                {
                    towatch.Resume();
                    towatch.Abort("Bad bad thread!");
                }
                else
                {
                    towatch.Resume();
                }
            }
        }
    }
}

Запустите это в отладчике и получайте удовольствие от того, что происходит.

Использование характеристик переполнения стека

Другая интерпретация вашего вопроса: «Где те фрагменты кода, которые потенциально могут вызвать исключение переполнения стека?». Очевидно, ответ таков: весь код с рекурсией. Затем вы можете провести ручной анализ каждого фрагмента кода.

Это также можно определить с помощью статического анализа кода. Для этого вам нужно декомпилировать все методы и выяснить, содержат ли они бесконечную рекурсию. Вот код, который сделает это за вас:

// A simple decompiler that extracts all method tokens (that is: call, callvirt, newobj in IL)
internal class Decompiler
{
    private Decompiler() { }

    static Decompiler()
    {
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];
        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static MethodBase[] Decompile(MethodBase mi, byte[] ildata)
    {
        HashSet<MethodBase> result = new HashSet<MethodBase>();

        Module module = mi.Module;

        int position = 0;
        while (position < ildata.Length)
        {
            OpCode code = OpCodes.Nop;

            ushort b = ildata[position++];
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = ildata[position++];
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            switch (code.OperandType)
            {
                case OperandType.InlineNone:
                    break;
                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    position += 1;
                    break;
                case OperandType.InlineVar:
                    position += 2;
                    break;
                case OperandType.InlineBrTarget:
                case OperandType.InlineField:
                case OperandType.InlineI:
                case OperandType.InlineSig:
                case OperandType.InlineString:
                case OperandType.InlineTok:
                case OperandType.InlineType:
                case OperandType.ShortInlineR:
                    position += 4;
                    break;
                case OperandType.InlineR:
                case OperandType.InlineI8:
                    position += 8;
                    break;
                case OperandType.InlineSwitch:
                    int count = BitConverter.ToInt32(ildata, position);
                    position += count * 4 + 4;
                    break;

                case OperandType.InlineMethod:
                    int methodId = BitConverter.ToInt32(ildata, position);
                    position += 4;
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes));
                        }
                        else
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()));
                        }
                    }
                    catch { } 
                    break;


                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }
        }
        return result.ToArray();
    }
}

class StackOverflowDetector
{
    // This method will be found:
    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        RecursionDetector();
        Console.WriteLine();
        Console.ReadLine();
    }

    static void RecursionDetector()
    {
        // First decompile all methods in the assembly:
        Dictionary<MethodBase, MethodBase[]> calling = new Dictionary<MethodBase, MethodBase[]>();
        var assembly = typeof(StackOverflowDetector).Assembly;

        foreach (var type in assembly.GetTypes())
        {
            foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).OfType<MethodBase>())
            {
                var body = member.GetMethodBody();
                if (body!=null)
                {
                    var bytes = body.GetILAsByteArray();
                    if (bytes != null)
                    {
                        // Store all the calls of this method:
                        var calls = Decompiler.Decompile(member, bytes);
                        calling[member] = calls;
                    }
                }
            }
        }

        // Check every method:
        foreach (var method in calling.Keys)
        {
            // If method A -> ... -> method A, we have a possible infinite recursion
            CheckRecursion(method, calling, new HashSet<MethodBase>());
        }
    }

Теперь тот факт, что цикл метода содержит рекурсию, ни в коем случае не является гарантией того, что произойдет переполнение стека - это просто наиболее вероятное предварительное условие для исключения вашего переполнения стека. Короче говоря, это означает, что этот код будет определять фрагменты кода, в которых может произойти переполнение стека, что должно значительно сузить большую часть кода.

Другие подходы

Вы можете попробовать и другие подходы, которые я здесь не описал.

  1. Обработка переполнения стека путем размещения и обработки процесса CLR. Учтите, что вы все равно не можете его «поймать».
  2. Изменение всего кода IL, создание другой DLL, добавление проверок рекурсии. Да, это вполне возможно (я уже реализовал это раньше :-); это просто сложно и требует большого количества кода, чтобы сделать это правильно.
  3. Используйте API профилирования .NET для захвата всех вызовов методов и использования этого для определения переполнения стека. Например, вы можете реализовать проверки: если вы встретите один и тот же метод X раз в своем дереве вызовов, вы подадите сигнал. здесь есть проект, который даст вам фору.
person atlaste    schedule 06.06.2015

Я бы предложил создать оболочку вокруг объекта XmlWriter, чтобы он подсчитывал количество вызовов WriteStartElement / WriteEndElement, и если вы ограничите количество тегов некоторым числом (например, 100), вы сможете создать другое исключение, например - InvalidOperation.

Это должно решить проблему в большинстве случаев.

public class LimitedDepthXmlWriter : XmlWriter
{
    private readonly XmlWriter _innerWriter;
    private readonly int _maxDepth;
    private int _depth;

    public LimitedDepthXmlWriter(XmlWriter innerWriter): this(innerWriter, 100)
    {
    }

    public LimitedDepthXmlWriter(XmlWriter innerWriter, int maxDepth)
    {
        _maxDepth = maxDepth;
        _innerWriter = innerWriter;
    }

    public override void Close()
    {
        _innerWriter.Close();
    }

    public override void Flush()
    {
        _innerWriter.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return _innerWriter.LookupPrefix(ns);
    }

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        _innerWriter.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        _innerWriter.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        _innerWriter.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        _innerWriter.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        _innerWriter.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        _innerWriter.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        _innerWriter.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        _innerWriter.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        _depth--;

        _innerWriter.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        _innerWriter.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        _innerWriter.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        _innerWriter.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(string data)
    {
        _innerWriter.WriteRaw(data);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        _innerWriter.WriteRaw(buffer, index, count);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        _innerWriter.WriteStartAttribute(prefix, localName, ns);
    }

    public override void WriteStartDocument(bool standalone)
    {
        _innerWriter.WriteStartDocument(standalone);
    }

    public override void WriteStartDocument()
    {
        _innerWriter.WriteStartDocument();
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        if (_depth++ > _maxDepth) ThrowException();

        _innerWriter.WriteStartElement(prefix, localName, ns);
    }

    public override WriteState WriteState
    {
        get { return _innerWriter.WriteState; }
    }

    public override void WriteString(string text)
    {
        _innerWriter.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        _innerWriter.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        _innerWriter.WriteWhitespace(ws);
    }

    private void ThrowException()
    {
        throw new InvalidOperationException(string.Format("Result xml has more than {0} nested tags. It is possible that xslt transformation contains an endless recursive call.", _maxDepth));
    }
}
person Dmitry Dzygin    schedule 22.01.2010

Этот ответ предназначен для @WilliamJockusch.

Мне интересно, есть ли общий способ отследить StackOverflowExceptions. Другими словами, предположим, что где-то в моем коде есть бесконечная рекурсия, но я понятия не имею, где. Я хочу отследить это каким-нибудь способом, который проще, чем проходить через код повсюду, пока я не увижу, как это происходит. Меня не волнует, насколько это хакерское. Например, было бы здорово иметь модуль, который я мог бы активировать, возможно, даже из другого потока, который опрашивал бы глубину стека и жаловался, если бы он достиг уровня, который я считал «слишком высоким». Например, я мог бы установить «слишком высоко» на 600 кадров, полагая, что если стек будет слишком глубоким, это должно быть проблемой. Возможно ли что-то подобное. Другой пример - записывать каждый 1000-й вызов метода в моем коде в выходные данные отладки. Шансы на то, что это приведет к некоторым свидетельствам превышения, были бы довольно хорошими, и это, вероятно, не слишком сильно взорвало бы выпуск. Ключевым моментом является то, что он не может включать в себя выписку чека, где бы ни происходило переполнение. Потому что вся проблема в том, что я не знаю, где это. Желательно, чтобы решение не зависело от того, как выглядит моя среда разработки; то есть не следует предполагать, что я использую C # через определенный набор инструментов (например, VS).

Похоже, вы хотите услышать некоторые методы отладки, чтобы поймать этот StackOverflow, поэтому я подумал, что поделюсь парой, чтобы вы попробовали.

2. Аспектно-ориентированное программирование.

Плюсы: дампы памяти - верный способ устранить причину переполнения стека. AC # MVP и я работали вместе над устранением неполадок SO, и он продолжил блог об этом здесь.

Этот метод - самый быстрый способ найти проблему.

Этот метод не требует, чтобы вы воспроизводили проблемы, следуя шагам, указанным в журналах.

Минусы: дампы памяти очень большие, и вы должны присоединить AdPlus / procdump к процессу.

3. Ведение журнала активности пользователей.

Pro's: это, вероятно, самый простой способ реализовать код, который проверяет размер стека вызовов из любого метода без написания кода в каждом методе вашего приложения. Существует множество фреймворков AOP, которые позволяют вам перехватывать до и после вызовов.

Расскажет вам о методах, вызывающих переполнение стека.

Позволяет проверять StackTrace().FrameCount при входе и выходе всех методов в вашем приложении.

Минусы: это повлияет на производительность - хуки встроены в IL для каждого метода, и вы не можете "деактивировать" его.

Это в некоторой степени зависит от набора инструментов вашей среды разработки.

Если ваше приложение зависит от стороннего кода (в Xsl-скриптах), вы должны сначала решить, хотите ли вы защищаться от ошибок в них или нет. Если вы действительно хотите защищаться, я думаю, вам следует выполнить свою логику, которая подвержена внешним ошибкам, в отдельных доменах приложений. Перехватывать исключение StackOverflowException нецелесообразно.

Неделю назад я пытался выследить несколько трудно воспроизводимых проблем. Я разместил это User Activity Logging, Telemetry (and Variables). в глобальных обработчиках исключений). Я пришел к выводу, что это действительно простой регистратор действий пользователя, позволяющий увидеть, как воспроизводить проблемы в отладчике при возникновении любого необработанного исключения.

Pro's: вы можете включить или выключить его по желанию (т. е. подписавшись на события).

Отслеживание действий пользователя не требует перехвата всех методов.

Вы можете подсчитать количество подписанных методов событий гораздо проще, чем с помощью AOP.

Файлы журнала относительно малы, и в них основное внимание уделяется действиям, которые необходимо выполнить для воспроизведения проблемы.

Это может помочь вам понять, как пользователи используют ваше приложение.

Минусы: не подходит для службы Windows , и я уверен, что для веб-приложений есть инструменты получше.

Не обязательно сообщает вам методы, вызывающие переполнение стека.

Требует, чтобы вы просматривали журналы вручную, воспроизводя проблемы, а не дамп памяти, где вы можете получить его и сразу отладить.

Возможно, вы могли бы попробовать все методы, которые я упомянул выше, и некоторые из них, опубликованные @atlaste, и рассказать нам, какой из них, по вашему мнению, был самым простым / самым быстрым / самым грязным / наиболее приемлемым для работы в среде PROD / и т. Д.


В любом случае удачи в отслеживании этого ТАК.

1. Дампы памяти.

person Jeremy Thompson    schedule 06.06.2015
comment
Запуск кода в новом домене приложений не предотвратит прекращение всего процесса исключением StackOverflowException. Такое исключение не может быть обнаружено в .NET начиная с версии 2 и выше. - person William Jockusch; 12.06.2015

Также проверьте этот вопрос.

В .NET 4.0 вы можете добавить атрибут _1_ из System.Runtime.ExceptionServices к методу, содержащему блок try / catch. Это действительно сработало! Может быть, не рекомендуется, но работает.

person Shrike    schedule 01.09.2010
comment
При управлении размещением среды выполнения .NET можно изменить обработку StackOverflowException, чтобы выгружать только AppDomain и не останавливать процесс (что и происходит по умолчанию). - person Abel; 29.11.2013
comment
HandleProcessCorruptedStateExceptions не позволяет обрабатывать StackOverflowException. - person Govert; 07.09.2016

У меня сегодня был stackoverflow, я прочитал некоторые из ваших сообщений и решил помочь сборщику мусора.

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.ExceptionServices;

namespace ExceptionCatching
{
    public class Test
    {
        public void StackOverflow()
        {
            StackOverflow();
        }

        public void CustomException()
        {
            throw new Exception();
        }

        public unsafe void AccessViolation()
        {
            byte b = *(byte*)(8762765876);
        }
    }

    class Program
    {
        [HandleProcessCorruptedStateExceptions]
        static void Main(string[] args)
        {
            Test test = new Test();
            try {
                //test.StackOverflow();
                test.AccessViolation();
                //test.CustomException();
            }
            catch
            {
                Console.WriteLine("Caught.");
            }

            Console.WriteLine("End of program");

        }

    }      
}
person jdehaan    schedule 25.08.2010
comment
Это хорошее решение для написания метода, который требует много места в стеке (байтовый массив выделяется в стеке) таким образом, чтобы он очищал стек после каждого вызова. Однако это не сборщик мусора, а раскрутка стека (после выхода из Go-метода), которая заставляет стековую память снова становиться доступной. GC применяется только для кучи памяти. - person Brian Rasmussen; 27.10.2010

Раньше у меня был почти бесконечный цикл вроде этого:

Вместо этого позвольте ресурсу выйти из области видимости следующим образом:

    class Foo
    {
        public Foo()
        {
            Go();
        }

        public void Go()
        {
            for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
            {
                byte[] b = new byte[1]; // Causes stackoverflow
            }
        }
    }

Это сработало для меня, надеюсь, это кому-то поможет.

class Foo
{
    public Foo()
    {
        GoHelper();
    }

    public void GoHelper()
    {
        for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
        {
            Go();
        }
    }

    public void Go()
    {
        byte[] b = new byte[1]; // Will get cleaned by GC
    }   // right now
}

@WilliamJockusch, если я правильно понял вашу озабоченность, невозможно (с математической точки зрения) всегда идентифицировать бесконечную рекурсию, поскольку это означало бы решить Проблема с остановкой. Чтобы решить эту проблему, вам понадобится суперрекурсивный алгоритм (например, предикаты проб и ошибок, например) или машина, которая может гиперкомпьютер (пример объяснен в следующий раздел - доступен в качестве предварительного просмотра - эту книгу).

person Fixation    schedule 09.07.2013
comment
Я решил проголосовать против этого ответа по ряду причин. Во-первых, это не вызывает переполнения стека. Вы размещаете в куче байтовый массив, который просто собирается. Во-вторых, в случае первого кода компилятор достаточно умен, чтобы понять, что вы не используете _1_ и что у него нет побочных эффектов, которые нужно удалить. Я почти уверен, что второй код будет подхвачен инлайнером, потому что это настолько тривиальное тело, что создаст точно такой же код. В-третьих, область действия первого присваивания - это содержимое цикла '{' .. '}'. На самом деле, единственное, что делает код, - это бесконечный цикл. - person Abel; 29.11.2013
comment
В некоторой степени несвязанная, но хорошая практика программирования для бесконечного цикла - использовать b, а не какую-то неясную реализацию цикла _3_ на основе _2_. - person atlaste; 10.06.2015
comment
Я это понимаю. Но с практической точки зрения, если стек переполняется на n кадрах, должна быть возможность сломаться, когда стек достигает (скажем) n-10 кадров. - person Nick Mertin; 11.06.2015

С практической точки зрения вам нужно знать:

Имейте в виду, что на современных машинах эти данные чрезвычайно изменчивы из-за многозадачности, и я не слышал о программном обеспечении, которое выполняет эту задачу.

  • Сколько памяти стека потребуется вашему рекурсивному методу в данный момент для конкретного вывода.
  • Судя по всему, кроме запуска другого процесса, похоже, нет никакого способа обработать _1_. Прежде чем кто-нибудь спросит, я пробовал использовать _2_, но это не сработало:

Сообщите мне, если что-то неясно.

Сколько памяти стека у вас осталось в данный момент

person Gentian Kasa    schedule 12.06.2015
comment
Да, конечно. Имея ограниченный объем памяти, вы можете это сделать. Как только у вас есть данные памяти (достаточно знать, сколько памяти / кадров осталось), вы можете остановиться или продолжить, в зависимости от случая. Что касается реализации (очевидно), есть много других деталей, которые следует учитывать, но можно узнать, когда стек почти заполнен. - person William Jockusch; 12.06.2015
comment
На практике вы также можете приблизить решение, если у вас нет доступа к стеку, а есть доступ только к коду, но это другое дело. - person Gentian Kasa; 13.06.2015
comment
Как он может это сделать из XSLT? Это исключение вызывает XslCompiledTransform. - person Gentian Kasa; 13.06.2015

Однако, если вы все же в конечном итоге используете решение с раздельным процессом, я бы порекомендовал использовать Process.Exited и Process.StandardOutput и самостоятельно обрабатывать ошибки, чтобы предоставить вашим пользователям лучший опыт.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace StackOverflowExceptionAppDomainTest
{
    class Program
    {
        static void recrusiveAlgorithm()
        {
            recrusiveAlgorithm();
        }
        static void Main(string[] args)
        {
            if(args.Length>0&&args[0]=="--child")
            {
                recrusiveAlgorithm();
            }
            else
            {
                var domain = AppDomain.CreateDomain("Child domain to test StackOverflowException in.");
                domain.ExecuteAssembly(Assembly.GetEntryAssembly().CodeBase, new[] { "--child" });
                domain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) =>
                {
                    Console.WriteLine("Detected unhandled exception: " + e.ExceptionObject.ToString());
                };
                while (true)
                {
                    Console.WriteLine("*");
                    Thread.Sleep(1000);
                }
            }
        }
    }
}

Вы можете считывать это свойство каждые несколько вызовов, StackOverflowException, и если трассировка стека превысила определенный порог, который вы предварительно установили, вы можете вернуть функцию.

person Nick Mertin    schedule 11.06.2015

Вы также должны попробовать заменить некоторые рекурсивные функции циклами.

@William Jockusch Один из подходов, который вы могли бы использовать, - это написать / изменить профилировщик для отслеживания размера стека при получении уведомления о вызове. Environment.StackTrace CLRProfiler может быть хорошим местом для начала, но это не кажется тривиальным занятием. Ссылка на загрузку исходного кода профилировщика в блоге Дэвида Браумана: blogs.msdn.com/b/davbr/archive/2011/02/01/

person sharp12345    schedule 27.12.2012
comment

- person Abel; 29.11.2013