Program Tip

StackOverflowException을 방지 및 / 또는 처리하려면 어떻게해야합니까?

programtip 2020. 11. 8. 10:55
반응형

StackOverflowException을 방지 및 / 또는 처리하려면 어떻게해야합니까?


나는 내가 쓰고 있는 내 StackOverflowException에서 XslCompiledTransform.Transform메소드에 대한 호출에서 얻는 것을 방지하거나 처리하고 싶습니다 Xsl Editor. 문제는 사용자 Xsl script가 무한 재귀적인를 작성할 수 있다는 것입니다 Transform. (즉, 문제는 일반적으로 그러한 예외의 원인 인 일반적인 프로그래밍 오류가 아닙니다.)

허용되는 재귀 수를 감지 및 / 또는 제한하는 방법이 있습니까? 아니면이 코드가 나에게 폭발하는 것을 막기위한 다른 아이디어가 있습니까?


Microsoft에서 :

.NET Framework 버전 2.0부터 StackOverflowException 개체는 try-catch 블록에서 catch 할 수 없으며 해당 프로세스는 기본적으로 종료됩니다. 따라서 사용자는 스택 오버플로를 감지하고 방지하기 위해 코드를 작성하는 것이 좋습니다. 예를 들어 애플리케이션이 재귀에 의존하는 경우 카운터 또는 상태 조건을 사용하여 재귀 루프를 종료합니다.

예외가 코드가 아닌 내부 .NET 메서드 내에서 발생한다고 가정합니다.

몇 가지를 할 수 있습니다.

  • xsl에서 무한 재귀를 확인하고 변환 (Ugh)을 적용하기 전에 사용자에게 알리는 코드를 작성하십시오.
  • XslTransform 코드를 별도의 프로세스로로드합니다 (해키, 작업량이 적음).

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

참고 @WilliamJockusch의 현상금에있는 질문과 원래 질문은 다릅니다.

이 답변은 타사 라이브러리의 일반적인 경우에 대한 StackOverflow와 함께 할 수있는 작업과 할 수없는 작업에 대한 것입니다. XslTransform의 특별한 경우를 찾고 있다면 허용되는 답변을 참조하십시오.


스택의 데이터가 특정 제한 (바이트)을 초과하기 때문에 스택 오버플로가 발생합니다. 이 탐지의 작동 방식에 대한 자세한 내용은 여기 에서 찾을 수 있습니다 .

StackOverflowExceptions를 추적하는 일반적인 방법이 있는지 궁금합니다. 즉, 내 코드 어딘가에 무한 재귀가 있지만 어디인지 모르겠다 고 가정합니다. 나는 그것이 일어나는 것을 볼 때까지 모든 곳에서 코드를 밟는 것보다 더 쉬운 방법으로 그것을 추적하고 싶습니다. 나는 그것이 얼마나 끔찍한 지 상관하지 않습니다.

링크에서 언급했듯이 정적 코드 분석에서 스택 오버플로를 감지하려면 결정할 수없는 중지 문제를 해결해야합니다 . 은색 총알이 없음 을 확인 했으므로 이제 문제를 추적하는 데 도움이되는 몇 가지 트릭을 보여 드리겠습니다.

이 질문은 다른 방식으로 해석 될 수 있다고 생각합니다. 제가 약간 지루하기 때문에 :-), 저는 그것을 다른 변형으로 나눌 것입니다.

테스트 환경에서 스택 오버플로 감지

기본적으로 여기서 문제는 (제한된) 테스트 환경이 있고 (확장 된) 프로덕션 환경에서 스택 오버플로를 감지하려고한다는 것입니다.

SO 자체를 감지하는 대신 스택 깊이를 설정할 수 있다는 사실을 활용하여이 문제를 해결합니다. 디버거는 필요한 모든 정보를 제공합니다. 대부분의 언어에서는 스택 크기 또는 최대 재귀 깊이를 지정할 수 있습니다.

기본적으로 스택 깊이를 가능한 한 작게 만들어 SO를 강제하려고합니다. 넘치지 않으면 프로덕션 환경을 위해 항상 더 크게 만들 수 있습니다 (=이 경우 : 더 안전함). 스택 오버플로가 발생하는 순간 '유효한'것인지 여부를 수동으로 결정할 수 있습니다.

