Può sembrare una contraddizione, soprattutto dopo il mio post precedente su questo argomento, ma la prima cosa da fare per cominciare ad analizzare un dump è chiedersi: mi serve veramente un dump?!? smile_omg

Mi spiego: quando si analizza un errore o un problema in un’applicazione (di qualsiasi tipo) c’è un buon numero di cose da fare e considerazioni delle quali tenere conto, prima di decidere di catturare un dump, semplicemente perchè non tutti i problemi possono essere risolti in questo modo… tenete a mente che in fondo un dump non è altro che una fotografia dello stato di un processo in un determinato momento, possiamo cercare di capire cosa sia successo nel passato di quel processo (con alcune limitazioni), ma ovviamente non possiamo sapere cosa è successo al processo dopo che il dump è stato catturato. Ad esempio, se avete un problema con il debugging remoto di una vostra applicazione, difficilmente un dump sarà veramente d’aiuto (se non in casi particolari, molto meglio analizzare altri tipi di log), mentre se abbiamo per le mani un memory leak allora il dump è una delle prime cose che chiedo che mi sia fornito; ma in ogni caso c’è dell’altro, prima di arrivare al dump vero e proprio.

Da dove cominciamo? A volte lo si da per scontato, ma il primo passo per analizzare il problema è conoscere il più possibile lo scenario nel quale si verifica. Se si tratta di un’applicazione scritta da voi dovreste già conoscere tutti i dettagli, ma se ad esempio siete dei consulenti e state cercando di aiutare un vostro cliente a capire perchè una certa eccezione viene lanciata una volta ogni tanto, in maniera sporadica e non riproducibile, allora diventa fondamentale avere una discussione approfondita con le persone che hanno sviluppato l’applicazione stessa e molto probabilmente anche con gli Amministratori di Sistema che gestiscono le macchine e l’ambiente sulle quali l’applicazione gira.

Bisogna cercare di chiarire il più possibile lo scenario nel quale si verifica il problema, credo che questi siano i punti fondamentali da chiarire sempre:

  • Cosa succede esattamente? “La pagina va in errore” è decisamente troppo generico, molto meglio qualcosa del tipo “Quando compilo il form x e cerco di creare un report, il server restituisce un messaggio d’errore e non è più possibile continuare”
  • Qual’è il comportamento atteso? So che può sembrare un domanda banale con una risposta scontata, ma vi assicuro che le sorprese non mancano
  • Quando si verifica il problema? Sicuramente aiuta sapere se l’errore compare due volte al giorno, ogni 5 minuti, o una volta a settimana, o magari alla mattina presto (quando in genere tutti gli utenti si loggano all’applicazione), o solo quando l’utilizzo della memoria e del processore superano certe soglie ecc…
  • Quando è apparso il problema per la prima volta? Attenzione, spesso capita che ci si accorga di un errore solo dopo qualche tempo, o magari gli utenti lo segnalano solo dopo giorni, quando sono ormai esasperati dall’inconveniente o il loro lavoro diventa veramente difficoltoso. È importante invece cercare di capire (attraverso l’event viewer, i log di IIS, Performance Monitor ed alti tipi di trace se disponibili) quando il problema si è veramente verificato per la prima volta; questo può aiutare a risalire ad eventuali modifiche avvenute in quel periodo
  • Qual’è il business impact di questo problema? Mi è capitato di ricevere richieste che all’inizio sembravano “tranquille” per poi scoprire che l’applicazione doveva andare in produzione il giorno dopo… La gravità del problema spesso ha anche un effetto sulla “capacità di movimento” che ci verrà concessa per il troubleshooting; è importante sapere se ad esempio possiamo fare test e riavviare servizi a nostro piacimento, o se dobbiamo tentare di essere il meno invasivi possibile per non arrecare ulteriori disservizi agli utenti
  • Qual soluzione ci si aspetta? Quale scopo si vuole raggiungere? Può sembrare un duplicato della seconda domanda, ma in realtà non è così; è molto importante avere un obiettivo ben chiaro verso il quale lavorare, altrimenti molto facilmente ci si ritroverà a cercare di correggere a caso comportamenti e problemi di contorno ma che non hanno veramente a che fare con il problema principale, con conseguente spreco di tempo e fatica (e soldi). E poi, banalmente, come si fa a sapere che il problema è stato risolto, se non si sa esattamente qual’è e cosa si vuole ottenere? smile_nerd
