Original address: http://blogs.msdn.com/tess/archive/2006/10/10/ASP.NET-Crash-_2D00_-Crazy-looping-in-a-SiteMap.aspx
Published: Tuesday, October 10, 2006 4:10 PM
Author: Tess
One day, I received an email about my blog asking the following questions, briefly described as follows:
I wanted to create a sitemap quickly, so I overridden the BuildSiteMap() method, and inside it I wrote a loop that added some fake sitemap nodes.
public override SiteMapNode BuildSiteMap(){
for (int i = 0; i < 5; i++)
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
return myRoot;
}
When running the program, a stack overflow occurs and the server crashes. I used the debugger to step through and found something really strange:
1) int i = 0
2) i < 5
3) myRoot...
4) int i = 0
5) i < 5
etc.
The value of i never seems to increase unless I call to the SiteMapNode (access a property, call a method), which looks like the loop is correct.
What makes this loop indeterminate? At first glance it might be a compiler or CLR bug.
(When I got this problem, I really didn't know about site navigation in ASP.NET 2.0, but I found these articles... http://weblogs. asp.net/scottgu/archive/2005/11/20/431019.aspx and http://aspnet.4guysfromrolla.com/articles/111605-1.aspx , the descriptions are really good.)
initial thoughts
The most important thing about this problem is that it always restarts, which means that it can be debugged on the spot. But let’s not go that far yet, let’s look back and see what we have now...
1. Stack overflow
2. A cycle that starts again and again
I've discussed stack overflow in a previous blog post, and I'll repeat it... Stack overflow occurs when too many function pointers, variable pointers, and parameters are allocated, so that the amount of memory allocated on the stack is not enough. By far the most common cause of stack overflows is unterminating recursion. In other words, function A calls function B, function B calls function A...
So the callstack looks a bit like this....
...
functionB()
functionA()
functionB()
functionA()
Okay, that's all well and good, but that just explains Stack Overflow. So what’s going on with this crazy cycle?
OK...imagine there is such a function (there is a breakpoint at -->)
void MyRecursiveFunction(){
for(int i=0; i<5; i++){
--> MyRecursiveFunction();
}
}
When you first stop at the breakpoint, the value of i should be 0, and the callstack will look like this...
MyRecursiveFunction()
...
Now call the MyRecrusive function. Every time the function itself is called, i=0 will appear again (although we are not really in the same loop). If you call the MyRecrusive function several times and replace it with the actual code that is executed, it will execute code similar to the following:
for(int i=0; i<5; i++){
for(int i2=0; i2<5; i2++){
for(int i3=0; i3<5; i3++){
for(int i4=0; i4<5; i4++){
for(int i5=0; i5<5; i5++){
for(int i6=0; i6<5; i6++){
for(int i7=0; i7<5; i7++){
...
}
}
}
}
}
}
}
...looking at it in visual studio, it looks like the same loop is always run and does not change the value of variable i. For now, you won't have a deep understanding of this until you actually see the stack call.
If we look at the callstack, the callstack now looks like this...
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
...
so the conclusion of the initial thought is that we undoubtedly want to look at some recursion...but where? The code in the example
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
does not seem that complicated...
The most suspicious thing here is new SiteMapNode() and myRoot.ChildNodes.Add(), this is no longer so mysterious if we look at it with reflector.
Debugging issues
Finally:) Less words, more windbg action...
Since it re-renders easily, I will re-render it on my machine by simply attaching windbg (File / Attach to process) to w3wp.exe and hitting g to start. The problem then reappears and the program aborts telling me it's a stack overflow (which we already know).
(7e4.ddc): Stack overflow - code c00000fd (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0fa4235c ebx=02beca74 ecx=02beca74 edx=02becb54 esi=02becb54 edi=02beca74
eip=686b5cb4 esp=02163000 ebp=02163004 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210246
System_Web_ni+0xf5cb4:686b5cb4 56 push esi
Let’s check the stack and use the !clrstack command to see how it was aborted, but we can only see....
0:016> !clrstack
OS Thread ID: 0xddc (16)
ESP EIP 02163000 686b5cb4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode)
... This doesn't really help us much. Sometimes when we encounter stack overflow, some such problems arise using the !clrstack command. Therefore, we also need to use the !dumpstack command to view the raw stack.
0:016> !dumpstack
OS Thread ID: 0xddc (16)
Current frame: (MethodDesc 0x68b03720 +0x4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
ChildEBP RetAddr Caller,Callee
02163004 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
0216300c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
0216303c 686b5cdf (MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
02163074 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
0216307c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
021630ac 686b5cdf (MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
021630e4 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
021630ec 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
0216311c 686b5cdf (MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
02163154 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
0216315c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
...
Okay, it looks like the problem is with the ChildNodes property. When this property is used, the GetChildNodes function is called, which calls the BuildSiteMap function again, which in turn calls the ChildNodes property, and so on, causing a stack overflow.
in conclusion
In the documentation about BuildSitemap you can find the following paragraph:
The BuildSiteMap method is called by the default implementation of the FindSiteMapNode, GetChildNodes, and GetParentNode methods. If you override the BuildSiteMap method in a derived class, make sure that it loads the site map data only once and returns it on subsequent calls.
In order to avoid recursion and stack overflow, it is best to avoid calling this method. Like in the BuildSiteMap example, we can use the AddNode method to add child nodes.
This is archived in the Site Map Providers article, which is also worth reading.
BuildSiteMap should generally not call methods or properties provided by other site maps, because many methods and properties will implement BuildSiteMap calls by default. For example, the RootNode in BuildSiteMap causes recursion, causing it to terminate with a stack overflow.
http://www.cnblogs.com/Ring1981/archive/2006/10/19/443280.html