Исходный адрес: http://blogs.msdn.com/tess/archive/2006/10/10/ASP.NET-Crash-_2D00_-Crazy-looping-in-a-SiteMap.aspx .
Опубликовано: вторник, 10 октября 2006 г., 16:10
Автор: Тесс
Однажды я получила электронное письмо о своем блоге со следующими вопросами, кратко описанными следующим образом:
Я хотел быстро создать карту сайта, поэтому переопределил метод BuildSiteMap() и внутри него написал цикл, который добавлял несколько поддельных узлов карты сайта.
общедоступное переопределение SiteMapNode BuildSiteMap(){
для (int я = 0; я <5; я++)
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
вернуть myRoot;
}
При запуске программы происходит переполнение стека и происходит сбой сервера. Я использовал отладчик и обнаружил что-то действительно странное:
1) int i = 0
2) я < 5
3) мой Root...
4) интервал я = 0
5) я < 5
и т. д.
Кажется, что значение i никогда не увеличивается, если я не вызываю SiteMapNode (обращаюсь к свойству, вызываю метод), что выглядит как правильный цикл.
Что делает этот цикл неопределенным? На первый взгляд это могла быть ошибка компилятора или CLR
(Когда я столкнулся с этой проблемой, я действительно не знал о навигации по сайту в ASP.NET 2.0, но нашел эти статьи... http://weblogs.asp. net/scottgu/archive/2005/11/20/431019.aspx и http://aspnet.4guysfromrolla.com/articles/111605-1.aspx , описания действительно хороши.)
первоначальные мысли
Самое главное в этой проблеме то, что она всегда перезапускается, а это значит, что ее можно отладить на месте. Но не будем пока заходить так далеко, давайте оглянемся назад и посмотрим, что мы имеем сейчас...
1. Переполнение стека
2. Цикл, который начинается снова и снова
Я обсуждал переполнение стека в предыдущем сообщении блога и повторю это... Переполнение стека происходит, когда выделяется слишком много указателей функций, указателей переменных и параметров, поэтому объем памяти, выделенной в стеке, не достаточно. Безусловно, наиболее распространенной причиной переполнения стека является бесконечная рекурсия. Другими словами, функция A вызывает функцию B, функция B вызывает функцию A...
Итак, стек вызовов выглядит примерно так....
...
функцияB()
функцияА()
функцияB()
функцияА()
Хорошо, это все хорошо, но это просто объясняет переполнение стека. Так что же происходит с этим сумасшедшим циклом?
ОК... представьте, что есть такая функция (есть точка останова -->)
void MyRecursiveFunction(){
for(int я=0; я<5; я++){
--> МояРекурсивнаяФункция();
}
}
Когда вы впервые останавливаетесь на точке останова, значение i должно быть равно 0, а стек вызовов будет выглядеть так...
МояРекурсивнаяФункция()
...
Теперь вызовите функцию MyRecrusive. Каждый раз, когда вызывается сама функция, i=0 будет появляться снова (хотя на самом деле мы не находимся в одном и том же цикле). Если вы вызовете функцию MyRecrusive несколько раз и замените ее фактическим исполняемым кодом, она выполнит код, аналогичный следующему:
for(int я=0; я<5; я++){
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++){
...
}
}
}
}
}
}
}
... глядя на это в Visual Studio, кажется, что один и тот же цикл всегда выполняется и не меняет значение переменной i. На данный момент у вас не будет глубокого понимания этого, пока вы действительно не увидите вызов стека.
Если мы посмотрим на стек вызовов, он теперь будет выглядеть так...
MyRecursiveFunction()
МояРекурсивнаяФункция()
МояРекурсивнаяФункция()
МояРекурсивнаяФункция()
МояРекурсивнаяФункция()
МояРекурсивнаяФункция()
МояРекурсивнаяФункция()
...
Итак, первоначальный вывод состоит в том, что мы, несомненно, хотим рассмотреть некую рекурсию... но где? Код в примере
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
не кажется таким уж сложным...
Самое подозрительное здесь new SiteMapNode() и myRoot.ChildNodes.Add(), это уже не так загадочно, если мы посмотрим на это с помощью рефлектора.
Проблемы с отладкой
Наконец :) Меньше слов, больше динамичного действия...
Поскольку он легко перерисовывается, я перерисую его на своей машине, просто подключив Windbg (Файл / Прикрепить к процессу) к w3wp.exe и нажав g, чтобы начать. Затем проблема появляется снова, и программа прерывает работу, сообщая мне, что это переполнение стека (о чем мы уже знаем).
(7e4.ddc): переполнение стека — код c00000fd (первый шанс)
Об исключениях первого шанса сообщается до начала обработки исключений.
Это исключение можно ожидать и обработать.
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
Идентификатор потока ОС: 0xddc (16)
ESP EIP 02163000 686b5cb4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode)
... Это нам особо не поможет. Иногда, когда мы сталкиваемся с переполнением стека, такие проблемы возникают при использовании команды !clrstack. Поэтому нам также необходимо использовать команду !dumpstack для просмотра необработанного стека.
0:016> !dumpstack
Идентификатор потока ОС: 0xddc (16)
Текущий кадр: (MethodDesc 0x68b03720 +0x4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
ChildEBP RetAddr вызывающий абонент, вызываемый абонент
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())
...
Ладно, похоже, проблема в свойстве ChildNodes. При использовании этого свойства вызывается функция GetChildNodes, которая снова вызывает функцию BuildSiteMap, которая, в свою очередь, вызывает свойство ChildNodes и т. д., вызывая переполнение стека.
в заключение
В документации по BuildSitemap вы можете найти следующий абзац:
Метод BuildSiteMap вызывается реализацией по умолчанию методов FindSiteMapNode, GetChildNodes и GetParentNode. Если вы переопределяете метод BuildSiteMap в производном классе, убедитесь, что он загружает данные карты сайта только один раз и возвращает их при последующих вызовах.
Чтобы избежать рекурсии и переполнения стека, лучше избегать вызова этого метода. Как и в примере BuildSiteMap, мы можем использовать метод AddNode для добавления дочерних узлов.
Это заархивировано в статье «Поставщики карты сайта», которую также стоит прочитать.
BuildSiteMap обычно не следует вызывать методы или свойства, предоставленные другими картами сайта, поскольку многие методы и свойства по умолчанию реализуют вызовы BuildSiteMap. Например, RootNode в BuildSiteMap вызывает рекурсию, что приводит к ее завершению с переполнением стека.
http://www.cnblogs.com/Ring1981/archive/2006/10/19/443280.html