"I can't understand why people are frightened of new ideas. I'm frightened of the old ones." - John Cage
This isn't a treatise on how to handle exceptions; that's something you should study before reading this! The good news
about .NET Framework 2.0 is that unhandled exceptions, no matter where they come from, now cause termination of the app.
That might sound scary to you, but believe me -- that's the way it should be!
Some time ago, I wrote a piece entitled "Documenting Exceptional Developers." You may also want to go back and read that one.
Without some sort of "Handler of Last Resort" exception handling being built in, if your application throws an unhandled exception and goes into the Black Hole, all you are going to get to see is an EventLog entry that tells you it went into the Black Hole. It will tell you when, but it won't tell you where or how; in fact it will be just a tad this side of being utterly useless. Hey, we already know that our app blew up! Do you have to insult us too by rubbing it in?
There have been a number of posts in the C# Newsgroup around this subject recently, and a fellow developer and I were discussing it (it was his app that blew up, not mine!). So I did a little research and made an UnBlog post, and decided I'd write something up about it.
Even though we always hope to have everything encapsulated within nice structured try/catch/finally blocks, occasionally we get a BTH (Bad Thing Happened)-- apparently for no foreseeable reason. And what you get (if anything) is that very insulting, uninformative Event Log entry, and that's all. When you are working with Windows Forms, this raises a System.Threading.ThreadException event. We'll look at a way to wire that up.
Exceptions are expensive. Chris Brumme has a nice blog entry that I'll paraphrase which describes what usually happens when an exception is thrown:
- Get a stack trace by interpreting metadata emitted by the compiler to guide the stack unwind.
- Run through a chain of handlers up the stack, calling each handler twice.
- Compensate for mismatches between Structured, C++ and managed exceptions.
- Allocate a managed Exception instance and run its constructor. Often this involves looking up resources for the various error messages.
- Probably take a trip through the OS kernel, and often, take a hardware exception.
- Notify any attached debuggers, profilers, vectored exception handlers and other interested parties.
Obviously, the above list takes time, and it burns up CPU cycles. More often than not, when dumps are taken, developers find out their applications are throwing a lot more exceptions than they expected, or they were expecting them but didn't know that they can actually cause a problem since they don't seem to have a direct effect on the application.
If exceptions are handled and the end users don't see them, then why is this bad?
According to Tess Ferrandez, a Developer Tools Escalation Engineer at Microsoft:
- Exceptions are expensive.
- Exceptions can take you into unnecessary code paths.
- Exceptions are generally thrown when something went wrong.
Tess has a detailed blog entry on the above and much more.
And that just covers handled exceptions! Coding defensively means thinking long and hard about what could go wrong, writing good quality code that detects the condition BEFORE an exception is thrown, and having good exception handling logic (if an exception is thrown) that makes business decisions about whether it is a condition that can be handled or reported and the app allowed to continue running, or whether the app should be brought to a halt at that point. You, the developer, must be in control of this decision -- Don't let the runtime make this decision!
When your app completely goes into the Black Hole, the AppDomain that it was in unloads. That also fires an event - the AppDomain.UnhandledException event -- and we'll look at that. We'll look at how to wire up a Console or Service executable with these events.
Finally we'll go over the Registry entries that will pop up a JIT debugger, which, if installed, will give you a last chance to find out what blew up, and we'll cover the use of ADPLUS, a tool with which I became very familiar when stress - testing coporate software at the MS Testing Lab in Charlotte, NC, and which has been vastly improved and made easier to get a hold of by Microsoft since then. And, I'll show how to do it with ASP.NET.
First, we must understand that the UnhandledException event is not an unhandled exception "handler". Registering for the event does not cause unhandled exceptions to be "handled" -- it simply notifies you that an exception has gone unhandled, in case you want to try to save state before your thread or application dies.
In Frameworks v1.0 and 1.1, an unhandled exception did not always mean that your application would die. If the unhandled exception occurred on anything other than the main thread or a thread that began in unmanaged code, the CLR would eat the exception and allow your app to keep going. This is generally very evil, because all kinds of nasty managed and unmanaged atrocities could occur until your application simply wasn't actually accomplishing anything -- and you wouldn't be able to get a clue as to why this happened.
In v2.0, an unhandled exception on any thread will take down the whole application smartly. It's much easier to debug crashes than it is to debug hangs or the silent-stoppage-of-work problem from the previously - described default behavior, and we'll point you to ADPLUS at MSDN to show you how to do it. In .NET 2.0 you will certainly find out that just as in any Zombie movie, the "really dead"
is always preferable to the "living dead"!
I remember the first time I saw ADPLUS and BlackBox dumps of code I had written. It was at the MS Testing labs in 2001. It was very humbling to see that code I thought was well written was generating thousands of unhandled exceptions at runtime!
There are basically five ways I've identified so far that enable you to deal with UE's (Unhandled Exceptions):
1. Putting Application.Run() in try-catch block
2. Using Application.ThreadException event
3. Using AppDomain.UnhandledException event
4. Add registry entry to pop up JIT Debugger
5. Use ADPLUS in crash mode with debug symbols installed.
Putting Application.Run method call in a try-catch block
[STAThread]
static void Main() {
#if DEBUG
// don't catch unhandled exceptions if debugging
Application.Run(new Form1());
#else
// enable unhandled exceptions trapping for release version
try {
Application.Run(new Form1());
}
catch(Exception ex) {
// your logging facility here.
}
#endif
}
Using an Unhandled Exception Delegate:
public static void Application_ThreadException( object sender, ThreadExceptionEventArgs e )
{
// Handle exception Here
}
// Example usage:
static void Main()
{
Application.ThreadException += new ThreadExceptionEventHandler( Application_ThreadException );
Application.Run(new Form1());
}
Winforms ThreadException Handler:
[STAThread]
static void Main()
{
// Subscribe to thread (unhandled) exception events
ThreadExceptionHandler handler =
new ThreadExceptionHandler();
Application.ThreadException +=
new ThreadExceptionEventHandler(
handler.Application_ThreadException);
// Load the form
Application.Run(new Form1());
}
internal class ThreadExceptionHandler
{
public void Application_ThreadException( object sender, ThreadExceptionEventArgs e )
{
try
{
// Exit program if user clicks Abort.
DialogResult result = ShowThreadExceptionDialog( e.Exception );
if (result == DialogResult.Abort)
Application.Exit();
}
catch
{
// Fatal error, terminate program
try
{
MessageBox.Show("Fatal Error","Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
finally
{
Application.Exit();
}
}
}
private DialogResult ShowThreadExceptionDialog(Exception ex)
{
string errorMessage=
"Unhandled Exception:\n\n" +
ex.Message + "\n\n" +
ex.GetType() +
"\n\nStack Trace:\n" +
ex.StackTrace;
return MessageBox.Show(errorMessage, "Application Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Stop);
}
}
AppDomain Unhandled Exception event:
Thread.GetDomain().UnhandledException += new
UnhandledExceptionEventHandler(Application_UnhandledException);
public static void Application_UnhandledException(
object sender, UnhandledExceptionEventArgs e)
{
// Log it, save state, whatever here. "e" contains the exception object.
}
Using Registry Entry to force popup of JIT Debugger (if installed)
You can also set the following registry key:
HKLM\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSetting
to a DWORD of 0xFF00 or 0xFF02, which should force a JIT-attach dialog to come up and give you the opportunity to
at least figure out what kind of exception this is and where it happened. That would give you a starting point to figure out why your app is blowing up. The JIT Debugger is installed with the Framework SDK, which is available either as a separate download, or as an installation option with Visual Studio.NET.
ASP.NET : GetLastError Handler:
// in global.asax:
protected void Application_Error(Object sender, EventArgs e)
{
ExceptionHandler.LogException exc = new ExceptionHandler.LogException();
Exception ex = Server.GetLastError().GetBaseException();
// pass ex to your logging or notification utility
}
See here for more details:
Using ADPLUS:
There's no reason to launch into a treatise on ADPLUS since Microsoft has provided ample information and a link to the product on MSDN. Suffice to say, it works. Especially in Crash mode, where it basically just sits around waiting for your app to blow up, and then "takes its dump" (no pun intended). Provided you've taken the time to download and install the OS Debug Symbol files for your System, you can find out a lot from these dump files. Properly configured, ADPLUS doesn't use a lot of resources, and you can deploy it by simply copying the ADPLUS Folder and symbol files to a production machine. There's nothing to register, and nothing to "install". I should mention in passing that the download for the debug symbols is quite large (over 200MB for Windows XP SP2), so I will reproduce the command-line help output below for you to get a better idea of what ADPLUS can do:
ADPlus 6.02.020 Usage Information
Command line switches
-Crash Runs ADPlus in Crash mode
-Hang Runs ADPlus in Hang mode
-p <PID> Defines a Process ID to be monitored
-pn <ProcessName> Defines a process name to be monitored
-sc <spawning command> Defines the application and parameters to be started
in the debugger
-iis All iis related processes will be monitored (inetinfo, dllhost,
mtx, etc.)
-o <output directory> Defines the directory where logs and dumps are
to be placed.
-quiet No dialog boxes will be displayed
-notify <destination> Will send a message to the destination (Windows messaging must be on)
-c <config file name> Defines a configuration file to be used
-ce <custom exception code> Defines a custom exception to be monitored
-ce 0x80501001
-bp <breakpoint parameters> Sets a breakpoint
Syntax: -bp address;optional_additional_parameters
-bp MyModule!MyClass::MyMethod
-bp MyModule!MyClass::MyMethod;MiniDump
-y <symbol path> Defines the symbol path to be used
-yp <symbol path to add> Defines an additional symbol path
-FullOnFirst Sets ADPlus to create full dumps on first chance exceptions
-FullOnFirstOver Sets ADPlus to create full dumps on first chance exceptions,
overwriting the previous dump
-MiniOnSecond Sets ADPlus to create mini dumps on second chance exceptions
-NoDumpOnFirst Sets ADPlus to not create any dumps on first chance exceptions
-NoDumpOnSecond Sets ADPlus to not create any dumps on second chance exceptions
-do Dump Only - changes default behavior to not include additional info, just a dump
-CTCF Creates a full dump on CTL+C, and quits
-CTCFG Creates a full dump on CTL+C, and resumes execution
-NoTlist Will not use TList; only -p can be used (-pn and -iis will not work)
-dbg <debugger> Allows you to select the debugger to be used
cdb, windbg or ntsd (default is cdb)
Required: ('-hang', or '-crash') AND ('-iis' or '-p' or '-pn')
If using a config file (-c switch) the required switches above can be
provided in the config file or in the command line
The -sc switch, if used, must be the last one
Examples: 'ADPlus -hang -iis', Produces memory dumps of IIS and all
MTS/COM+ packages currently running.
'ADPlus -crash -p 1896', Attaches the debugger to process with PID
1896, and monitors it for 1st and 2nd
chance access violations (crashes).
'ADPlus -?' or 'ADPlus -help': Displays help information.
-------------------------------------------------------------------------------
HELP and Documentation
For more detailed information on how to use and config ADPlus please see
the debugger's help file (debugger.chm) under
Using Debugging Tools for Windows
Crash dumps
User mode dump files
Creating a user mode dump file
ADPlus
-------------------------------------------------------------------------------
For more information on using ADPlus, please refer to the following KB:
http://support.microsoft.com/default.aspx?scid=kb;en-us;286350
I hope this brief coverage of Unhandled Exceptions has been valuable in helping you to become more of an ExceptionLess developer. Remember, exceptions are expensive when they occur. They burn up CPU cycles, and you should avoid catching the generic "System.Exception" in production code. Above all, exceptions should never be used to handle business logic.
|