Un po’ di terminologia

Come in qualsiasi ambito, è importante dare lo stesso significato alle parole per evitare spiacevoli incomprensioni; crash può voler dire parecchie cose (l’applicazione restituisce un errore, il browser mostra solo una pagina bianca, i dati mostrati sono diversi da quelli attesi ecc…):

  • crash: si riferisce ad un processo che per qualche motivo (solitamente un’eccezione non gestita correttamente, ma non solo) viene terminato dal Sistema Operativo (o da IIS ecc…). Come esserne sicuri? Dal Task Manager o con Process Explorer potete verificare se il processo scompare dalla lista di quelli attivi, o se viene sostituito da una nuova istanza che prende un PID diverso; nell’event log molto probabilmente avrete uno o più messaggi simili a “process yxz terminated unexpectedly”
  • hang: si ha quando un processo raggiunge uno stato per il quale non è più in grado di rispondere alle richieste degli utenti (ad esempio in IIS potreste ricevere un messaggio “Server too busy”) ma il processo non muore. In una situazione del genere il processo resta in memoria senza fare niente ed è necessario riavviarlo manualmente per ripristinarne l’operatività. IIS ha un meccanismo per monitorare automaticamente lo stato degli application pool e rilevare situazioni come questa; se l’application pool (il processo w3wp.exe) non risponde a questo meccanismo interno di controllo per un certo periodo, IIS lo ricicla (chiude l’istanza di w3wp.exe problematica e ne fa partire una nuova). A prima vista questo potrebbe sembrare un crash ma in realtà non lo è, e nell’event log avrete messaggi simili a “a process serving application pool yxz has failed to respond to a ping”
  • deadlock: si può considerare una forma speciale e “grave” di hang. Immaginate che il Thread 1 di un processo acquisisca un lock (un uso esclusivo) su sulla risorsa A (un handle, una socket ecc…) ma per completare il suo lavoro (e rilasciare il lock) ha bisogno di accedere alla risorsa B; ora immaginate che questa risorsa B sia bloccata dal Thread 2 che a sua volta per completare il suo lavoro ha bisogno di accedere alla risorsa A (ricordate, bloccata dal Thread 1)… questo è un deadlock, sostanzialmente lo stesso concetto di una referenza circolare in Excel.
  • memory leak: questo viene spesso confuso con un alto utilizzo della memoria, può sembrare una differenza sottile ma non lo è. Si ha un memory leak quando l’applicazione richiede continuamente memoria al Sistema Operativo e non la rilascia mai, con il risultato che la quantità di memoria utilizzata dal processo cresce all’infinito fino a quando, molto probabilmente, viene lanciata una OutOfMemoryException ed il processo viene chiuso. Diversa cosa è invece un’applicazione che (per svariati motivi) ha un utilizzo molto alto della memoria, ma non ha invece una crescita continua; in una situazione del genere il processo può continuare a lavorare per giorni o settimane senza provocare mai OutOfMemoryExceptions. Da tener presente che l’aumento di memoria utilizzata dal processo in un vero memory leak, può provocare il crash in 5 minuti o in alcuni giorni, ma lo scenario è sempre lo stesso: un continuo aumento della memoria utilizzata ed il crash finale.
Hang o crash dump?

Una volta raccolti tutti i dettagli riguardo il problema e chiarito che abbiamo bisogno di un dump, dobbiamo decidere quale sia la modalità migliore. Ovviamente, dipende dal problema smile_regular. Ci può essere qualche variazione a seconda delle circostanze, ma sostanzialmente possiamo catturare un crash dump o un hang dump. Qual’è la differenza?

