سؤال

لدي تطبيق خدمة Windows المدارة لنظام Windows يتعطل من حين لآخر في الإنتاج بسبب StackoverFlowException المدارة. أعلم أن هذا لأنني قمت بتشغيل ADPLUS في وضع التعطل وتحليل الوفاة تفريغ التعطل باستخدام SOS. لقد أرفقت Debugger WINDBG وتعيينها على "استثناء غير معالج".

مشكلتي هي أنه لا يمكنني رؤية أي من المداخن المدارة أو التبديل إلى أي من مؤشرات الترابط. انهم جميعا ممزقة بحلول الوقت الذي يكسر مصحح الأخطاء.

أنا لست خبير WINDBG، وعلى الرغم من تثبيت Visual Studio على النظام المباشر أو استخدام تصحيح الأخطاء عن بعد وتصحيح الأخطاء باستخدام هذه الأداة، هل لدى أي شخص أي اقتراحات حول كيفية الحصول على تتبع مكدس من الخيط المخالف؟

إليك ما أفعله.

!الخيوط

...

XXXX 11 27C 000000001B2175F0 B220 المعوقين 00000000072C9058: 00000000072CAD80 0000000019BDDD3F0 0 UKN System.stackoverflowException (0000000000c010d0)

...

وفي هذه المرحلة، ترى معرف XXXX الذي يشير إلى موضوع ميت جدا.

هل كانت مفيدة؟

المحلول

بمجرد أن تصل إلى فائض المكدس، فأنت بعيد جدا عن التوفيق في تصحيح المشكلة - تهب مساحة المكدس الخاصة بك تترك برنامجك في حالة غير محددة، لذلك لا يمكنك الاعتماد عليها أي من المعلومات الموجودة فيه في تلك المرحلة - أي تتبع المكدس التي تحاول أن تكون تالفة ويمكن أن تشير إليكم بسهولة في الاتجاه الخاطئ. أي، بمجرد حدوث StackoverFlowException، فقد فات الأوان.

أيضا، وفقا ل وثائق لا يمكنك التقاط StackoverflowException من .NET 2.0 فصاعدا، وبالتالي فإن الاقتراحات الأخرى لتطلع التعليمات البرمجية الخاصة بك بمحاولة / معلقة لهذا المحتمل أن تعمل. هذا يجعل هذا منطقي مثالي، بالنظر إلى الآثار الجانبية لسريع المكدس (أنا مندهش. NET سمح لك بالقبض عليه).

الخيار الحقيقي الوحيد هو الانخراط في مليلات تحليل التعليمات البرمجية، والبحث عن أي شيء يمكن أن يتسبب في تجاوز سعة مكدس، ووضع نوع من العلامات حتى تتمكن من الحصول على فكرة عند حدوثها قبل تحدث. على سبيل المثال، من الواضح أن أي طرق متكررة هي المقام الأول للبدء، لذلك أعطهم عداد عمق و رمي استثناء خاص بك إذا وصلوا إلى بعض القيمة "غير المعقولة" التي تحددها، بهذه الطريقة يمكنك في الواقع الحصول على تتبع مكدس صالح.

نصائح أخرى

هل هو خيار لف التعليمات الخاصة بك مع try-catch أن يكتب إلى EventLog (أو ملف، أو أيا كان) وتشغيل هذا التصحيح لمرة واحدة؟

try { ... } catch(SOE) { EventLog.Write(...); throw; }

لن تكون قادرا على التصحيح، لكنك ستحصل على تتبع المكدس.

خيار واحد لديك هو استخدام كتلة TRY / CATCK على مستوى عال، ثم قم بطباعة أو تسجيل تتبع المكدس المقدم من الاستثناء. كل استثناء لديه StackTrace الممتلكات التي يمكن أن تخبرك حيث ألقيت منها. لن يسمح لك بذلك بأي تصحيح تفاعلي، لكن يجب أن يمنحك مكانا للبدء.

ما يستحقه، بدءا من .NET 4.0، Visual Studio (وأي موظفين يعتمدون على ICorDebug API) كسب القدرة على تصحيح miniumbss. هذا يعني أنك ستتمكن من تحميل تفريغ التعطل في مصحح الأخطاء مقابل جهاز كمبيوتر مختلف وشاهد المكدسات المدارة مشابهة إذا كنت قد أرفقت مصححا في وقت التعطل. انظر نتحدث PDC أو بلوق ريك byers للمزيد من المعلومات. لسوء الحظ، لن يساعدك هذا في مشكلة في متناول اليد، ولكن ربما تكون في المرة القادمة التي تواجه هذه المشكلة.

إلقاء نظرة على سجل Debug Mode Crashus الخاص بك. معرفة ما إذا كانت هناك أي انتهاكات وصول أو استثناءات تجاوز الفطائر الأصلية الحقيقية التي تحدث قبل أن يتم إلقاء فترة stackoverflowEx المدارة.

تخميني هو أن هناك استثناء على مكدس الخيط الذي تقوم به الباردة قبل خروج الخيط.

