Vorrei spendere qualche tempo per parlare di diverse tecniche di diagnosi di un'applicazione. In questa serie di post cominceremo ad analizzare le ragioni per le quali un'applicazione scritta usando il .NET Framework si pianta, presenterò alcuni esempi e pattern che possono causare quei comportamenti e mostrerò alcune tecniche diagnostiche per risalire alle cause prime di problemi profondi (interop signature mismatch e data corruption) molto difficili da diagnosticare con i tool usati comumente.
La prima puntata è una chiaccherata sulle ragioni di morte di un processo che contiene in CLR.
Qualche anno fa, quando si scriveva in C o C++ era facile piantare un programma. Bastava dimenticarsi di assegnare a null un puntatore assegnato a qualcosa che era stato deallocato e il tuo programma si piantava nel modo più creativo. Oggi è molto più difficile trovarsi un una situazione dove un'applicazione scritta nei Framework del XXI secolo sia così fragile.
Quando un'applicazione scritta usando il .NET Framework si pianta, succede uno dei seguenti eventi ritenuti catastrofici dal runtime (in ordine di probabilità):
- Un'eccezione sollevata nel codice non è stata gestita
- Si è verificata una access violation nell'interop (cioè sei andato a leggere memoria che non è tua)
- causata dal codice nativo eseguito
- causata da un errore nel codice di marshalling (signature sbagliata, convenzioni di allocazione non rispettate tra caller e callee, etc.)
- heap corruption causata da codice nativo
- Una risorsa critica per l'esecuzione del programma non è disponibile. Quando questo succede, spesso il programma ha un leak di risorse native (le alloca e non le rilascia mai) o un cattivo uso del Dispose Pattern che lo portano ad una morte prematura per soffocamento. Le risorse la mancanza delle quali può uccidere il processo sono
- Memoria
- System Handles
- Stack overflow (raro - può succedere se si esagera con la profondità di una query o di una ricorsione)
- Un bug nel CLR che causa una access violation (se ne trovate uno dopo il CLR V2 vi pago da bere :-) )
La domanda fondamentale è "Perché un processo viene distrutto in questi casi".
La risposta è di una semplicità disarmante:"Perché è l'unica azione che garantisce la correttezza dell'elaborazione e il più possibile l'integrità dei dati". Se si continua l'esecuzione in questi stati, è garantito che alcuni degli invarianti che il programmatore ha posto come ipotesi per il funzionamento dell'applicazione sono stati violati.
In questo caso sia per ragioni di correttezza sia di sicurezza, il comportamento più sicuro per l'applicazione è terminare il processo e generare un dump utile per scoprire cosa è andato storto.
Questa considerazione è la ragione dietro alla decisione di cambiare il comportamento di default del runtime in caso di una eccezione non gestita. Mentre .NET 1.1 consente di proseguire, .NET 2.0 e superiori termina il processo a meno che non si setti un parametro di configurazione esplicitamente.
<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="true" />
</runtime>
</configuration>
La prossima volta parleremo di MDA (Managed Debugging Assistants) uno strumento potente e poco conosciuto per la diagnostica di problemi seri e difficili da identificare altrimenti.
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
- Autopsia di un'applicazione - Terza puntata: l'applicazione che si congela, l'8 aprile 2008 alle 07:08
- Autopsia di un'applicazione - seconda puntata gli MDA (Managed Debug Assistants), il 6 aprile 2008 alle 00:43