Jak zapobiec i / lub poradzić sobie z problemem StackOverflowException?

Chciałbym albo zapobiec lub obsłużyć wyjątek Stoskoverflowexception, który otrzymuję od wywołania XslCompiledTransform.Metoda Transform w edytorze Xsl, który piszę. Problem wydaje się być taki, że użytkownik może napisać skrypt Xsl, który jest nieskończenie rekurencyjny, a to po prostu wybucha podczas wywołania metody Transform. (Czyli problemem nie jest tylko typowy błąd programowy, który zwykle jest przyczyną takiego wyjątku.)

Czy jest sposób na wykrycie i / lub ogranicz liczbę dozwolonych rekursji? Albo jakieś inne pomysły, żeby ten kod nie wybuchł na mnie?

Author: Patrick Hofman, 2008-10-16

10 answers

From Microsoft:

Począwszy od. NET Framework Wersja 2.0, A StackOverflowException obiekt nie może zostać złapany przez try-catch block i odpowiedni proces jest domyślnie zakończone. W konsekwencji, użytkownikom zaleca się pisanie kodu wykrywanie i zapobieganie stosowi przepełnienie. Na przykład, jeśli twój zastosowanie zależy od rekurencji, użycie licznik lub stan warunek do Zakończ pętlę rekurencyjną.

Zakładam, że wyjątkiem jest dzieje się w wewnętrznej metodzie. NET, a nie w Twoim kodzie.

Możesz zrobić kilka rzeczy.
  • napisz kod, który sprawdza xsl pod kątem nieskończonej rekurencji i powiadamia użytkownika przed zastosowaniem transformacji (Ugh).
  • załaduj kod XslTransform do osobnego procesu (Hacky, ale mniej pracy).

Możesz użyć klasy Process, aby załadować zespół, który zastosuje transformację do oddzielnego procesu i ostrzec użytkownika o awarii, jeśli umrze, bez zabijania głównej aplikacji.

EDIT: właśnie testowałem, oto jak to zrobić:

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 Process:

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);
        }
    }
}
 56
Author: FlySwat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-23 11:22:23

UWAGA pytanie w bounty autorstwa @ WilliamJockusch i pytanie Oryginalne są różne.

Ta odpowiedź dotyczy StackOverflow w ogólnym przypadku bibliotek innych firm i tego, co możesz/nie możesz z nimi zrobić. Jeśli szukasz specjalnego przypadku z XslTransform, zobacz akceptowaną odpowiedź.


Przepełnienie stosu ma miejsce, ponieważ dane na stosie przekraczają określony limit (w bajtach). Szczegóły działania tej detekcji można znaleziono tutaj .

Zastanawiam się, czy jest jakiś ogólny sposób na wyśledzenie StackOverflowExceptions. Innymi słowy, Załóżmy, że mam nieskończoną rekurencję gdzieś w moim kodzie, ale nie mam pojęcia gdzie. Chcę go wyśledzić w jakiś sposób, który jest łatwiejszy niż przechodzenie przez KOD w całym miejscu, dopóki nie zobaczę, że to się dzieje. Nie obchodzi mnie to.

Jak wspomniałem w linku, wykrycie przepełnienia stosu na podstawie statycznej analizy kodu wymagałoby rozwiązania problem wstrzymania, który jest nie do podjęcia decyzji. Teraz, gdy ustaliliśmy, że nie ma srebrnej kuli, mogę ci pokazać kilka sztuczek, które moim zdaniem pomagają namierzyć problem.

Myślę, że to pytanie można interpretować na różne sposoby, a ponieważ jestem trochę znudzony : -), podzielę je na różne odmiany.

Wykrywanie przepełnienia stosu w środowisku testowym

Zasadniczo problem polega na tym, że masz (ograniczone) środowisko testowe i chcesz wykryć przepełnienie stosu w (rozszerzonym) środowisku produkcyjnym.

Zamiast wykrywać samo SO, rozwiązuję to wykorzystując fakt, że głębokość stosu może być ustawiona. Debugger da Ci wszystkie potrzebne informacje. Większość języków pozwala określić rozmiar stosu lub maksymalną głębokość rekurencji.

