作者:tess
原文地址:ASP.NET Crash - Crazy looping in a SiteMap

本文只是一个快速的概括性翻译, 如果敢兴趣还请您看原版.

我想快速创建一个并不美观的站点地图(site map), 所以我只简单的在我override的BuildSiteMap()函数里使用一个小循环添加了几个地图节点(sitemap node)

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;
}

当我运行这个web应用程序的时候我得到的却是"堆栈溢出(Stack overflow)",然后服务器就崩溃了. 然后我借助调试器调试这段代码,我所看到的东西非常奇怪:

1int i = 0
2) i < 5
3) myRoot
4int i = 0
5) i < 5
etc. 

看起来i的值并没有增加,是编译器的bug还是CLR的?(暂时撇开sitemap的内部机制, 因为sitemap从设计上不允许我们这么使用,但是这个条语句你却可以随便写的)

在debug之前我们向后退几步重审一下:

  1. 堆栈溢出
  2. 一个看起来像是无尽的循环

造成堆栈溢出是因为我们已经占用了大量的内存,而它们是当初为了在栈上分配太多的指向指向局部变量或参数的指针而服务的.(we have exceeded the amount of memory reserved for the stack by allocating too many function pointers, pointers to local vars and parameters on the stack.)但是往往我们都是因为死循环(never-ending recursion)而造成的, 换言之, funcitonA()调用了functionB(),而functionB()里又调用了functionA().

void MyRecursiveFunction(){
     
for(int i=0; i<5; i++){
-->      MyRecursiveFunction();
     }

}
 

所以此时我们的调用栈(callstack)看起来应该是这样的:

functionB()
functionA()
functionB()
functionA()

好,那我们现在想象一下如果你现在有这样的函数:

当你第一次在断点位置上停止的时候i的值应该是0,调用栈看起来应该是

MyRecursiveFunction()
...

现在我们用另一种方式执行进入函数内部(其实还是它本身)

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++){
                     
                  }
               }
            }
         }
      }
   }
}

所以调用栈应该是这样的:

MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
 

调试:

将windbg附属到w3wp.exe的进程上(文件/附属到进程),按下g运行.程序一会就终止了显示出了下列信息,表明是堆栈溢出(就像我们已经知道的一样)

(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

如果我们用!clrstack看一下堆栈来确定我们是怎么结束的,我们只能看到这个:

0:016> !clrstack
OS Thread Id: 0xddc (16)
ESP EIP 
02163000 686b5cb4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode)

可惜的这些不能告诉我们更多的东西, 当我们陷入堆栈溢出错误的时候!clrstack有时会在列举堆栈信息上出现一些问题,所以我们必须使用!dumpstack来看一下.
(注意:!dumpstack不展示真实的堆栈,有一些函数可能是错误的,但是它能很好的让我们知道究竟怎么了)

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())
...

Ok,看起来问题出在ChildNodes属性上了, 它调用GetChildNodes()函数,而GetChildNodes()又再次调用我们的BuildSiteMap函数, 该函数调用了ChildNodes属性, 所以这样造成一个死循环.

结论:

创建站点地图一文中你可以找到答案和解决文章开始时问题的正确处理方法.

posted on 2006-10-12 14:59  维生素C.NET  阅读(3391)  评论(2编辑  收藏  举报