Diagnostika jedné chyby, aneb proč je dobré mít zdrojáky .NET Frameworku

V jedné starší aplikaci, která běží už několik let, se začla objevovat vyjímka NullReferenceException, což je vždy nepříjemné. Aplikace je postavena ještě na .NET Remotingu, ASP.NET Web Services a databázi Firebird (tím i Firebird .NET Provideru). Klient volá metodu webové služby, ta přes .NET Remoting další modul a ten používá Firebird databázi. Jediné co se objevilo v logu bylo tohle:

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.Services.Protocols.ServerProtocol.GenerateFaultString(Exception e, Boolean htmlEscapeMessage)
   at System.Web.Services.Protocols.Soap11ServerProtocolHelper.WriteFault(XmlWriter writer, SoapException soapException, HttpStatusCode statusCode)
   at System.Web.Services.Protocols.SoapServerProtocol.WriteException(Exception e, Stream outputStream)
   at System.Web.Services.Protocols.WebServiceHandler.WriteException(Exception e)
   at System.Web.Services.Protocols.WebServiceHandler.Invoke()
   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()

Vyjímka pochází přímo z NET Frameworku, lze odvodit že uvnitř metody webové služby došlo k vyjímce, ta měla být převedena na SoapException a vrácena SOAP Fault odpověď na klienta. Jenže se tak nestalo.

Jak dál ?

Ve WCF lze v takových případech v konfiguraci aplikace zapnout detailní logování, naštěstí to lze i v ASP.NET Web Services a vyjímka se objevovala alespoň jednou za den, takže si stačilo počkat. Ukázalo se, že jde o deadlock v databázi, tedy vyjímku FbException. Proč ale není převedena na SoapException a vzniká vyjímka NullReferenceException uvnitř frameworku ? Detailní log popisuje poměrně dobře jednotlivé kroky, takže stačí mít jen zdrojové kódy ... a ty Microsoft naštěstí uvolnil.

Kde se to pokazilo ?

Vyjímka NullReferenceException vzniká v metodě GenerateFaultString, kde nejvíce pravděpodobné je to na řádku 136

 if (text.Length == 0) text = e.GetType().Name;

Když proměnná text (načtená hodnota z property Exception.Message) má hodnotu null. Stačilo by tedy nahradit tu podmínku použitím String.IsNullOrEmpty() ...

Co říká dokumentace

Dokumentace k property Exception.Message uvádí, že:

The error message that explains the reason for the exception, or an empty string ("").

Takže hodnota null by se neměla objevit. To jsem si ověřil pokud předám do konstruktoru třídy Exception hodnotu null do proměnné message, v takovém připadě není hodnota property Message rovna null, ale je použit text ve kterém je název třídy vyjímky. Property Exception.Message je ovšem virtuální, takže v odvozené vyjímce ji lze přepsat a vracet tak nekoretně null hodnotu. Potom vznikne přesně ona NullReferenceException vyjímka v .NET Frameworku, pokud je takový kód v metodě ASP.NET Web Service. Druhá podmínka pro tuto chybu je nastavení customErrors mode="On" ve web.config.

Firebird .NET Provider

Ve skutečnosti vyjímka FbException (která obsahuje text v property Message) obsahovala ještě další vnořenou vyjímku IscException, kde nebyl v textovém výpisu žádný text vyjímky. A opravdu, pohledem do zdrojového kódu je vidět, že Message property je přepsána tak, že může vracet i hodnotu null.

Závěr

Bez detailního logování a zdrojových kódů je taková chyba velmi těžko odhalitelná. Obě chyby jsou reportovány:

Komentáře jsou uzavřeny