Death by Unhandled Exception

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.

posted on Wednesday, December 14, 2005 12:02 AM by scott

Comments

Wednesday, December 14, 2005 4:47 AM by Sergio Pereira

# re: Death by Unhandled Exception

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?
Wednesday, December 14, 2005 10:18 AM by scott

# re: Death by Unhandled Exception

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!).
Wednesday, December 14, 2005 11:35 AM by Girish Bharadwaj

# Exceptions and ASP.NET 2.0

Scott, in his usual style, has a blog entry regarding unhandled exceptions in ASP.NET 2.0. The gist of...
Thursday, December 15, 2005 8:45 AM by Milan Negovan

# re: Death by Unhandled Exception

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.
Thursday, December 15, 2005 3:24 PM by Anatoly Lubarsky

# re: Death by Unhandled Exception

It's definitely a bug. At least event log entry is...
Thursday, December 15, 2005 5:50 PM by scottgu@microsoft.com

# re: Death by Unhandled Exception

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
Friday, December 16, 2005 7:56 PM by Christopher Steen

# Link Listing - December 16, 2005

ASP.NET Podcast Show #30 - Minimizing the ASP.NET ViewState
Part #2 [Via: Wallym ]
Complex data binding...