يمكنك أيضا استخدام Debugdiag من www.iis.net ثم قم بتعيين قاعدة تعطل وإنشاء ملف تفريغ كامل لانتهاكات الوصول (SXE AV) والاستثناءات الأصلية التي تجاوزت المكدس (SXE SOV)

شكرا، هارون

لدي فئة recursionchecker لهذا النوع من الأشياء. أنا هنا إخلاء حقوق الطبع والنشر على الرمز أدناه.

يتعلق الأمر بما إذا كان يجد نفسه يضرب التحقق من الكائن المستهدف في كثير من الأحيان. انها ليست كل نهاية جميع؛ يمكن أن تسبب الحلقات إيجابيات خاطئة، على سبيل المثال. يمكن للمرء تجنب ذلك من خلال إجراء مكالمة أخرى بعد الكود المحفوف بالمخاطر، وروي المدقق أنه يمكن أن يقلل من مكالمة توزيعها للكائن الهدف. لا يزال لن يكون مقاوم للرصاص.

لاستخدامها، أنا فقط اتصل

public void DangerousMethod() {
  RecursionChecker.Check(someTargetObjectThatWillBeTheSameIfWeReturnHereViaRecursion);
  // recursion-risky code here.
}

هنا هي فئة RecursionHecker:

/// <summary>If you use this class frequently from multiple threads, expect a lot of blocking. In that case,
/// might want to make this a non-static class and have an instance per thread.</summary>
public static class RecursionChecker
{
  #if DEBUG
  private static HashSet<ReentrancyInfo> ReentrancyNotes = new HashSet<ReentrancyInfo>();
  private static object LockObject { get; set; } = new object();
  private static void CleanUp(HashSet<ReentrancyInfo> notes) {
    List<ReentrancyInfo> deadOrStale = notes.Where(info => info.IsDeadOrStale()).ToList();
    foreach (ReentrancyInfo killMe in deadOrStale) {
      notes.Remove(killMe);
    }
  }
  #endif
  public static void Check(object target, int maxOK = 10, int staleMilliseconds = 1000)
  {
    #if DEBUG
    lock (LockObject) {
      HashSet<ReentrancyInfo> notes = RecursionChecker.ReentrancyNotes;
      foreach (ReentrancyInfo note in notes) {
        if (note.HandlePotentiallyRentrantCall(target, maxOK)) {
          break;
        }
      }
      ReentrancyInfo newNote = new ReentrancyInfo(target, staleMilliseconds);
      newNote.HandlePotentiallyRentrantCall(target, maxOK);
      RecursionChecker.CleanUp(notes);
      notes.Add(newNote);
    }
    #endif
  }
}

فصول المساعد أدناه:

internal class ReentrancyInfo
{
  public WeakReference<object> ReentrantObject { get; set;}
  public object GetReentrantObject() {
    return this.ReentrantObject?.TryGetTarget();
  }
  public DateTime LastCall { get; set;}
  public int StaleMilliseconds { get; set;}
  public int ReentrancyCount { get; set;}
  public bool IsDeadOrStale() {
    bool r = false;
    if (this.LastCall.MillisecondsBeforeNow() > this.StaleMilliseconds) {
      r = true;
    } else if (this.GetReentrantObject() == null) {
      r = true;
    }
    return r;
  }
  public ReentrancyInfo(object reentrantObject, int staleMilliseconds = 1000)
  {
    this.ReentrantObject = new WeakReference<object>(reentrantObject);
    this.StaleMilliseconds = staleMilliseconds;
    this.LastCall = DateTime.Now;
  }
  public bool HandlePotentiallyRentrantCall(object target, int maxOK) {
    bool r = false;
    object myTarget = this.GetReentrantObject();
    if (target.DoesEqual(myTarget)) {
      DateTime last = this.LastCall;
      int ms = last.MillisecondsBeforeNow();
      if (ms > this.StaleMilliseconds) {
        this.ReentrancyCount = 1;
      }
      else {
        if (this.ReentrancyCount == maxOK) {
          throw new Exception("Probable infinite recursion");
        }
        this.ReentrancyCount++;
      }
    }
    this.LastCall = DateTime.Now;
    return r;
  }
}

public static class DateTimeAdditions
{
  public static int MillisecondsBeforeNow(this DateTime time) {
    DateTime now = DateTime.Now;
    TimeSpan elapsed = now.Subtract(time);
    int r;
    double totalMS = elapsed.TotalMilliseconds;
    if (totalMS > int.MaxValue) {
      r = int.MaxValue;
    } else {
      r = (int)totalMS;
    }
    return r;
  }
}

public static class WeakReferenceAdditions {
  /// <summary> returns null if target is not available. </summary>
  public static TTarget TryGetTarget<TTarget> (this WeakReference<TTarget> reference) where TTarget: class 
  {
    TTarget r = null;
    if (reference != null) {
      reference.TryGetTarget(out r);
    }
    return r;
  }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top