2013-10-16 15 views
7

समस्याहैंडल रिसाव के स्रोत का पता लगाने के लिए कैसे

मैं सिर्फ कल कुछ प्रदर्शन लॉगिंग में डाल के रूप में मैं, कार्य प्रबंधक देख काफी कुछ समय पहले से एक हैंडल रिसाव देखा हालांकि यह फिक्सिंग कम प्राथमिकता किया गया है। यह प्रत्येक 10 सेकंड में एक नमूना के साथ एक रातोंरात दौड़ है।

मैंने अभी तक विफलता के लिए इसे नहीं चलाया है, समय की बाधाओं के कारण और मेरा टेस्ट कंप्यूटर भी मेरा देव कंप्यूटर है इसलिए कोड लिखते समय इसे चलाना आदर्श नहीं है ... इसलिए मुझे यकीन नहीं है कि यह कब होगा दुर्घटना, लेकिन मुझे अत्यधिक संदेह है कि यह केवल समय की बात है।

Graph of application resource usages and performance

नोट: लाल क्षेत्र में बॉक्सिंग वह जगह है जहाँ मैं काम कर पाश "बंद कर दिया" और एक छोटी विराम के बाद इसे पुन: प्रारंभ। थ्रेड "स्टॉप" पर ~ 100 से ~ 20 तक गिर गए। हैंडल तब तक नहीं गिरते जब तक कि लूप को ~ 62,000 से ~ 40,000 तक लगभग 30 सेकंड के बाद पुनरारंभ नहीं किया गया। तो कुछ हैंडल जीसीएड प्राप्त कर रहे हैं, जितना मैं उम्मीद करता हूं उतना ही नहीं। मैं यह नहीं समझ सकता कि इन सभी हैंडल को एकत्रित होने से या जहां वे मूल रूप से आ रहे हैं (यानी कार्य, जीयूआई, फाइल इत्यादि) से कौन सा रूट रोक रहा है।

यदि आपके पास पहले से ही कोई विचार है कि इस समस्या का कारण क्या हो सकता है, तो आगे पढ़ने की आवश्यकता नहीं है। मैंने इस मुद्दे को सुलझाने के शॉट-गन शैली दृष्टिकोण में संदर्भ के लिए शेष जानकारी और कोड प्रदान किया है। मैं हटा दूंगा, संपादित कर सकता हूं, क्योंकि मूल कारण कम हो गया है। एक ही टोकन द्वारा, यदि कुछ ब्याज गुम है तो मुझे बताएं और मैं इसे प्रदान करने की कोशिश करूंगा (लॉग, डंप इत्यादि)।


व्हॉट आई हैव डन

अपने ही मैं Tracking Handle Misuse पर इस ट्यूटोरियल के माध्यम से चला और मिल गया है पर जहाँ तक खोजने के लिए जहां हैंडल खुला और बंद डंप फ़ाइलों को देख के रूप में ... हालांकि यह हजारों हैंडल के साथ बहुत जबरदस्त था और मुझे कोई समझ नहीं आया और मुझे लोड करने के लिए प्रतीक प्राप्त करने में परेशानी थी, इसलिए पॉइंटर्स सिर्फ मेरे लिए चिंतित थे।

मैं के माध्यम से अपनी सूची में निम्नलिखित दो जाना अभी तक नहीं है, लेकिन अगर कोई दोस्ताना तरीकों पहले थे सोचा ...

मैं भी है कोड को विभाजित करें, मुझे इस बात का एक और छोटा एप्लीकेशन में संभावित कारण होने का संदेह है और सब कुछ बिना किसी मुद्दे के कचरा इकट्ठा करने के लिए दिखाई देता है (यद्यपि निष्पादन पैटर्न वास्तविक ऐप की तुलना में बहुत सरल था)।

संभावित अपराधियों

