Death by Unhandled Exception

Wednesday, December 14, 2005

In .NET 1.1, an unhandled exception would terminate a process only if the exception occurred on the main application thread. The runtime would silently swallow unhandled exceptions on other threads to prevent termination. I’ll leave it for the reader to decide if this is behavior is naughty or nice.

In .NET 2.0, unhandled exceptions on any thread will most likely terminate the application (see Exceptions In Managed Threads for details). For ASP.NET developers taking threading into their own hands to implement a “fire and forget” strategy, it’s important to realize this change in behavior. Imagine the following code inside a web form.

protected void button1_click(object sender, EventArgs e)
{
doWork(
null);
}

protected void button2_click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(
new WaitCallback(doWork), null
);
}

protected void doWork(object state)
{
string s = state.ToString();
}

Both button event handlers ultimately call the doWork method and pass a null value as the parameter. The doWork method will always throw a NullReferenceException. The button1_click method calls doWork synchronously. Since the exception in doWork occurs on a thread processing a request, the ASP.NET runtime will catch the exception and throw up the default ASP.NET error page. One technique for globallylogging these types of exceptions is to wire upApplication_Error.

The button2_click method uses a second thread to execute doWork. The second thread is not in a request processing context, so the exception will not be seenin the Application_Error event handler. This is true for QueueUserWorkItem and when working with aThread object directly. Note: I’m not encouraging anyone to use either QueueUserWorkItem or Thread.Start in ASP.NET. I generally recommend you avoid spinning off work unless you know what you are doing. Asynchronous pages might be what you are looking for instead.

In ASP.NET 1.1, the runtime would swallow the NullReferenceExceptionfrom the second thread and the application would continue in blissful ignorance - not so in ASP.NET 2.0. Here is what happens when doWork executes on a second thread with the WebDev server.

dead webdev

This couldn’t happen with IIS, right? Well, on XP (IIS 5), you won’t see an error dialog box, but check the Application event log afterwards….

aspnet_wp.exe (PID: 316) stopped unexpectedly.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

It’s dead, Jim.

With IIS 6, w3wp.exe willgo down in flames. Iusually see two event log entries left behind. The first entry is a warning entry with healthy amounts of detail, including a stack trace if pdb files are available. The second entry is cryptic:

EventType clr20r3, P1 w3wp.exe, P2 6.0.3790.1830, P3 42435be1, P4 app_web_pion_w1z, P5 0.0.0.0, P6 439f8d74, P7 3, P8 1, P9 system.nullreferenceexception, P10 NIL.

Of course, another ASP.NET worker process will rise upimmediately afterwards, buttermination is just notin the "good thing" category.Make sure you have a try / catch in place if you are using your own threads in ASP.NET 2.0.


Comments
Sergio Pereira Wednesday, December 14, 2005
Change of behavior is #1 in my upgrade pain chart. I expect to find problem reltaed to what you just described due, not because the pages directly use secondary threads, but because other components (including 3rd party stuff) are very likely using those extra threads.
In .Net 1.x, if I'm not mistaken, there's no way to globally catch exceptions in the secondary threads, would you know if this changed in 2.0?
scott Wednesday, December 14, 2005
Sergio:

I don't know of anyway to install a global handler. The best you can do, I think, is hook the UnhandledException event on AppDomain.Current domain. By the time the event fires it's too late to stop the process from terminating, but you might be able to retrieve some diagnostic information (assuming there is no heap, stack, or internal data structure corruption that caused the error!).
Milan Negovan Thursday, December 15, 2005
In 1.x it has always bothered me how exceptions were/are swallowed inside HttpHandlers. A handler would appear to simply, uummm, do nothing. It usually takes some time to even realize there's an exception thrown inside it and therefore it quietly abandons the request it processes. I'd rather see this exception thrown.

An HttpHandler doesn't execute on a separate thread (unless you code it so), but I'm wondering if this "change of behavior" manifests itself there, too.
Anatoly Lubarsky Thursday, December 15, 2005
It's definitely a bug. At least event log entry is...
scottgu@microsoft.com Friday, December 16, 2005
This KB article (http://support.microsoft.com/?id=911816) provides more background on why the behavior changed -- in general having unhandled exceptions occur on rogue threads is a pretty bad thing, and in servers it is better to fail fast than let things leak or die later.

Note that at the bottom of the article above, there is a switch you can make that will revert to the older V1.1 behavior of swalling errors:

<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="true" />
</runtime>
</configuration>

Hope this helps,

Scott
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!