العنوان الأصلي: http://blogs.msdn.com/tess/archive/2006/10/10/ASP.NET-Crash-_2D00_-Crazy-looping-in-a-SiteMap.aspx
تم النشر:الثلاثاء 10 تشرين الأول (أكتوبر) 2006 الساعة 4:10 مساءً
المؤلف: تيس
في أحد الأيام، تلقيت رسالة بريد إلكتروني حول مدونتي تطرح الأسئلة التالية، والتي تم وصفها باختصار على النحو التالي:
أردت إنشاء خريطة موقع بسرعة، لذلك تجاوزت طريقة BuildSiteMap()، وكتبت بداخلها حلقة أضافت بعض عقد خريطة الموقع المزيفة.
التجاوز العام SiteMapNode BuildSiteMap(){
لـ (int i = 0; i < 5; i++)
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
إرجاع myRoot;
}
عند تشغيل البرنامج، يحدث تجاوز سعة المكدس ويتعطل الخادم. لقد استخدمت مصحح الأخطاء للتنقل ووجدت شيئًا غريبًا حقًا:
1) int i = 0
2) ط <5
3) جذر...
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...
لذا فإن callstack يبدو قليلاً مثل هذا ....
...
دالة ب ()
دالة (أ)
دالة ب ()
دالة (أ)
حسنًا، كل هذا جيد وجيد، ولكن هذا يفسر فقط Stack Overflow. إذن ما الذي يحدث مع هذه الدورة المجنونة؟
حسنًا...تخيل وجود مثل هذه الوظيفة (توجد نقطة توقف عند -->)
باطلة MyRecursiveFunction () {
ل(int i=0;i<5;i++){
--> MyRecursiveFunction();
}
}
عندما تتوقف عند نقطة التوقف لأول مرة، يجب أن تكون قيمة i 0، وستبدو مجموعة الاستدعاءات هكذا...
دالة متكررة ()
...
الآن قم باستدعاء الدالة 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++){
...
}
}
}
}
}
}
}
...بالنظر إليها في الاستوديو المرئي، يبدو أن نفس الحلقة يتم تشغيلها دائمًا ولا تغير قيمة المتغير i. في الوقت الحالي، لن يكون لديك فهم عميق لهذا حتى ترى استدعاء المكدس فعليًا.
إذا نظرنا إلى مكدس الاستدعاءات، فإن مكدس الاستدعاءات يبدو الآن كما يلي...
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): تجاوز سعة المكدس - الكود 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> !مكدس النفايات
معرف مؤشر ترابط نظام التشغيل: 0xddc (16)
الإطار الحالي: (MethodDesc 0x68b03720 +0x4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
ChildEBP RetAddr المتصل، 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())
...
حسنًا، يبدو أن المشكلة تكمن في خاصية 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