W zasadzie staram się wymusić tak, aby głębokość stosu była jak najmniejsza. Jeśli nie przepełni się, zawsze mogę go powiększyć (=w tym przypadku: bezpieczniej) dla środowisko produkcyjne. W momencie, gdy pojawi się przepełnienie stosu, możesz ręcznie zdecydować ,czy jest on "prawidłowy", czy nie.

Aby to zrobić, przekaż rozmiar stosu (w naszym przypadku: małą wartość) do parametru wątku i zobacz, co się stanie. Domyślna wielkość stosu w. NET to 1 MB, użyjemy o wiele mniejszej wartości:

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

Uwaga: użyjemy tego kodu również poniżej.

Gdy przepełni się, możesz ustawić go na większą wartość, dopóki nie otrzymasz tak, że ma to sens.

Tworzenie WYJĄTKÓW przed tobą więc

StackOverflowException nie jest chwytliwe. Oznacza to, że niewiele można zrobić, gdy to się stało. Tak więc, jeśli uważasz, że coś musi pójść nie tak w Twoim kodzie, możesz zrobić własny wyjątek w niektórych przypadkach. Jedyną rzeczą, której potrzebujesz do tego jest aktualna głębokość stosu; nie ma potrzeby licznika, możesz użyć rzeczywistych wartości Z. 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();
    }
}

Zauważ, że to podejście działa również w przypadku komponentów innych firm które używają mechanizmu zwrotnego. Jedyną rzeczą wymaganą jest to, że możesz przechwycić niektóre wywołania w śledzeniu stosu.

Wykrywanie w osobnym wątku

Wyraźnie to zasugerowałeś, więc teraz to.

Możesz spróbować wykryć SO w osobnym wątku.. ale to pewnie nic Ci nie da. Przepełnienie stosu może się zdarzyć szybko , nawet zanim otrzymasz przełącznik kontekstowy. Oznacza to, że mechanizm ten nie jest w ogóle niezawodny... I wouldn ' t polecam korzystanie z niego . Fajnie było budować, więc oto kod: -)

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

Uruchom to w debuggerze i baw się dobrze z tym, co się dzieje.

Korzystanie z właściwości przepełnienia stosu

Inna interpretacja twojego pytania brzmi: "Gdzie są fragmenty kodu, które mogłyby potencjalnie spowodować wyjątek przepełnienia stosu?". Oczywiście odpowiedź na to pytanie brzmi: cały kod z rekurencją. Dla każdego kawałka kodu możesz wykonać kilka instrukcji analiza.

Możliwe jest również określenie tego za pomocą statycznej analizy kodu. W tym celu musisz zdekompilować wszystkie metody i dowiedzieć się, czy zawierają nieskończoną rekurencję. Oto jakiś kod, który robi to za Ciebie:

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

Fakt, że cykl metod zawiera rekurencję, nie jest bynajmniej gwarancją, że nastąpi przepełnienie stosu - jest to po prostu najbardziej prawdopodobny warunek wstępny wyjątku przepełnienie stosu. W skrócie oznacza to, że kod ten określi fragmenty kodu, w których może wystąpić przepełnienie stosu , co powinno znacznie zawęzić większość kodu.

Jeszcze inne podejścia

Są inne podejścia, których tutaj nie opisałem.

  1. Obsługa przepełnienia stosu poprzez hostowanie procesu CLR i obsługę go. Zauważ, że nadal nie możesz go "złapać".
  2. zmiana całego kodu IL, budowa kolejnej biblioteki DLL, dodanie sprawdzania rekurencji. Tak, to całkiem możliwe (mam zaimplementowałem to w przeszłości : -); jest to po prostu trudne i wymaga dużo kodu, aby to zrobić dobrze.
  3. użyj API profilowania. NET, aby przechwycić wszystkie wywołania metod i użyć go do określenia przepełnienia stosu. Na przykład, możesz zaimplementować sprawdzanie, czy jeśli napotkasz tę samą metodę X razy w drzewie wywołań, dasz sygnał. Jest projekt tutaj który da ci przewagę.
 20
Author: atlaste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:46

