2010-09-15 12 views
8

Sto cercando un modo per eseguire il debug di una patch critica di Critical 7 (TCriticalSection) rara/deadlock. In questo caso, se un thread è in attesa su una sezione critica per più di 10 secondi, mi piacerebbe produrre un report con la traccia stack di entrambi i thread che attualmente bloccano la sezione critica e anche il thread che non è riuscito a essere in grado per bloccare la sezione critica dopo aver atteso 10 secondi. Va bene quindi se viene sollevata un'eccezione o se l'applicazione termina.Delphi: Esecuzione del debug della sezione critica del debug segnalando lo stack di chiamate di thread in esecuzione sul blocco "failure"

Preferirei continuare a utilizzare le sezioni critiche, anziché utilizzare altre primitive di sincronizzazione, se possibile, ma posso cambiare se necessario (ad esempio per ottenere una funzione di timeout).

Se lo strumento/metodo funziona in runtime al di fuori dell'IDE, questo è un bonus, poiché questo è difficile da riprodurre su richiesta. Nel raro caso riesco a duplicare il deadlock all'interno dell'IDE, se provo a mettere in pausa per avviare il debug, l'IDE rimane lì senza fare nulla e non arriva mai a uno stato in cui posso visualizzare i thread o gli stack delle chiamate. Posso resettare il programma in esecuzione, però.

Aggiornamento: In questo caso, sto trattando solo una sezione critica e 2 thread, quindi questo probabilmente non è un problema di ordinamento dei blocchi. Credo che ci sia un tentativo annidato improprio di inserire il blocco attraverso due thread diversi, il che si traduce in un deadlock.

risposta

8

È necessario creare e utilizzare la classe oggetto di blocco. Può essere implementato utilizzando sezioni o mutex critici, a seconda che si desideri eseguirne il debug o meno.

La creazione di una propria classe ha un ulteriore vantaggio: è possibile implementare una gerarchia di blocco e generare un'eccezione quando viene violato. I deadlock si verificano quando le serrature non vengono prese esattamente nello stesso ordine, ogni volta. Assegnando un livello di blocco a ciascun blocco è possibile verificare che i blocchi siano stati presi nell'ordine corretto. È possibile memorizzare il livello di blocco corrente in un threadvar e consentire solo i blocchi che hanno un livello di blocco inferiore, altrimenti si genera un'eccezione. Questo colpirà tutte le violazioni, anche quando non si verifica un deadlock, quindi dovrebbe accelerare molto il tuo debugging.

Come per ottenere la traccia dello stack dei thread, ci sono molte domande qui su Stack Overflow che si occupa di questo.

Aggiornamento

Lei scrive:

In questo caso, ho solo porre rimedio ad una sezione critica e 2 fili, quindi questo probabilmente non è un problema di blocco ordinamento. Credo che ci sia un tentativo annidato improprio di inserire il blocco attraverso due thread diversi, il che si traduce in un deadlock.

Questa non può essere l'intera storia. Non c'è modo di deadlock con due thread e una singola sezione critica da solo su Windows, perché le sezioni critiche possono essere acquisite in modo ricorsivo da un thread. Deve esserci un altro meccanismo di blocco, come ad esempio la chiamata SendMessage().

Ma se si ha realmente a che fare solo con due thread, uno di questi deve essere il thread principale/VCL/GUI. In tal caso dovresti essere in grado di utilizzare la funzione MadExcept "Main thread freeze checking". Proverà ad inviare un messaggio al thread principale e fallirà dopo che è trascorso un tempo personalizzabile senza che il messaggio sia gestito. Se il thread principale sta bloccando la sezione critica e l'altro thread sta bloccando una chiamata di gestione messaggi, allora MadExcept dovrebbe essere in grado di catturarlo e darti una traccia stack per entrambi i thread.

+1

+1 per il controllo bloccato del thread MadExcept. –

+0

madExcept può anche essere richiesto di fare un dump del thread in qualsiasi momento, quindi è forse l'ideale per questo. – mj2008

+0

madExcept sembra l'opzione migliore. Grazie! – Anagoge

0

Se si desidera essere in grado di attendere qualcosa con un timeout, è possibile provare a sostituire la sezione critica con un segnale TEvent. Puoi dire di aspettare l'evento, dargli una durata di timeout e controllare il codice del risultato. Se il segnale è stato impostato, puoi continuare. In caso contrario, se è scaduto, si genera un'eccezione.

Almeno, è così che lo farei in D2010. Non sono sicuro che Delphi 7 abbia TEvent, ma probabilmente lo fa.

3

Questo non è un anwer diretto alla tua domanda, ma qualcosa che ho incontrato di recente che ha avuto me (e un paio di colleghi) per un po '.

È stato un blocco di thread intermittenti, che coinvolge una sezione critica e una volta che abbiamo saputo la causa, è stato molto ovvio e ci ha dato un momento "d'oh". Tuttavia, ci sono voluti alcuni seri tentativi di ricerca (aggiungendo sempre più tracce di registrazione per individuare la dichiarazione incriminata) ed è per questo che ho pensato di parlarne.

Era anche su una sezione critica entra. Un altro thread aveva effettivamente acquisito quella sezione critica. Un blocco morto come tale non sembrava essere la causa, in quanto c'era solo una parte critica coinvolta, quindi non ci potevano essere problemi con l'acquisizione di serrature in un ordine diverso. Il thread che contiene la sezione critica dovrebbe semplicemente essere continuato e quindi rilasciato il blocco, consentendo l'altro thread per acquisirlo.

Alla fine si è scoperto che il thread che conteneva il blocco era in definitiva l'accesso al ItemIndex di un (IIRC) combobox, abbastanza innocuo sembrerebbe. Sfortunatamente, ottenere ItemIndex dipende dall'elaborazione dei messaggi. E il thread in attesa del lock era il thread principale dell'applicazione ... (nel caso qualcuno si chiedesse: il thread principale fa tutto il processo di elaborazione ...)

Avremmo potuto pensarci molto prima se avesse avuto stato un po 'più ovvio dall'inizio che il vcl era coinvolto. Tuttavia, è iniziato con codice non-ui e il coinvolgimento di vcl è diventato evidente solo dopo aver aggiunto la strumentazione (enter - exit tracing) lungo l'albero delle chiamate e tornare indietro attraverso tutti gli eventi innescati e i loro gestori fino al codice ui.

Spero solo che questa storia sia di aiuto a qualcuno di fronte a un misterioso blocco.

2

Utilizzare il Mutex anziché la Sezione critica. C'è una piccola differenza tra mutex e sezioni critiche: le sezioni critiche sono più efficaci mentre i mutex sono più flessibili. È possibile passare facilmente tra mutex e sezioni critiche, utilizzando per esempio i mutex nella versione di debug.

per sezione critica usiamo:

var 
    FLock: TRTLCriticalSection; 

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

lo stesso con mutex:

var FLock: THandle; 

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

È possibile utilizzare timeout (in millisecondi; 10000 per 10 secondi) con mutex implementando acquisire funzione di blocco come this:

1

È anche possibile utilizzare le sezioni critiche con TryEnterCriticalSection API anziché EnterCriticalSection.

Se si utilizza TryEnterCriticalSection e l'acquisizione del blocco non riesce, l'API restituisce False e si può gestire l'errore in qualsiasi modo si ritenga opportuno, invece di bloccare semplicemente il thread.

Qualcosa di simile

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

notano che Delphi di TCriticalSection utilizza EnterCriticalSection quindi a meno che tweak che di classe, si dovrà fare la propria classe o ti avere a che fare con la critica sezione di inizializzazione/deinizializzazione.

Problemi correlati