2013-10-16 54 views
7

的问题如何诊断句柄泄漏源

我只是把一些性能记录昨天,我注意到从看任务管理器前一段时间一个句柄泄漏,但修复它一直是低优先级。这是一个每10秒钟进行一次采样的过夜运行。

我还没有运行该故障,由于时间的限制,我的测试计算机也是这样运行此一边写代码是不理想......所以我不知道我的dev的电脑,如果/当它会崩溃,但我高度怀疑这只是时间问题。

Graph of application resource usages and performance

注:的红色区域盒装是我“停止”的工作循环,并在短暂的停顿后重新启动它。线程从〜100下降到了〜20。手柄没有下降,直到循环在约30秒后重新开始从〜62000至〜40,000。因此,一些把手越来越GC'd,只是没有那么多,因为我想到应该是。我想不出有什么根防止越来越收集所有这些手柄或他们最初是来自何处(即任务,GUI,文件等)。

如果您已经知道可能导致此问题的原因,则无需进一步阅读。我已经提供了这个信息和代码的其余部分在sussing出来的问题的鸟枪式的方法参考。我将删除,编辑等,因为根本原因被缩小。同样的道理,如果感兴趣的东西是缺少让我知道,我会尽力为它(日志,转储等)。


我已经完成

在我自己的,我通过这个教程Tracking Handle Misuse走了,并得到尽可能看着转储文件找到,其中把手打开和关闭......但是,它的数量太多,难以理解,而且我很难让符号加载,所以指针对我来说只是一句胡言乱语。

我还没有经过我的名单上有以下两个,而是想知道是否有一些友好的方法第一...

我也将我怀疑这个代码分解成另一个小应用程序的代码,并且所有内容似乎都可以毫无问题地收集垃圾(尽管执行模式与真实应用程序相比大大简化了)。

潜在元凶

我的确有这个最后只要应用是开了好长寿命的实例化类,包括创建只有一次每个然后隐藏/需要5个所示表格。我使用主对象作为我的应用程序控制器,然后模型和视图通过事件连接到演示者优先模式中的演示者。

下面是一些事情,我在此应用中,这可能是也可能不是重要的事:

  • 使用自定义ActionFunc和lambda表达式广泛,其中一些可能是长寿命
  • 3个自定义代表参加事件并可以产生异步执行的Task
  • 安全调用Controls的扩展。
  • 非常,非常大量使用TaskParallel.For/Parallel.Foreach运行工法(或事件如上所述)
  • 不要使用Thread.sleep()方法,而是一个自定义Sleep.For(),它使用一个的AutoResetEvent。

主循环

这个应用程序时,它运行的一般流程是基于环比在离线版本的一系列文件和数字输入信号的轮询在在线版本。下面是有关离线版本的注释的sudo代码,这是我可以从我的笔记本电脑运行而不需要外部硬件以及上面监视的图表(我无法访问硬件在线模式)。

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); 
} 

异步事件

下面是活动的延伸和大部分使用的是默认选项,异步执行。 'TryRaising()'扩展只是将代理包装在try-catch中并记录任何异常(但它们不会重新抛出它不是正常程序流的一部分,因为它们负责捕获异常)。

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); 
     } 
    } 
} 

GUI安全,调用

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

下载Process Explorer并选择您的应用程序。然后选择View - > Lower Pane - Handles。这应该给你一个想法,你正在泄漏的处理类型(互斥,事件,文件,...)如果它是一个有名的互斥或文件句柄,你应该有一个很好的机会直接找到问题的来源。 –

+0

@AloisKraus是的,我已经有体育课,只是不知道如何充分使用它。我看到列表中可能有200个句柄,尽管我的应用程序(刚刚重新启动)现在正在使用约2500个。 〜30'File'和〜40'Key'看起来很稳定,但是很多正在创建和销毁的Thread线程处理(红/绿突出显示被添加和删除)。列表中只有4个'Event'和6'Mutant'类型。我主要是开火并忘记任务,我不会处理它们或者通常等待。我确实在任务内部捕获异常,记录它们,然后返回......让任务自行减速。 – HodlDwon

回答

1

所有评论到目前为止已经相当有益的,我已经找到至少一个我的手柄源泄漏为Sleep.For()方法。我仍然认为我有泄漏处理,但速度明显较慢,我现在也明白更好为什么他们漏水。

它必须处理传入的标记范围,并在using语句中清除方法中的本地标记。一旦我解决了这个问题,我开始在Process Explorer中看到所有未命名的Event句柄被创建并销毁,而不是坐在那里。

顺便说一句,我昨天晚上发现Anatomy of a "Memory Leak",肯定会进一步了解Windbg进一步调查。

我也在做一个长时间运行的性能测试,看看这是否是唯一的漏洞,并回顾我的代码中使用WaitHandles的其他部分,以确保我正确的范围和处置它们。

固定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