Sugerowałbym stworzenie owijki wokół obiektu XmlWriter, tak aby zliczał ilość wywołań do WriteStartElement/Writeendelement, a jeśli ograniczysz ilość tagów do pewnej liczby (np. 100), będziesz mógł rzucić inny wyjątek, na przykład - InvalidOperation.

To powinno rozwiązać problem w większości przypadków

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));
    }
}
 8
Author: Dmitry Dzygin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-23 11:55:59

Jeśli aplikacja zależy od kodu 3d-party (w skryptach Xsl) to najpierw musisz zdecydować czy chcesz bronić się przed błędami w nich czy nie. Jeśli naprawdę chcesz się bronić to myślę, że powinieneś wykonać swoją logikę, która jest podatna na zewnętrzne błędy w oddzielnych AppDomains. Łapanie StackOverflowException nie jest dobre.

Sprawdź również to pytanie .

 3
Author: Shrike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:35

Miałem dzisiaj stackoverflow i przeczytałem kilka Twoich postów i postanowiłem pomóc Zbieraczowi śmieci.

Kiedyś miałem prawie nieskończoną pętlę taką:

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

Zamiast tego niech zasób wyczerpie się w ten sposób:

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
}
U mnie zadziałało, mam nadzieję, że komuś pomoże.
 3
Author: Fixation,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-09 16:22:20

Ta odpowiedź jest dla @ WilliamJockusch.

Zastanawiam się, czy jest jakiś ogólny sposób, aby wyśledzić StackOverflowExceptions. Innymi słowy, przypuśćmy, że mam nieskończoną rekurencja gdzieś w moim kodzie, ale nie mam pojęcia gdzie. Chcę wyśledzić go w jakiś sposób, który jest łatwiejszy niż przechodzenie przez kod / align = "left" / I don ' t care how hackish tak. Na przykład, byłoby wspaniale mieć moduł, który mógłbym aktywować, być może nawet z kolejny wątek, który przepytywał stos głębokość i narzekał, jeśli dostał się do poziomu uznałem " zbyt wysoki."Dla przykład, mogę ustawić "zbyt wysokie" na 600 klatek, domyślając się, że jeśli stack był zbyt głęboki, to musi być problem. Czy coś takiego możliwe. Innym przykładem może być logowanie co 1000-ty wywołanie metody w moim kodzie do wyjścia debugowania. Szanse, że to będzie trochę dowód overlow byłby całkiem dobry i prawdopodobnie nie zbyt mocno wysadzić wyjście. Kluczem jest to, że nie może obejmować zapisywanie czeku wszędzie tam, gdzie występuje przepełnienie. Ponieważ cały problem w tym, że nie wiem, gdzie to jest. Preferowane rozwiązanie nie powinno zależeć od tego, jak wygląda moje środowisko programistyczne; tj., nie powinno zakładać, że używam C# poprzez określony zestaw narzędzi (np. VS).

Wygląda na to, że chcesz usłyszeć kilka technik debugowania, aby złapać ten StackOverflow, więc pomyślałem, że podzielę się kilkoma dla Ciebie spróbuj.

1. Zrzuty Pamięci.

Pro ' s: Zrzuty pamięci to pewny sposób na ustalenie przyczyny przepełnienia stosu. A C # MVP & I worked together to SO and he went on to blog about it here.

Ta metoda jest najszybszym sposobem, aby wyśledzić problem.

Ta metoda nie wymaga odtworzenia problemów, wykonując kroki widoczne w logach.

Con ' s: Zrzuty pamięci są bardzo duże i musisz dołączyć AdPlus / procdump proces.

2. Programowanie Zorientowane Na Aspekt.

Pro ' s: jest to prawdopodobnie najprostszy sposób na zaimplementowanie kodu, który sprawdza rozmiar stosu wywołań z dowolnej metody bez pisania kodu w każdej metodzie aplikacji. Istnieje kilka frameworków AOP, które pozwalają przechwytywać połączenia przed i po nich.

Powie Ci, jakie metody powodują przepełnienie stosu.

Pozwala sprawdzić StackTrace().FrameCount w wejście i wyjście wszystkich metod w aplikacji.

Con: będzie to miało wpływ na wydajność-Hooki są osadzone w IL dla każdej metody i nie można naprawdę "de-aktywować"go.