मैं 5 फार्म कि केवल एक बार बनाई गई हैं प्रत्येक और फिर छिपा/रूप में की जरूरत दिखाया सहित कई लंबे समय से रहते थे instanced कक्षाएं कि जब तक आवेदन के लिए खुला है पिछले, है। मैं अपने ऑब्जेक्ट कंट्रोलर के रूप में एक मुख्य ऑब्जेक्ट का उपयोग करता हूं और फिर मॉडल और व्यूज़ प्रेजेंटर्स-फर्स्ट पैटर्न में प्रस्तुतियों के ईवेंट के माध्यम से वायर्ड होते हैं।

  • बड़े पैमाने पर उपयोग कस्टम Action, Func और lambdas, जिनमें से कुछ लंबे समय से रहते थे
  • 3 कस्टम हो सकता है:

    नीचे कुछ चीजें मैं इस आवेदन, जो या महत्वपूर्ण नहीं हो सकता में क्या कर रहे हैं घटनाओं के लिए प्रतिनिधि और जो Task एस एसिंक निष्पादन के लिए उत्पन्न कर सकते हैं।

  • Controls पर सुरक्षित रूप से आमंत्रित करने के लिए एक्सटेंशन।
  • बहुत, बहुत भारी Task और Parallel.For/Parallel.Foreach कार्यकर्ता तरीकों को चलाने के लिए (या घटनाओं के रूप में ऊपर उल्लेख किया है) का उपयोग
  • कभी Thread.Sleep (का उपयोग करें), लेकिन इसके बजाय एक कस्टम Sleep.For() जो एक AutoResetEvent उपयोग करता है।

मुख्य लूप

इस आवेदन जाता है जब यह रनिंग के सामान्य प्रवाह ऑफलाइन संस्करण में फ़ाइलों की एक श्रृंखला और एक डिजिटल इनपुट संकेत के मतदान पर एक पाश पर आधारित है ऑनलाइन संस्करण में। नीचे ऑफ़लाइन संस्करण के लिए टिप्पणियों के साथ सुडो-कोड है जो बाहरी लैपटॉप की आवश्यकता के बिना मैं अपने लैपटॉप से ​​चला सकता हूं और उपरोक्त चार्ट क्या निगरानी कर रहा था (मेरे पास ऑनलाइन के लिए हार्डवेयर तक पहुंच नहीं है इस समय मोड)।

public void foo() 
{ 
    // Sudo Code 
    var InfiniteReplay = true; 
    var Stopped = new CancellationToken(); 
    var FileList = new List<string>(); 
    var AutoMode = new ManualResetEvent(false); 
    var CompleteSignal = new ManualResetEvent(false); 
    Action<CancellationToken> PauseIfRequired = (tkn) => { }; 

    // Enumerate a Directory... 

    // ... Load each file and do work 
    do 
    { 
     foreach (var File in FileList) 
     { 
      /// Method stops the loop waiting on a local AutoResetEvent 
      /// if the CompleteSignal returns faster than the 
      /// desired working rate of ~2 seconds 
      PauseIfRequired(Stopped); 

      /// While not 'Stopped', poll for Automatic Mode 
      /// NOTE: This mimics how the online system polls a digital 
      /// input instead of a ManualResetEvent. 
      while (!Stopped.IsCancellationRequested) 
      { 
       if (AutoMode.WaitOne(100)) 
       { 
        /// Class level Field as the Interface did not allow 
        /// for passing the string with the event below 
        m_nextFile = File; 

        // Raises Event async using Task.Factory.StartNew() extension 
        m_acquireData.Raise(); 
        break; 
       } 
      } 

      // Escape if Canceled 
      if (Stopped.IsCancellationRequested) 
       break; 

      // If In Automatic Mode, Wait for Complete Signal 
      if (AutoMode.WaitOne(0)) 
      { 
       // Ensure Signal Transition 
       CompleteSignal.WaitOne(0); 
       if (!CompleteSignal.WaitOne(10000)) 
       { 
        // Log timeout and warn User after 10 seconds, then continue looping 
       } 
      } 
     } 
     // Keep looping through same set of files until 'Stopped' if in Infinite Replay Mode 
    } while (!Stopped.IsCancellationRequested && InfiniteReplay); 
} 

Async घटनाक्रम

नीचे की घटनाओं के लिए विस्तार है और सबसे डिफ़ॉल्ट अतुल्यकालिक विकल्प का उपयोग कर क्रियान्वित कर रहे हैं। 'TryRaising()' एक्सटेंशन केवल प्रतिनिधियों को एक प्रयास में लपेटते हैं और किसी अपवाद को लॉग करते हैं (जबकि वे फिर से फेंक नहीं देते हैं, यह अपवादों को पकड़ने के लिए जिम्मेदार होने के लिए सामान्य कार्यक्रम प्रवाह का हिस्सा नहीं है)।

using System.Threading.Tasks; 
using System; 

namespace Common.EventDelegates 
{ 
    public delegate void TriggerEvent(); 
    public delegate void ValueEvent<T>(T p_value) where T : struct; 
    public delegate void ReferenceEvent<T>(T p_reference); 