Avremo bisogno di un crash dump se non è possibile determinare in anticipo quando il problema si verificherà: nel caso di un crash vero e proprio ad esempio (ma non è l’unica circostanza) il processo viene riciclato all’improvviso, non avremmo il tempo di fare niente per raccogliere i dati “manualmente”, o se dovremo catturare il dump su un’eccezione o un breakpoint. Normalmente per catturare un crash dump è importante che il processo (o i processi) che vogliamo dumpare siano già attivi prima di lanciare il debugger.

Avremo bisogno invece di un hang dump quando dopo l’occorrenza del problema il processo resterà ancora in memoria, ad esempio se abbiamo un alto utilizzo della CPU o della memoria.

First o second chance?

Come suggerisce il nome, un’eccezione dovrebbe essere un’evento eccezionale (quindi raro) nella vita di un’applicazione, non la regola; ad esempio è sempre bene verificare se un oggetto è valido prima di provare ad utilizzarlo, invece di lasciare che venga lanciata un’eccezione e poi gestirla in un blocco try…catch (ma ci sono occasioni nelle quali può essere necessario ottenere l’eccezione… come sempre, tutto è relativo smile_nerd). Ma cosa succede quando si ha un debugger attaccato al processo? Quando l’eccezione viene lanciata, il debugger viene notificato prima che venga eseguito l’eventuale blocco try…catch nell’applicazione: sostanzialmente il debugger ha una prima possibilità (una first chance) di gestire l’eccezione; se il debugger decide di non fare niente e lascia passare l’eccezione, riprende la normale esecuzione e l’applicazione ha la possiblità di gestire l’eccezione come previsto dallo Sviluppatore. Se l’applicazione non gestisce l’eccezione, il debugger viene notificato nuovamente ed ha una seconda possibilità (second chance) di vedere e gestire l’eccezione. Questa è la circostanza nella quale, normalmente, un processo va in crash.

Il tutto è più chiaro se proviamo ad utilizzare adplus (del quale parlerò più approfonditamente più avanti): il comportamento di default se utilizzato in crash mode è quello di catturare un minidump (un dump con una quantità limitata d’informazioni) per ogni first chance exception, mentre avremo un full dump (un dump completo del processo) in caso di secondo chance exception.

Quanto costa?

Ovviamente intendo in termini di performance del server, che potrebbe anche essere una macchina di produzione stressata e “da toccare con i guanti”. Come potete facilmente immaginare, un costo c’è, specialmente nel caso del crash dump perchè il debugger resta attaccato al processo per tutto il tempo necessario a riprodurre il crash e molto probabilmente nel frattempo dovrà loggare un buon numero di eccezioni. Sfortunatamente non i può quantificare in maniera esatta. Posso dirvi che nella mia esperienza fino a questo momento, mi è capitato solamente in due occasioni di avere per le mani un server già talmente sovraccarico (o un’applicazione che lanciava un numero talmente elevato di eccezioni) da non riuscire ad utilizzare il debugger per catturare un dump; c’è da notare che in entrambe le situazioni il server era già oltre il normale punto di utilizzo e carico, ed il debugger è stato solo la goccia che ha fatto traboccare il vaso. Nella maggior parte delle circostanze, con un po’ di attenzione potremo catturare un dump anche in ambiente di produzione senza arrecare danni agli utenti (che sono sempre lo scopo finale del nostro lavoro).

La situazione ideale è avere un ambiente di test nel quale poter provare a riprodurre il problema: se ci si riesce, si può tranquillamente lavorare sul server di test, raccogliere i dump e fare i test necessari e toccare la produzione solamente a lavori ultimanti (e soluzione trovata smile_regular).

Nel prossimo post vedremo cosa sono i simboli, perchè sono importanti e come utilizzarli al meglio con dump e Windbg.

 

Carlo Cardella
Senior Support Engineer
EMEA IIS and Web Developer Support