Delphi: Debuggen kritischer Abschnitt hängt von Reporting-Aufruf-Stack von laufenden Threads auf Sperre "Fehler"

8

Ich bin auf der Suche nach einer Möglichkeit, einen seltenen kritischen Abschnitt (TCriticalSection) von Delphi 7 mit Hang / Deadlock zu debuggen. Wenn in diesem Fall ein Thread länger als etwa 10 Sekunden auf einen kritischen Abschnitt wartet, würde ich gerne einen Bericht mit dem Stack-Trace von sowohl dem Thread, der gerade den kritischen Abschnitt sperrt, als auch dem Thread, der nicht in der Lage war, erstellen um den kritischen Abschnitt nach einer Wartezeit von 10 Sekunden zu sperren. Es ist OK, wenn eine Ausnahme ausgelöst wird oder die Anwendung beendet wird.

Ich würde es vorziehen, weiterhin kritische Abschnitte zu verwenden, anstatt, wenn möglich, andere Synchronisationsprimitive zu verwenden, kann aber bei Bedarf wechseln (z. B. um eine Zeitüberschreitungsfunktion zu erhalten).

Wenn das Tool / die Methode zur Laufzeit außerhalb der IDE arbeitet, ist das ein Bonus, da dies bei Bedarf nur schwer reproduziert werden kann. In dem seltenen Fall, dass ich den Deadlock in der IDE duplizieren kann, wenn ich versuche, Pause zu starten, um das Debuggen zu starten, sitzt die IDE einfach da und tut nichts, und kommt niemals in einen Zustand, in dem ich Threads oder Call Stacks sehen kann. Ich kann das laufende Programm jedoch zurücksetzen.

Update: In diesem Fall habe ich es nur mit einem kritischen Abschnitt und zwei Threads zu tun, also ist dies wahrscheinlich kein Lock-Order-Problem. Ich glaube, es gibt einen falsch verschachtelten Versuch, die Sperre über zwei verschiedene Threads einzugeben, was zu einem Deadlock führt.

    
Anagoge 15.09.2010, 16:45
quelle

5 Antworten

8

Sie sollten Ihre eigene Sperrobjektklasse erstellen und verwenden. Es kann mit kritischen Abschnitten oder Mutexen implementiert werden, je nachdem, ob Sie dies debuggen möchten oder nicht.

Das Erstellen einer eigenen Klasse hat einen zusätzlichen Vorteil: Sie können eine Sperrhierarchie implementieren und bei Verletzung eine Ausnahme auslösen. Deadlocks treten auf, wenn Sperren nicht jedes Mal in genau der gleichen Reihenfolge ausgeführt werden. Durch Zuweisen einer Sperrstufe zu jeder Sperrung kann überprüft werden, ob die Sperrungen in der richtigen Reihenfolge vorgenommen wurden. Sie können die aktuelle Sperrstufe in einer Threadvariable speichern und zulassen, dass nur Sperren mit einer niedrigeren Sperrstufe ausgeführt werden, da andernfalls eine Ausnahme ausgelöst wird. Dadurch werden alle Verstöße aufgefangen, auch wenn kein Deadlock auftritt. Daher sollte das Debugging sehr beschleunigt werden.

Um den Stack-Trace der Threads zu erhalten, gibt es hier viele Fragen zu Stack Overflow.

Aktualisieren

Sie schreiben:

  

In diesem Fall habe ich es nur mit einem kritischen Abschnitt und zwei Threads zu tun, das ist also wahrscheinlich kein Lock-Order-Problem. Ich glaube, es gibt einen falsch verschachtelten Versuch, die Sperre über zwei verschiedene Threads einzugeben, was zu einem Deadlock führt.

Das kann nicht die ganze Geschichte sein. Es gibt keine Möglichkeit zum Deadlock mit zwei Threads und einem einzigen kritischen Abschnitt unter Windows, da kritische Abschnitte dort rekursiv von einem Thread erfasst werden können. Es muss ein anderer Blockierungsmechanismus beteiligt sein, wie zum Beispiel der SendMessage() -Aufruf.

Aber wenn Sie wirklich nur mit zwei Threads arbeiten, dann muss einer von ihnen der Haupt / VCL / GUI-Thread sein. In diesem Fall sollten Sie in der Lage sein, das "Main-Thread-Freeze-Checking" Feature von MadExcept zu verwenden. Es versucht, eine Nachricht an den Hauptthread zu senden, und schlägt fehl, nachdem eine anpassbare Zeit verstrichen ist, ohne dass die Nachricht verarbeitet wurde. Wenn Ihr Hauptthread im kritischen Abschnitt blockiert und der andere Thread bei einem Nachrichtenverarbeitungsaufruf blockiert, sollte MadExcept in der Lage sein, dies zu erfassen und Ihnen eine Stack-Ablaufverfolgung für beide Threads zu geben.

    
mghie 15.09.2010, 17:52
quelle
3