To w pewnym stopniu zależy od zestawu narzędzi środowiska programistycznego.

3. Rejestrowanie Aktywności Użytkowników.

Tydzień temu próbowałem upolować kilka trudnych do odtworzenia problemów. Wysłałem ten QA rejestrowanie aktywności użytkownika, telemetria (i zmienne w globalnym wyjątku Handlers) . Wniosek, do którego doszedłem, był bardzo prosty user-actions-logger, aby zobaczyć, jak odtworzyć problemy w debuggerze, gdy wystąpi dowolny nieobsługiwany wyjątek.

Pro ' s: możesz go włączyć lub wyłączyć do woli (np. subskrybowanie wydarzeń).

Śledzenie działań użytkownika nie wymaga przechwytywania każdej metody.

Możesz również policzyć liczbę subskrybowanych metod zdarzeń znacznie prościej niż w AOP.

Pliki dziennika są stosunkowo małe i skupić się na tym, jakie działania trzeba wykonać, aby odtworzyć problem.

Może pomóc ci zrozumieć, w jaki sposób użytkownicy korzystają z Twojej aplikacji.

Con ' s: nie nadaje się do usługi Windows i jestem pewien, że są lepsze narzędzia, takie jak to dla aplikacji internetowych .

Nie mówi koniecznie o metodach, które powodują przepełnienie stosu.

Wymaga ręcznego przeglądania dzienników odtwarzających problemy, a nie zrzutu pamięci, w którym może go pobrać i debugować od razu.

 


Może spróbujesz wszystkich technik, o których wspomniałem powyżej i niektórych, które @ atlaste opublikował i powiedz nam, które z nich znalazłeś były najłatwiejsze/najszybsze/najbrudniejsze/najbardziej akceptowalne do uruchomienia w środowisku PROD / itp.

W każdym razie powodzenia w tropieniu tego.
 3
Author: Jeremy Thompson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:35

Z. NET 4.0 możesz dodać atrybut HandleProcessCorruptedStateExceptions z systemu.Runtime.Wyjątki dla metody zawierającej blok try/catch. To naprawdę zadziałało! Może nie polecam, ale działa.

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

        }

    }      
}
 2
Author: jdehaan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-01 14:33:14

Wygląda na to, że poza uruchomieniem innego procesu, nie ma żadnego sposobu obsługi StackOverflowException. Zanim ktoś inny zapyta, próbowałem użyć AppDomain, ale to nie zadziałało:

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

Jeśli jednak używasz rozwiązania oddzielnego procesu, zalecałbym użycie Process.Exited i Process.StandardOutput i obsługę błędów samodzielnie, aby zapewnić użytkownikom lepsze wrażenia.

 0
Author: Nick Mertin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-11 13:47:43

@ WilliamJockusch, jeśli dobrze zrozumiałem twoją troskę, nie jest możliwe (z matematycznego punktu widzenia), aby Zawsze zidentyfikować nieskończoną rekurencję, ponieważ oznaczałoby to rozwiązanie problemu zatrzymania. Aby go rozwiązać, Potrzebujesz Super-rekurencyjnego algorytmu (Jak na przykład predykaty prób i błędów) lub maszyny, która może hiperkomputować (przykład jest wyjaśniony w następującej sekcji - dostępnej jako podgląd - z book ).

Z praktycznego punktu widzenia, musiałbyś wiedzieć:

  • ile pamięci stacka zostało w danym momencie
  • ile pamięci w stosie będzie potrzebna twoja rekurencyjna metoda w danym czasie dla określonego wyjścia.

Należy pamiętać, że przy obecnych maszynach dane te są bardzo zmienne z powodu wielozadaniowości i nie słyszałem o oprogramowaniu, które wykonuje to zadanie.

Daj znać, jeśli coś jest niejasne.

 0
Author: Gentian Kasa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-12 08:59:18

Możesz odczytywać tę właściwość co kilka wywołań, Environment.StackTrace, a jeśli stacktrace przekroczył określony próg, który ustawiłeś, możesz zwrócić funkcję.

Powinieneś także spróbować zastąpić niektóre funkcje rekurencyjne pętlami.

 -2
Author: sharp12345,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-27 18:18:10