Preventing Dialogs on the Server-Side in ASP.NET or Trace.Fail considered Harmful
Dialogs on the server-side? What is this nonsense of which you speak? Are you insane? Sadly no.
Back in the day, if you ran a VB6 COM DLL under Classic ASP, or as I like to call it "Poopy-SP," and you forgot to set "Unattended Execution" and "Retain in Memory" you could get yourself into a pickle if an error in the application caused a dialogbox or messagebox to pop up on the Server-side, just waiting for some poor schmuck to click "OK."
Recently I got an email that a high-powered blogger was seeing Dialog Boxes on his hosted instance of DasBlog while running under ASP.NET 2.0. These dialogs would hang the thread they were on, and hang IIS until they were dismissed. Shocked he and I were to say the least as "that ain't supposed to happen."
I suggested that he disable JIT Debugging in his registry. He tried it and indicated that he was getting a full stack trace inside the message box. Since the JIT Debugging Dialog doesn't look like that, I figured that wasn't the problem.
I spoke to some folks at MSFT who said that if there were unhandled exceptions being thrown from DasBlog, how they'd be presented would be controlled via the legacyUnhandledExceptionPolicy setting in his web.config like this (we don't have any unhandled exceptions, BTW, there's 'global' exception handlers in two places):
<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="true" />
</runtime>
</configuration>
This tells the CLR to act more like .NET 1.1 in this respect as the behavior for unhandled exceptions has changed in 2.0 (MSDN Mag Article here and MSDN Topic here).
This "solution" smelled fishy to me, didn't seem like a good idea, and ultimately didn't solve his problem.
He then indicated that he was seeing errors in our SharedBasePageErrorHandler, which made sense to me as we handle all Page-level exceptions there.
private void SharedBasePageErrorHandler(object sender, EventArgs e )
{
Exception pageException = Server.GetLastError();
try
{
loggingService.AddEvent(
new EventDataItem(EventCodes.Error,
pageException.ToString().Replace("\n","<br>"),
this.Page.Request.Url.ToString()));
System.Diagnostics.Trace.Fail(this.Context.Error.Message);
}
catch
{
}
}
Seems pretty straightforward, and his stuff WAS getting logged indicating that the LoggingService.AddEvent was working fine.
What about this System.Diagnostics.Trace.Fail call, though? Seems harmless, but it's the only other thing in there and it would explain where the stack trace was coming from - it's inside of this.Context.Error.Message.
There was a problem back in the day with Trace.Fail or Debug.Fail locking up the ASP.NET Worker Process.
If you reflector into the implementation of the DefaultTraceListener in the .NET Framework you'll see a call to AssertWrapper.ShowAssert (that should only happen if AssertUIEnabled is true and UI Permissions are available.) That must be the case in our blogger friend's situation.
public override void Fail(string message, string detailMessage)
{
string text1;
StackTrace trace1 = new StackTrace(true);
int num1 = 0;
bool flag1 = DefaultTraceListener.UiPermission;
try
{
text1 = this.StackTraceToString(trace1, num1, trace1.FrameCount - 1);
}
catch
{
text1 = "";
}
this.WriteAssert(text1, message, detailMessage);
if (this.AssertUiEnabled && flag1)
{
AssertWrapper.ShowAssert(text1, trace1.GetFrame(num1), message, detailMessage);
}
}
Assuming those things are true and ShowAssert does get called, interally the AssertWrapper.ShowAssert calls SafeNativeMethods.MessageBox only if the user is in an interactive mode. It's possible that MB_SERVICE_NOTIFICATION isn't being passed into the MessageBox indicating that it's a non-interactive service who is throwing the box.
Setting this option in the web.config (or machine.config) works around the issue by not even getting into AssertWrapper.
<configuration>
<system.diagnostics>
<assert assertuienabled="false" logfilename="c:\log.txt"/>
</system.diagnostics>
</configuration>
This looks like a bug in ASP.NET 2.0, but we'll see.
The workarounds are:
- Don't use Trace.Fail()
or
- Set AssertUIEnabled="false" in your web.config.
The strange part about this is why it's seen on some systems and not others.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
I'm glad to know there's a solution to the problem.
Comments are closed.
Out of curiosity, would a third workaround be to remove the DefaultTraceListener? According to the MSDN docs on Trace.Fail: