2010-09-15 14 views
8

Ich bin auf der Suche nach einer Möglichkeit, eine seltene Delphi 7 kritischen Abschnitt (TCriticalSection) 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.Delphi: Debuggen kritischer Abschnitt durch Berichtsaufruf von laufenden Threads auf Sperre "Fehler" hängen

Ich würde es vorziehen, weiterhin kritische Abschnitte zu verwenden, anstatt andere Synchronisationsprimitive zu verwenden, wenn möglich, aber wenn nötig, um eine Zeitüberschreitungsfunktion zu erhalten.

Wenn das Tool/die Methode zur Laufzeit außerhalb der IDE funktioniert, 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 nur mit einem kritischen Abschnitt und 2 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.

Antwort

8

Sie sollten Ihre eigene Sperrobjektklasse erstellen und verwenden. Es kann mit kritischen Abschnitten oder Mutexen implementiert werden, abhängig davon, 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 geahndet, auch wenn kein Deadlock auftritt. Daher sollte das Debugging sehr beschleunigt werden.

Um die Stapelverfolgung der Threads zu erhalten, gibt es hier viele Fragen zu Stack Overflow.

aktualisiert

Sie schreiben:

In diesem Fall bin ich nur mit einem kritischen Abschnitt und zwei Threads zu tun, so ist dies wahrscheinlich kein Schloss Bestell 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 Blockiermechanismus involviert 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 die Funktion MadExcept "Main thread freeze checking" verwenden können. 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 Nachrichtenaufruf blockiert, sollte MadExcept in der Lage sein, dies zu erfassen und Ihnen eine Stack-Ablaufverfolgung für beide Threads zu geben.

+1

+1 für den MadExcept-Thread eingefroren überprüfen. –

+0

madExcept kann auch jederzeit aufgefordert werden, einen Thread Dump zu nehmen, also ist das vielleicht ideal dafür. – mj2008

+0

madExcept sieht wie die beste Option aus. Vielen Dank! – Anagoge

0

Wenn Sie auf etwas mit einem Timeout 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.

3

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

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 auf einem kritischen Abschnitt eingeben. 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 weitergeführt worden sein 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 ItemIndex eines (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 irgendjemand fragt: Der Haupt-Thread erledigt die gesamte Nachrichtenverarbeitung ...)

Wir hätten viel früher daran denken können, wenn es hätte war von Anfang an etwas offensichtlicher, dass der vcl involviert war. Es begann jedoch in nicht-ui bezogenen Code und vcl Beteiligung wurde erst nach dem Hinzufügen Instrumentierung (Enter - Exit Tracing) entlang der Aufruf-Struktur und zurück durch alle ausgelösten Ereignisse und ihre Handler bis zu dem ui-Code ersichtlich.

Ich hoffe nur, diese Geschichte wird jemandem helfen, der mit einem mysteriösen Hang konfrontiert wird.

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 kritischen Abschnitt verwenden wir:

var 
    FLock: TRTLCriticalSection; 

    InitializeCriticalSection(FLock); // create lock 
    DeleteCriticalSection(FLock);  // free lock 
    EnterCriticalSection(FLock);  // acquire lock 
    LeaveCriticalSection(FLock);  // release lock 

das gleiche mit Mutex:

var FLock: THandle; 

    FLock:= CreateMutex(nil, False, nil); // create lock 
    CloseHandle(FLock);     // free lock 
    WaitForSingleObject(FLock, Timeout); // acquire lock 
    ReleaseMutex(FLock);     // release lock 

Sie können Timeouts verwenden (in Millisekunden; 10000 für 10 Sekunden) mit mutexes durch Lock-Funktion Implementierung erwerben wie dies:

function AcquireLock(Lock: THandle; TimeOut: LongWord): Boolean; 
begin 
    Result:= WaitForSingleObject(Lock, Timeout) = WAIT_OBJECT_0; 
end; 
1

Sie können auch kritische Abschnitte mit derverwendenAPI statt EnterCriticalSection.

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 beheben, anstatt nur den Thread zu sperren.

So etwas wie

while not TryEnterCriticalSection(fLock) and (additional_checks) do 
begin 
    deal_with_failure(); 
    sleep(500); // wait 500 ms 
end; 

Sie beachten Sie, dass Delphi TCriticalSection verwendet EnterCriticalSection so, wenn Sie diese Klasse optimieren, müssen Sie Ihre eigene Klasse tun, oder Sie werden mit den kritischen Abschnitt Initialisierung/Deinitialisierung beschäftigen.