    public static partial class DelegateExtensions 
    { 
     public static void Raise(this TriggerEvent p_response, bool p_synchronized = false) 
     { 
      if (p_response == null) 
       return; 

      if (!p_synchronized) 
       Task.Factory.StartNew(() => { p_response.TryRaising(); }); 
      else 
       p_response.TryRaising(); 
     } 

     public static void Broadcast<T>(this ValueEvent<T> p_response, T p_value, bool p_synchronized = false) 
      where T : struct 
     { 
      if (p_response == null) 
       return; 

      if (!p_synchronized) 
       Task.Factory.StartNew(() => { p_response.TryBroadcasting(p_value); }); 
      else 
       p_response.TryBroadcasting(p_value); 
     } 

     public static void Send<T>(this ReferenceEvent<T> p_response, T p_reference, bool p_synchronized = false) 
      where T : class 
     { 
      if (p_response == null) 
       return; 

      if (!p_synchronized) 
       Task.Factory.StartNew(() => { p_response.TrySending(p_reference); }); 
      else 
       p_response.TrySending(p_reference); 
     } 
    } 
} 

जीयूआई सुरक्षित आह्वान

using System; 
using System.Windows.Forms; 
using Common.FluentValidation; 
using Common.Environment; 

namespace Common.Extensions 
{ 
    public static class InvokeExtensions 
    { 
     /// <summary> 
     /// Execute a method on the control's owning thread. 
     /// </summary> 
     /// http://stackoverflow.com/q/714666 
     public static void SafeInvoke(this Control p_control, Action p_action, bool p_forceSynchronous = false) 
     { 
      p_control 
       .CannotBeNull("p_control"); 

      if (p_control.InvokeRequired) 
      { 
       if (p_forceSynchronous) 
        p_control.Invoke((Action)delegate { SafeInvoke(p_control, p_action, p_forceSynchronous); }); 
       else 
        p_control.BeginInvoke((Action)delegate { SafeInvoke(p_control, p_action, p_forceSynchronous); }); 
      } 
      else 
      { 
       if (!p_control.IsHandleCreated) 
       { 
        // The user is responsible for ensuring that the control has a valid handle 
        throw 
         new 
          InvalidOperationException("SafeInvoke on \"" + p_control.Name + "\" failed because the control had no handle."); 

        /// jwdebug 
        /// Only manually create handles when knowingly on the GUI thread 
        /// Add the line below to generate a handle http://stackoverflow.com/a/3289692/1718702 
        //var h = this.Handle; 
       } 

       if (p_control.IsDisposed) 
        throw 
         new 
          ObjectDisposedException("Control is already disposed."); 

       p_action.Invoke(); 
      } 
     } 
    } 
} 

Sleep.For()

using System.Threading; 
using Common.FluentValidation; 

namespace Common.Environment 
{ 
    public static partial class Sleep 
    { 
     public static bool For(int p_milliseconds, CancellationToken p_cancelToken = default(CancellationToken)) 
     { 
      // Used as "No-Op" during debug 
      if (p_milliseconds == 0) 
       return false; 

      // Validate 
      p_milliseconds 
       .MustBeEqualOrAbove(0, "p_milliseconds"); 

      // Exit immediate if cancelled 
      if (p_cancelToken != default(CancellationToken)) 
       if (p_cancelToken.IsCancellationRequested) 
        return true; 

      var SleepTimer = 
       new AutoResetEvent(false); 

      // Cancellation Callback Action 
      if (p_cancelToken != default(CancellationToken)) 
       p_cancelToken 
        .Register(() => SleepTimer.Set()); 

      // Block on SleepTimer 
      var Canceled = SleepTimer.WaitOne(p_milliseconds); 

      return Canceled; 
     } 
    } 
} 
+0

मैं बस इतना कह सकता हूं कि "शुभकामनाएं" ... यह एक कठिन है, और यह आमतौर पर इस (जटिल) तीसरे पक्ष के लिए इस जटिल में कुछ प्रगति करने के लिए बहुत मुश्किल है। लेकिन फिर मेरे साथी सोर्स कभी मुझे आश्चर्यचकित नहीं करते। – Floris

+4

प्रक्रिया एक्सप्लोरर डाउनलोड करें और अपना ऐप चुनें। फिर व्यू -> लोअर फलक - हैंडल के अंतर्गत चुनें। इससे आपको एक ऐसा विचार मिलना चाहिए जो आप लीक कर रहे हैं (म्यूटेक्स, इवेंट, फाइल, ...) यदि यह नामित म्यूटेक्स या फ़ाइल हैंडल है तो आपको सीधे यह पता लगाने का एक अच्छा मौका होना चाहिए कि समस्याएं कहां से आती हैं। –