Dies ist keine direkte Antwort auf Ihre Frage, aber etwas, auf das ich vor kurzem gestoßen bin, hatte mich (und ein paar Kollegen) für eine Weile überrascht.

Es war ein intermittierender Fadenhang, der einen kritischen Abschnitt umfasste und sobald wir die Ursache wussten, war es sehr offensichtlich und gab uns allen einen "d'oh" Moment. Es brauchte jedoch einige ernsthafte Jagd zu finden (Hinzufügen von mehr und mehr Trace-Logging, um die anstößige Aussage zu lokalisieren) und deshalb dachte ich, ich würde es erwähnen.

Es war auch an einer kritischen Stelle einzutreten. Ein anderer Thread hatte tatsächlich diesen kritischen Abschnitt erworben. Ein totes Schloss als solches schien nicht die Ursache zu sein, da es nur einen kritischen Abschnitt gab, so dass es keine Probleme geben konnte, Schlösser in einer anderen Reihenfolge zu erhalten. Der Thread, der den kritischen Abschnitt enthält, sollte einfach fortgesetzt und dann die Sperre freigegeben haben, damit der andere Thread sie erfassen kann.

Am Ende stellte sich heraus, dass der Thread, der das Schloss hielt, letztendlich auf den ItemIndex einer (IIRC) Combobox zugreift, ziemlich harmlos, wie es scheint. Leider ist der ItemIndex auf die Verarbeitung von Nachrichten angewiesen. Und der Thread, der auf die Sperre wartete, war der Hauptanwendungs-Thread ... (nur für den Fall, dass sich irgendwer fragt: Der Haupt-Thread erledigt die gesamte Nachrichtenverarbeitung ...)

Wir hätten vielleicht viel früher daran denken können, wenn es von Anfang an etwas deutlicher gewesen wäre, dass der vcl involviert war. Es begann jedoch mit nicht-ui-bezogenem Code und die vcl-Beteiligung wurde erst nach dem Hinzufügen der Instrumentierung (Eingabe - Exit-Ablaufverfolgung) entlang der Aufrufstruktur und zurück durch alle ausgelösten Ereignisse und ihre Handler bis zum ui-Code ersichtlich.

Hoffentlich wird diese Geschichte jemandem helfen, der mit einem mysteriösen Hang konfrontiert ist.

    
Marjan Venema 15.09.2010 18:35
quelle
2

Verwenden Sie Mutex anstelle von Critical Section. Es gibt einen kleinen Unterschied zwischen Mutexen und kritischen Abschnitten - kritische Abschnitte sind effektiver, während Mutexe flexibler sind. Sie können problemlos zwischen Mutexen und kritischen Abschnitten wechseln, indem Sie beispielsweise Mutexe in der Debug-Version verwenden.

für den kritischen Abschnitt verwenden wir:

%Vor%

das Gleiche mit Mutex:

%Vor%

Sie können Timeouts (in Millisekunden; 10000 für 10 Sekunden) mit Mutexes verwenden, indem Sie die Acquire Lock-Funktion wie folgt implementieren:

%Vor%     
kludg 15.09.2010 21:56
quelle
1

Sie können kritische Abschnitte auch mit der TryEnterCriticalSection API anstelle von EnterCriticalSection verwenden.

Wenn Sie TryEnterCriticalSection verwenden und die Lock-Erfassung fehlschlägt, gibt die API False zurück, und Sie können den Fehler auf jede nur erdenkliche Weise behandeln, anstatt nur den Thread zu sperren.

Etwas wie

%Vor%

Beachten Sie, dass Delphis TCriticalSection EnterCriticalSection verwendet. Wenn Sie diese Klasse also nicht optimieren, müssen Sie Ihre eigene Klasse erstellen, oder Sie müssen sich mit der Initialisierung / Deinitialisierung des kritischen Abschnitts befassen.

    
José Montesinos 22.11.2011 21:01
quelle
0

Wenn Sie auf etwas mit einer Zeitüberschreitung warten möchten, können Sie versuchen, Ihren kritischen Bereich durch ein TEvent-Signal zu ersetzen. Sie können sagen, dass Sie auf das Ereignis warten sollen, geben Sie ihm eine Zeitüberschreitung und überprüfen Sie den Ergebniscode. Wenn das Signal gesetzt wurde, können Sie fortfahren. Wenn nicht, wird eine Ausnahme ausgelöst.

Zumindest würde ich es in D2010 tun. Ich bin mir nicht sicher, ob Delphi 7 TEvent hat, aber wahrscheinlich.

    
Mason Wheeler 15.09.2010 17:44
quelle