Designing for Debugging


Having a consistent plan for exception and error handling is critical to ensuring that you'll be able to debug problems in production environments. Additionally, many developers are confused about when to use finalization, so I wanted to give you the concise rules to finalization.

How should you implement exception handling?

Even though Microsoft cleaned up the exception handling story considerably in .NET, compared to other environments, it's still the one area people have the biggest problem getting right. Two books show you the background of exception handling and the best practices that everyone needs to follow. The first is Jeffrey Richter's CLR via C# (Microsoft Press, 2006), and the second is Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, by Krzysztof Cwalina and Brad Abrams (Addison Wesley, 2006). If you're doing .NET development without having read those books' sections on exception handling, you're wasting your time.

Both books mention the idea that you should let the applications crash instead of trying to recover from exceptions. Continually in my consulting work, I find that developers will go to great lengths to attempt to recover from unknown exceptions, and this is exactly the wrong approach to take. If you have an unknown exception occurring in your application, and you attempt to recover, you'll be running with unpredictable behavior and opening yourself up to security vulnerabilities.

Additionally, if you try to recover from an unknown exception, you will end up causing an exception later in the application and mask off the root cause of the original exception. You could cause a different exception anywhere from a millisecond or less to two weeks later or more depending on the application and exception. By letting the application crash, you'll have a direct correlation between the root cause and the crash so you'll be able to fix the application more quickly.

How can I log unhandled exceptions in my applications?

As I described in the last question, you want to let your application crash if there's an unhandled exception, but you will want to at least log that there's been an unhandled exception. For ASP.NET applications, you can look for unhandled exceptions in two places. If you want to get those errors on a per page basis, provide a Page_Error method for the page. For application-wide unhandled exceptions, add a global application class (in Global.asax) to the project, and put your logging in the Application_Error method.

No matter which method is logging your ASP.NET unhandled exceptions, you'll be getting the actual Exception-derived unhandled exception by using the Server.GetLastError method. After carefully logging the error in your Page_Error or Application_Error method, you need to decide how to report the error to the user. If you call Server.ClearError, the error propagation through the rest of ASP.NET will not happen. If you do not call Server.ClearError, the error will be reported to the user through the settings in the <customErrors> section of Web.Config.

The one limitation of the <customErrors> section is that the defaultRedirect attribute and <error> elements, where you can specify HTTP error redirects caused by your ASP.NET code, is that you can redirect only to static HTML pages. What I prefer doing in my Application_Error is to put the unhandled exception into the session state and redirect to an ASPX page so I can do better logging and display to the user. I'll still keep my <customErrors> section for the HTTP errors. The following shows an example Application_Error performing the redirect.

