ASP.NET Crash: StackOverflowException with Server.Transfer

5 minute read

I have written a few posts about StackOverflowExceptions, here, here, here and here. The one I am going to talk about today is one of those unfortunate cases where you are trying to do the right thing and still shoot yourself in the foot.

Problem description

Randomly when browsing the application we get the Internet Explorer cannot display the webpage page, and the following event is found in the system event log

Event Type:     Warning
Event Source:   W3SVC
Event Category: None
Event ID:       1009
Date:           2008-05-06
Time:           10:04:26
User:           N/A
Computer:       MYMACHINE
Description:
A process serving application pool 'DefaultAppPool' terminated unexpectedly. The process id was '4436'. The process exit code was '0x800703e9'.

For more information, see Help and Support Center. 0x800703e9 means Recursion too deep; the stack overflowed so we are dealing with a stack overflow as the title of the post suggests, i.e. that we are exhausting the memory allocated for the stack which most often happens when you run into a situation where you have infinite recursion, i.e. methods calling themselves either directly or in a chain, and there is no stop condition.

Debugging the issue

If you want more detail on how to debug these types of issues in general you might want to check out some of the posts I mentioned earlier. To save some space I will go straight to the core and get a memory dump when the stack overflow occurs, using the config file unknown.cfg that you can find attached to this post which happens to be a hands-on lab on stack overflow exceptions.

I gather a memory dump of the asp.net process (w3wp.exe), when the stack overflow occurs, with the following command:

adplus -pn w3wp.exe -c unknown.cfg

Then I open up the 1st_Chance_Unknown_Exception dump in windbg and load sos.dll using .loadby sos mscorwks. At this point, since the unknown exception was what triggered the dump, I will be situated on the thread that caused the unknown exception, i.e. the stack overflow exception.

If we look at the stack it’s tempting to assume that the culprit is the call to GetResourceFromDefault since this is at the top of the stack. In reality this is just the drop of water that make the cup run over. Our real issue is what made it so full to begin with. It’s the same thing when you debug

0:024> !clrstack
OS Thread Id: 0x14ac (24)
ESP       EIP
0febaca0 7d4e2366 [HelperMethodFrame_2OBJ: 0febaca0] System.Environment.GetResourceFromDefault(System.String)
0febad00 793f7575 System.NullReferenceException..ctor()
0febaf34 79e7c74b [GCFrame: 0febaf34]
0febb050 79e7c74b [GCFrame: 0febb050]
0febb0b4 79e7c74b [GCFrame: 0febb0b4]
0febb6f4 0fe1076b ErrorPage.Page_Load(System.Object, System.EventArgs)
0febb728 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0febb738 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0febb748 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0febb758 6613cb50 System.Web.UI.Control.LoadRecursive()
0febb76c 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0febb968 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0febb9a0 6614d80f System.Web.UI.Page.ProcessRequest()
0febb9d8 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0febb9e0 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0febb9f4 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0febb9f8 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
0febbabc 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0febbb1c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0febbb30 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0febbb3c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0febc878 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0febc888 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0febc898 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0febc8a8 6613cb50 System.Web.UI.Control.LoadRecursive()
0febc8bc 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0febcab8 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0febcaf0 6614d80f System.Web.UI.Page.ProcessRequest()
0febcb28 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0febcb30 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0febcb44 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0febcb48 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
0febcc0c 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0febcc6c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0febcc80 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0febcc8c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0febd9c8 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
...<cut to save space/>
fecf25c 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0fecf2bc 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0fecf2d0 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0fecf2dc 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0fed0018 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0fed0028 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0fed0038 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0fed0048 6613cb50 System.Web.UI.Control.LoadRecursive()
0fed005c 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0fed0258 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0fed0290 6614d80f System.Web.UI.Page.ProcessRequest()
0fed02c8 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0fed02d0 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0fed02e4 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0fed02e8 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
0fed03ac 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0fed040c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0fed0420 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0fed042c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0fed1168 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0fed1178 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0fed1188 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0fed1198 6613cb50 System.Web.UI.Control.LoadRecursive()
0fed11ac 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0fed13a8 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0fed13e0 6614d80f System.Web.UI.Page.ProcessRequest()
0fed1418 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0fed1420 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0fed1434 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0fed1438 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
0fed14fc 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0fed155c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0fed1570 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0fed157c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0fed22b8 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0fed22c8 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0fed22d8 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0fed22e8 6613cb50 System.Web.UI.Control.LoadRecursive()
0fed22fc 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0fed24f8 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0fed2530 6614d80f System.Web.UI.Page.ProcessRequest()
0fed2568 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0fed2570 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0fed2584 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0fed2588 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
...

In this case we can see that the stack starts off with a call to ErrorPage.aspx, so we are probably handling some type of error condition, then it calls into Server.Transfer, and this again starts processing errorpage.aspx and so it continues. If you look closely the section marked in maroon keeps repeating over and over. In fact I cut out part of the stack to save some space so in reality this pattern repeated itself probably 20 times before we finally filled up the stack.

This is the code for ErrorPage.Page_Load simplified. It follows a pretty common pattern of try/catch and transferring to an error page in the case we can’t handle the exception.

protected void Page_Load(object sender, EventArgs e)
{
    try
    {
        Response.Write(((Exception)Session["CurrentException"]).Message.ToString() + " ");
        Response.Write("occurred on page " + Request.UrlReferrer.AbsoluteUri.ToString());
        Session["CurrentException"] = null;
    }
    catch (Exception ex)
    {
        Server.Transfer(Application["ErrorPage"].ToString());
    }
}

The problem here is that if an exception occurs in ErrorPage.aspx we will transfer to ourselves, and chances are big that when executing the next time the issue will not have gone away so we simply keep transferring to ourselves until we finally run out of stack space.

Same pattern in login.aspx

Another common place where we see these auto transfers is in login.aspx, i.e. when you have a setup where if you are not logged in you will be transferred to login.aspx which is perfectly fine, but then when you try to transfer back to the referring page once you are logged in you can run into this issue if you are not careful, if the user has logged on to the page directly.

Resolution

The solution in this case is thankfully very simple. If you get an error on the error page you should not transfer to the errorpage. This may seem pretty logical when you figure out what the issue is, but it’s pretty easy to end up with code like this, especially when the pattern is not as straight forward as this one.

While I am on the subject, my personal recommendation would be to not use an aspx page for the error page in case the issue is more general, like an out of memory exception for example. In a case like that an aspx error page would fail just like any other aspx page so for a more stable error handling routine, a static page like an html page is usually best for the error page.

Laters, Tess