이렇게하려면 스택 크기 (이 경우 작은 값)를 Thread 매개 변수에 전달하고 어떤 일이 발생하는지 확인합니다. .NET의 기본 스택 크기는 1MB이며 더 작은 값을 사용합니다.

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를 얻을 때까지 더 큰 값으로 설정할 수 있습니다.

그렇게하기 전에 예외 만들기

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. .NET 프로파일 링 API를 사용하여 모든 메서드 호출을 캡처하고이를 사용하여 스택 오버플로를 파악합니다. 예를 들어, 호출 트리에서 동일한 메서드를 X 번 발견하면 신호를 제공하는지 확인하는 것을 구현할 수 있습니다. 여기 에 당신에게 유리한 시작을 줄 프로젝트가 있습니다 .

XmlWriter 개체 주위에 래퍼를 만드는 것이 좋습니다. 따라서 WriteStartElement / WriteEndElement에 대한 호출 수를 계산하고 태그의 양을 특정 수 (fe 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));
    }
}

이 답변은 @WilliamJockusch입니다.

StackOverflowExceptions를 추적하는 일반적인 방법이 있는지 궁금합니다. 즉, 내 코드 어딘가에 무한 재귀가 있지만 어디인지 모르겠다 고 가정합니다. 나는 그것이 일어나는 것을 볼 때까지 모든 곳에서 코드를 밟는 것보다 더 쉬운 방법으로 그것을 추적하고 싶습니다. 나는 그것이 얼마나 끔찍한 지 상관하지 않습니다. 예를 들어, 다른 스레드에서 활성화 할 수있는 모듈이 있으면 스택 깊이를 폴링하고 "너무 높음"이라고 생각한 수준에 도달하면 불평하는 모듈이 있으면 좋을 것입니다. 예를 들어 "너무 높음"을 600 프레임으로 설정하여 스택이 너무 깊으면 문제가 될 수 있음을 알 수 있습니다. 그런 것이 가능합니다. 또 다른 예는 내 코드 내의 모든 1000 번째 메서드 호출을 디버그 출력에 기록하는 것입니다. 이것이 overlow의 증거를 얻을 가능성은 꽤 좋을 것이고 출력을 너무 심하게 날려 버리지는 않을 것입니다. 핵심은 오버플로가 발생하는 곳에서 수표를 작성하는 것을 포함 할 수 없다는 것입니다. 왜냐하면 전체 문제는 그것이 어디에 있는지 모른다는 것입니다. 바람직하게 솔루션은 내 개발 환경이 어떻게 생겼는지에 의존해서는 안됩니다. 즉, 특정 도구 집합 (예 : VS)을 통해 C #을 사용하고 있다고 가정해서는 안됩니다.

이 StackOverflow를 잡기 위해 몇 가지 디버깅 기술을 듣고 싶어하는 것처럼 들리므로 몇 가지 시도해 볼 수 있다고 생각했습니다.

1. 메모리 덤프.

프로의 : 메모리 덤프는 스택 오버플로의 원인을 해결하는 확실한 불 방법입니다. AC # MVP와 저는 SO 문제를 함께 해결했고 여기 에서 블로그에 올렸 습니다 .

이 방법은 문제를 추적하는 가장 빠른 방법입니다.

이 방법을 사용하면 로그에 표시된 단계를 따라 문제를 재현 할 필요가 없습니다.

단점 : 메모리 덤프는 매우 크고 프로세스에 AdPlus / procdump를 첨부해야합니다.

2. 측면 지향 프로그래밍.

장점 : 이것은 아마도 응용 프로그램의 모든 메서드에 코드를 작성하지 않고 모든 메서드에서 호출 스택의 크기를 확인하는 코드를 구현하는 가장 쉬운 방법 일 것입니다. 호출 전후에 인터셉트 할 수 있는 AOP 프레임 워크 가 많이 있습니다.

스택 오버플로를 일으키는 메서드를 알려줍니다.

StackTrace().FrameCount응용 프로그램에서 모든 방법의 시작 및 종료 를 확인할 수 있습니다.

단점 : 성능에 영향을 미칠 것입니다. 후크는 모든 메소드에 대해 IL에 내장되어 있으며 실제로 "비활성화"할 수 없습니다.