+0

@AloisKraus हां, मेरे पास पहले से ही पीई है, बस यह नहीं पता कि इसे पूरी तरह से कैसे उपयोग किया जाए। मैं सूची में शायद 200 हैंडल देख रहा हूं हालांकि मेरी ऐप (बस पुनरारंभ) अभी लगभग 2500 का उपयोग कर रही है। ~ 30 'फ़ाइल' और ~ 40' कुंजी 'जो दोनों स्थिर लगते हैं, लेकिन' थ्रेड 'हैंडल के बहुत सारे हैं जिन्हें बनाया और नष्ट किया जा रहा है (लाल/हरा हाइलाइट जोड़ा और हटा दिया जाता है)। सूची में केवल 4 'घटना' और 6 'उत्परिवर्ती' प्रकार। मैं ज्यादातर आग लगाना और भूलना चाहता हूं, मैं उनका निपटान नहीं करता हूं या आमतौर पर प्रतीक्षा करता हूं। मैं कार्य के अंदर अपवाद पकड़ता हूं, उन्हें लॉग इन करता हूं और फिर वापस लौटता हूं ... कार्य को अपने आप पर स्पिन करने देता हूं। – HodlDwon

उत्तर

1

सभी टिप्पणियां अब तक काफी सहायक रहे और मैं कम से कम एक मिल गया है Sleep.For() विधि होने के लिए मेरे हैंडल लीक का स्रोत। मुझे अभी भी लगता है कि मैंने लीकिंग को संभाला है, लेकिन काफी धीमी गति से और मैं अब भी बेहतर समझता हूं क्यों वे लीक कर रहे थे।

इसे पास किए गए टोकन के दायरे के साथ और एक उपयोग कथन में विधि के अंदर स्थानीय टोकन को साफ करना था। एक बार जब मैंने इसे ठीक कर लिया, तो मैंने उन सभी अज्ञात Event को प्रोसेस एक्सप्लोरर में केवल बस बैठने के बजाय बनाया और नष्ट कर दिया।

एक तरफ, मुझे कल रात देर रात Anatomy of a "Memory Leak" मिली और निश्चित रूप से आगे की जांच के लिए विंडबग के बारे में और जानेंगे।

मैं यह देखने के लिए एक लंबे समय तक चलने वाला प्रदर्शन परीक्षण भी कर रहा हूं कि यह एकमात्र रिसाव है या नहीं और मेरे कोड के अन्य अनुभागों की समीक्षा करना है जो प्रतीक्षा करें और यह सुनिश्चित करने के लिए कि मैं ठीक से उनका दायरा और निपटान करता हूं।

फिक्स्ड Sleep.For()

using System.Threading; 
using Common.FluentValidation; 
using System; 

namespace Common.Environment 
{ 
    public static partial class Sleep 
    { 
     /// <summary> 
     /// Block the current thread for a specified amount of time. 
     /// </summary> 
     /// <param name="p_milliseconds">Time to block for.</param> 
     /// <param name="p_cancelToken">External token for waking thread early.</param> 
     /// <returns>True if sleeping was cancelled before timer expired.</returns> 
     public static bool For(int p_milliseconds, CancellationToken p_cancelToken = default(CancellationToken)) 
     { 
      // Used as "No-Op" during debug 
      if (p_milliseconds == 0) 
       return false; 

      // Validate 
      p_milliseconds 
       .MustBeEqualOrAbove(0, "p_milliseconds"); 

      // Merge Tokens and block on either 
      CancellationToken LocalToken = new CancellationToken(); 
      using (var SleeperSource = CancellationTokenSource.CreateLinkedTokenSource(LocalToken, p_cancelToken)) 
      { 
       SleeperSource 
        .Token 
        .WaitHandle 
        .WaitOne(p_milliseconds); 

       return SleeperSource.IsCancellationRequested; 
      } 
     } 
    } 
} 

परीक्षण ऐप्लिकेशन (कंसोल)

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using Common.Environment; 
using System.Threading; 

namespace HandleTesting 
{ 
    class Program 
    { 
     private static CancellationTokenSource static_cts = new CancellationTokenSource(); 

