ที่อยู่เดิม: http://blogs.msdn.com/tess/archive/2006/10/10/ASP.NET-Crash-_2D00_-Crazy-looping-in-a-SiteMap.aspx
เผยแพร่: วันอังคารที่ 10 ตุลาคม 2549 16:10 น
ผู้แต่ง: Tess
วันหนึ่ง ฉันได้รับอีเมลเกี่ยวกับบล็อกของฉัน โดยถามคำถามต่อไปนี้ โดยมีคำอธิบายสั้นๆ ดังนี้
ฉันต้องการสร้างแผนผังเว็บไซต์อย่างรวดเร็ว ดังนั้นฉันจึงลบล้างเมธอด BuildSiteMap() และภายในนั้น ฉันเขียนลูปที่เพิ่มโหนดแผนผังเว็บไซต์ปลอมบางส่วน
การแทนที่สาธารณะ SiteMapNode BuildSiteMap(){
สำหรับ (int i = 0; i < 5; i++)
myRoot.ChildNodes.Add(SiteMapNode ใหม่ (นี้, i.ToString(), i.ToString(), i.ToString()));
ส่งคืน myRoot;
}
เมื่อรันโปรแกรม จะเกิดสแต็กโอเวอร์โฟลว์และเซิร์ฟเวอร์ล่ม ฉันใช้ดีบักเกอร์เพื่อก้าวผ่านและพบบางสิ่งที่แปลกมาก:
1) int i = 0
2) ฉัน < 5
3) รากของฉัน...
4) int i = 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. วงจรที่เริ่มต้นครั้งแล้วครั้งเล่า
ฉันได้กล่าวถึง stack overflow ในโพสต์บล็อกที่แล้ว และฉันจะทำซ้ำอีกครั้ง... Stack overflow เกิดขึ้นเมื่อมีตัวชี้ฟังก์ชัน ตัวชี้ตัวแปร และพารามิเตอร์มากเกินไป เพื่อไม่ให้จำนวนหน่วยความจำที่จัดสรรบนสแต็ก เพียงพอ. สาเหตุที่พบบ่อยที่สุดของสแต็กโอเวอร์โฟลว์คือการยกเลิกการเรียกซ้ำ กล่าวอีกนัยหนึ่ง ฟังก์ชัน A เรียกฟังก์ชัน B ฟังก์ชัน B เรียกฟังก์ชัน A...
Callstack หน้าตาก็จะประมาณนี้....
-
ฟังก์ชั่นB()
ฟังก์ชั่นA()
ฟังก์ชั่นB()
ฟังก์ชั่นA()
โอเค นั่นคือทั้งหมดที่ดีและดี แต่นั่นเป็นเพียงการอธิบาย Stack Overflow เกิดอะไรขึ้นกับวงจรอันบ้าคลั่งนี้?
ตกลง...ลองนึกภาพว่ามีฟังก์ชันดังกล่าว (มีเบรกพอยต์ที่ -->)
เป็นโมฆะ MyRecursiveFunction(){
สำหรับ(int i=0; i<5; i++){
--> MyRecursiveFunction();
-
-
เมื่อคุณหยุดที่จุดพักครั้งแรก ค่าของ i ควรเป็น 0 และ callstack จะมีลักษณะดังนี้...
MyRecursiveFunction()
-
ตอนนี้เรียกใช้ฟังก์ชัน MyRecrusive ทุกครั้งที่เรียกใช้ฟังก์ชัน i=0 จะปรากฏขึ้นอีกครั้ง (แม้ว่าเราจะไม่ได้อยู่ในวงเดียวกันก็ตาม) ถ้าคุณเรียกใช้ฟังก์ชัน MyRecrusive หลายครั้ง และแทนที่ด้วยโค้ดจริงที่ถูกดำเนินการ ฟังก์ชันจะดำเนินการโค้ดที่คล้ายกับต่อไปนี้:
สำหรับ(int i=0; i<5; i++){
สำหรับ (int i2=0; i2<5; i2++){
สำหรับ (int i3=0; i3<5; i3++){
สำหรับ (int i4=0; i4<5; i4++){
สำหรับ (int i5=0; i5<5; i5++){
สำหรับ (int i6=0; i6<5; i6++){
สำหรับ (int i7=0; i7<5; i7++){
-
-
-
-
-
-
-
-
...เมื่อดูใน Visual Studio ดูเหมือนว่าวนซ้ำเดิมจะทำงานอยู่เสมอและไม่เปลี่ยนค่าของตัวแปร i สำหรับตอนนี้ คุณจะไม่มีความเข้าใจอย่างลึกซึ้งเกี่ยวกับเรื่องนี้จนกว่าคุณจะเห็นการเรียกสแต็กจริงๆ
หากเราดูที่ Callstack ตอนนี้ Callstack จะเป็นดังนี้...
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
...
ดังนั้นบทสรุปของความคิดเริ่มแรกก็คือ ไม่ต้องสงสัยเลยว่าเราต้องการดูการเรียกซ้ำบ้าง...แต่ที่ไหนล่ะ? โค้ดในตัวอย่าง
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
ดูเหมือนจะไม่ซับซ้อนขนาดนั้น...
สิ่งที่น่าสงสัยที่สุดที่นี่คือ new SiteMapNode() และ myRoot.ChildNodes.Add() นี่จะไม่ลึกลับอีกต่อไปหากเรามองมันด้วยตัวสะท้อนแสง
ปัญหาการแก้ไขข้อบกพร่อง
สุดท้ายนี้ :) พูดน้อยลง การกระทำของ windbg มากขึ้น...
เนื่องจากมันเรนเดอร์ใหม่ได้อย่างง่ายดาย ฉันจะเรนเดอร์มันใหม่บนเครื่องของฉันโดยแนบ windbg (ไฟล์ / แนบไปที่กระบวนการ) ไปที่ w3wp.exe แล้วกด g เพื่อเริ่มต้น ปัญหาก็ปรากฏขึ้นอีกครั้งและโปรแกรมยกเลิกการบอกฉันว่าเป็นสแต็กโอเวอร์โฟลว์ (ซึ่งเรารู้อยู่แล้ว)
(7e4.ddc): Stack overflow - รหัส c00000fd (โอกาสครั้งแรก)
มีการรายงานข้อยกเว้นโอกาสครั้งแรกก่อนการจัดการข้อยกเว้นใดๆ
ข้อยกเว้นนี้อาจคาดหวังและจัดการได้
eax=0fa4235c ebx=02beca74 ecx=02beca74 edx=02beca74 esi=02beca74 edi=02beca74
eip=686b5cb4 esp=02163000 ebp=02163004 iopl=0 nv ขึ้น 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> !ถังขยะ
รหัสเธรดระบบปฏิบัติการ: 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 เพื่อเพิ่มโหนดลูกได้
ข้อมูลนี้ถูกเก็บถาวรไว้ในบทความ Site Map Providers ซึ่งก็ควรค่าแก่การอ่านเช่นกัน
โดยทั่วไป BuildSiteMap ไม่ควรเรียกเมธอดหรือคุณสมบัติที่แผนผังเว็บไซต์อื่นจัดเตรียมไว้ให้ เนื่องจากเมธอดและคุณสมบัติหลายอย่างจะใช้การเรียก BuildSiteMap ตามค่าเริ่มต้น ตัวอย่างเช่น RootNode ใน BuildSiteMap ทำให้เกิดการเรียกซ้ำ ส่งผลให้การเรียกซ้ำสิ้นสุดลงด้วยสแต็กโอเวอร์โฟลว์
http://www.cnblogs.com/Ring1981/archive/2006/10/19/443280.html