개발 환경 도구 세트에 따라 다소 다릅니다.

3. 사용자 활동 기록.

일주일 전에 나는 문제를 재현하기 어려운 몇 가지를 찾아 내려고했습니다. 이 QA 사용자 활동 로깅, 원격 측정 (및 전역 예외 처리기의 변수)을 게시했습니다 . 내가 내린 결론은 처리되지 않은 예외가 발생할 때 디버거에서 문제를 재현하는 방법을 알 수있는 정말 간단한 user-actions-logger였습니다.

프로의 : 당신은 (이벤트 즉, 구독) 의지에 따라 또는 해제를 설정할 수 있습니다.

사용자 작업을 추적하기 위해 모든 방법을 가로 챌 필요는 없습니다.

메서드가 AOP보다 훨씬 더 간단하게 구독 된 이벤트 수를 계산할 수 있습니다 .

로그 파일은 비교적 작으며 문제를 재현하기 위해 수행해야하는 작업에 중점을 둡니다.

사용자가 애플리케이션을 사용하는 방식을 이해하는 데 도움이 될 수 있습니다.

단점 : Windows 서비스에 적합하지 않으며 웹 앱에 대해 이와 같은 더 나은 도구가 있다고 확신합니다 .

스택 오버플로를 유발하는 메서드를 반드시 알려주지 는 않습니다 .

문제를 가져 와서 바로 디버깅 할 수있는 메모리 덤프 대신 수동으로 문제를 재현하는 로그를 단계별로 살펴 봐야합니다.

 


위에서 언급 한 모든 기술과 @atlaste가 게시 한 일부 기술을 시도해보고 어떤 기술이 PROD 환경에서 실행하기에 가장 쉽고 / 가장 빠르거나 / 더럽거나 / 가장 수용 가능한지 알려주십시오.

어쨌든 이것을 추적하는 행운을 빕니다.


응용 프로그램이 Xsl 스크립트의 3d 파티 코드에 의존하는 경우 먼저 버그로부터 방어 할 것인지 여부를 결정해야합니다. 정말로 방어하고 싶다면 별도의 AppDomains에서 외부 오류가 발생하기 쉬운 논리를 실행해야한다고 생각합니다. StackOverflowException을 잡는 것은 좋지 않습니다.

질문 도 확인하십시오 .


나는 오늘 스택 오버플로가 있었고 귀하의 게시물 중 일부를 읽고 가비지 수집기를 돕기로 결정했습니다.

나는 다음과 같이 거의 무한 루프를 사용했습니다.

    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
}

그것은 나를 위해 일했으며 누군가에게 도움이되기를 바랍니다.


.NET 4.0을 사용하면 HandleProcessCorruptedStateExceptionsSystem.Runtime.ExceptionServices 특성을 try / catch 블록이 포함 된 메서드에 추가 할 수 있습니다 . 이것은 정말 효과가있었습니다! 권장하지는 않지만 작동합니다.

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");

        }

    }      
}

@WilliamJockusch, if I understood correctly your concern, it's not possible (from a mathematical point of view) to always identify an infinite recursion as it would mean to solve the Halting problem. To solve it you'd need a Super-recursive algorithm (like Trial-and-error predicates for example) or a machine that can hypercompute (an example is explained in the following section - available as preview - of this book).

From a practical point of view, you'd have to know:

  • How much stack memory you have left at the given time
  • How much stack memory your recursive method will need at the given time for the specific output.

Keep in mind that, with the current machines, this data is extremely mutable due to multitasking and I haven't heard of a software that does the task.

Let me know if something is unclear.


By the looks of it, apart from starting another process, there doesn't seem to be any way of handling a StackOverflowException. Before anyone else asks, I tried using AppDomain, but that didn't work:

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

If you do end up using the separate-process solution, however, I would recommend using Process.Exited and Process.StandardOutput and handle the errors yourself, to give your users a better experience.


You can read up this property every few calls, Environment.StackTrace , and if the stacktrace exceded a specific threshold that you preset, you can return the function.

You should also try to replace some recursive functions with loops.

참고URL : https://stackoverflow.com/questions/206820/how-do-i-prevent-and-or-handle-a-stackoverflowexception

반응형