     static void Main(string[] args) 
     { 
      //Periodic.StartNew(() => 
      //{ 
      // Console.WriteLine(string.Format("CPU_{0} Mem_{1} T_{2} H_{3} GDI_{4} USR_{5}", 
      //  Performance.CPU_Percent_Load(), 
      //  Performance.PrivateMemorySize64(), 
      //  Performance.ThreadCount(), 
      //  Performance.HandleCount(), 
      //  Performance.GDI_Objects_Count(), 
      //  Performance.USER_Objects_Count())); 
      //}, 5); 

      Action RunMethod; 
      Console.WriteLine("Program Started...\r\n"); 
      var MainScope_cts = new CancellationTokenSource(); 
      do 
      { 
       GC.Collect(); 
       GC.WaitForPendingFinalizers(); 
       GC.Collect(); 

       try 
       { 
        var LoopScope_cts = new CancellationTokenSource(); 
        Console.WriteLine("Enter number of Sleep.For() iterations:"); 
        var Loops = int.Parse(Console.ReadLine()); 

        Console.WriteLine("Enter millisecond interval per iteration:"); 
        var Rate = int.Parse(Console.ReadLine()); 

        RunMethod =() => SomeMethod(Loops, Rate, MainScope_cts.Token); 

        RunMethod(); 
       } 
       catch (Exception ex) 
       { 
        Console.WriteLine(ex.Message); 
       } 
       Console.WriteLine("\r\nPress any key to try again, or press Escape to exit."); 
      } 
      while (Console.ReadKey().Key != ConsoleKey.Escape); 
      Console.WriteLine("\r\nProgram Ended..."); 
     } 

     private static void SomeMethod(int p_loops, int p_rate, CancellationToken p_token) 
     { 
      var local_cts = new CancellationTokenSource(); 
      Console.WriteLine("Method Executing " + p_loops + " Loops at " + p_rate + "ms each.\r\n"); 
      for (int i = 0; i < p_loops; i++) 
      { 
       var Handles = Performance.HandleCount(); 
       Sleep.For(p_rate, p_token); /*<--- Change token here to test GC and variable Scoping*/ 
       Console.WriteLine("H_pre " + Handles + ", H_post " + Performance.HandleCount()); 
      } 
     } 
    } 
} 

प्रदर्शन (हेल्पर क्लास)

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 
using System.Management; 
using Common.Extensions; 
using System.Diagnostics; 

namespace Common.Environment 
{ 
    public static partial class Performance 
    { 
     //https://stackoverflow.com/a/9543180/1718702 
     [DllImport("User32")] 
     extern public static int GetGuiResources(IntPtr hProcess, int uiFlags); 

     public static int GDI_Objects_Count() 
     { 
      //Return the count of GDI objects. 
      return GetGuiResources(System.Diagnostics.Process.GetCurrentProcess().Handle, 0); 
     } 
     public static int USER_Objects_Count() 
     { 
      //Return the count of USER objects. 
      return GetGuiResources(System.Diagnostics.Process.GetCurrentProcess().Handle, 1); 
     } 
     public static string CPU_Percent_Load() 
     { 
      //http://allen-conway-dotnet.blogspot.ca/2013/07/get-cpu-usage-across-all-cores-in-c.html 
      //Get CPU usage values using a WMI query 
      ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor"); 
      var cpuTimes = searcher.Get() 
       .Cast<ManagementObject>() 
       .Select(mo => 
        new 
        { 
         Name = mo["Name"], 
         Usage = mo["PercentProcessorTime"] 
        } 
       ).ToList(); 

      var Total = cpuTimes[cpuTimes.Count - 1]; 
      cpuTimes.RemoveAt(cpuTimes.Count - 1); 

      var PercentUsage = string.Join("_", cpuTimes.Select(x => Convert.ToInt32(x.Usage).ToString("00"))); 

      return PercentUsage + "," + Convert.ToInt32(Total.Usage).ToString("00"); 
     } 
     public static long PrivateMemorySize64() 
     { 
      using (var P = Process.GetCurrentProcess()) 
      { 
       return P.PrivateMemorySize64; 
      } 
     } 
     public static int ThreadCount() 
     { 
      using (var P = Process.GetCurrentProcess()) 
      { 
       return P.Threads.Count; 
      } 
     } 
     public static int HandleCount() 
     { 
      using (var P = Process.GetCurrentProcess()) 
      { 
       return P.HandleCount; 
      } 
     } 
    } 
} 

अपडेट 2013-10-18:

लंबे समय तक चलने वाले परिणाम। इसे ठीक करने के लिए कोई अन्य कोड परिवर्तन आवश्यक नहीं था। Graph of Application performance over ~20 hours

संबंधित मुद्दे