void Application_Error(object sender, EventArgs e) {     // Get the exception and stuff it into a session state variable.     // If you call Server.GetLastError in the redirect page, it will     // return null.     Exception ex = Server.GetLastError ( );     Session.Add ( "UnhandledException" , ex );     // Redirect to the error page.     Response.Redirect ( "ErrorReportingPage.aspx" , false );     // You have to clear the error or it will go through the     // <customErrors> section.     Server.ClearError ( ); }


For console applications, all unhandled exceptions by any thread in your application are reported on a per-AppDomain basis through the AppDomain.UnhandledException event. A breaking change between .NET 1.1 and .NET 2.0 is that in .NET 2.0, unhandled exceptions on all threads terminate the application. In .NET 1.1, only an exception on the main thread terminates the application. Having your application continue on its merry way after a pool thread disappears was completely wrong, and the old behavior was a bug in .NET itself.

Even with the improved unhandled exception behavior in console applications, there's still a small problem. If you have an unhandled exception in a pool thread or a finalizer thread, you get the standard crash message, which provides an opportunity to debug the application.

However, the main application thread is still running while the dialog is displayed, so your application can end before you get a chance to attach the debugger. I have no idea why pool threads and finalizer threads are treated this way, but I certainly hope that by the time the next version of the CLR ships, when one thread crashes, all threads will stop in unhandled exception scenarios.

Windows Forms applications add another twist to the unhandled exception mix. For background, pool, and finalizer threads, unhandled exceptions are reported through the AppDomain.UnhandledException event. As with the console application case, the main thread continues to run as you're handling the event. If the unhandled exception is in the main thread, you'll need to set the Application.ThreadException event in order to receive the notification. The good news is that setting the Application.ThreadException will disable the standard Windows Forms exception notification dialog box.

Since I'm talking about Windows Forms applications, instead of writing your own exception-reporting user interface, you should take a look at the Microsoft Exception Message Box, which is part of Feature Pack for Microsoft SQL Server 2005 - April 2006 and can be downloaded from http://www.microsoft.com/downloads/details.aspx?FamilyID=df0ba5aa-b4bd-4705-aa0a-b477ba72a9cb&DisplayLang=en. If you've ever seen an error in any of the SQL Server 2005 graphical tools, this is the same message box but in a redistributable package. The message box supports copying all the exception data to the clipboard in addition to showing the detailed data about the assertion. You can find more information on the features and usage of the Microsoft Exception Message Box at http://msdn2.microsoft.com/en-us/library/ms166343.aspx.

If you control the computers and network your application is running on, you can simply log any unhandled exception information to a file or database in a shared location. However, if you are providing software that runs outside your company, things get a little more interesting. You may want to develop with a Web service you can call with all the exception information from the user's application. If you don't have a server that can run your Web service, you may want to take a look at the Shareware Starter Kit that Microsoft put together to help shareware authors provide program registration, buy-now functionality, and, most importantly, a Web service to record your unhandled exceptions. You can find the source code and more information about the Shareware Software Starter Kit at http://sharewarestarterkit.com.

The last item I want to mention about unhandled exceptions is how you can send errors your users report to Microsoft. When the crash dialog box (which is in Figure 4-2, in case you've never seen it before) appears, the user can send the error information to Microsoft. To get all your company's errors, all you have to do is register for Windows Error Reporting at http://msdn.microsoft.com/isv/resources/wer/default.aspx. The only cost to you is for the purchase of a digital certificate.

Figure 4-2. Windows application error message


I'd strongly recommend that you sign up for Windows Error Reporting so you can get those crash reports. Even if you set up your logging to upload the data directly through a Web service right into your bug-tracking database, you never know when that interop problem will kill some memory inside the CLR and cause it to have a native crash. Although not everyone clicks the Send button, enough will for you to quickly see which errors are causing the most pain for end users.

When do I put a finalizer on my class?

Almost never. Finalization offers a way for a class to clean up resources before the object is garbage collected in memory. The idea was that if you were holding onto a native resource, such as a native Windows file handle, you'd have a method that allowed you to get the handle closed. The problem is that with C#, a finalizer method is denoted with a "~" in front of a method that's the same name as the class.

It was a mistake to use "~," because many developers coming from a C++ background thought that meant the method was a destructor and the object would be cleaned up when you set the object instance to null or when the reference goes out of scope. The garbage collector is a nondeterministic system, so you have no idea when the object is actually freed and its finalizer called. This can lead to situations in which you're holding onto native resources far longer than you expect.

To alleviate the problems on developers inadvertently using finalizers, .NET 2.0 introduces the SafeHandle class in the System.Runtime.InteropServices namespace. If you have a native resource, you'll wrap the native resource and only the native resource in a SafeHandle-derived class and override the abstract ReleaseHandle and IsInvalid get property. In those two methods, you'll handle cleaning up your native resource and returning if the handle you're wrapping is invalid. By using a SafeHandle-derived class, which is derived from CritialFinalizerObject class, you're telling the runtime to pay extra attention to this class to ensure that it truly gets cleaned up.

Having seen numerous problems with finalizers, I wanted to make sure I mentioned what you need to be doing instead. In code reviews, I specifically look for finalizers on classes and flag them as errors so the developer can get rid of them. A good article by Steve Toub, "Keep Your Code Running with the Reliability Features of the .NET Framework," in the October 2005 MSDN Magazine, discusses SafeHandle classes and reliability in more depth (http://msdn.microsoft.com/msdnmag/issues/05/10/Reliability/). A blog entry by Joe Duffy, "Never write a finalizer again (well, almost never)," also offers another discussion of the topic (http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=86c71425-57bc-4fcb-b34b-3262812f12cf).




Debugging Microsoft  .NET 2.0 Applications
Debugging Microsoft .NET 2.0 Applications
ISBN: 0735622027
EAN: 2147483647
Year: 2006
Pages: 99
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net