【问题标题】:How to debug a hanging action如何调试挂起动作
【发布时间】:2016-07-19 02:31:52
【问题描述】:

我们有一个 MVC 应用程序,每个人都知道,然后(从每天几次到每 2-3 天一次)遭受站点范围内的挂起(它不响应任何请求并且不重新启动它拥有任何一个)。我们设法将嫌疑人的数量减少到一个页面(只要网站遭受挂起,此页面就会显示在挂起请求的顶部)。知道哪个页面是罪魁祸首并没有帮助,因为我们无法在开发机器上重现问题。事实上,我们甚至无法在生产机器上重现问题(大多数情况下,只需打开错误页面并不会破坏任何东西)。

我们所知道的是,有时当用户访问此页面(控制器操作)时,站点会挂起,我们知道发出请求的方式不会导致挂起(来自 IIS日志我们知道用户是如何到达错误页面的,并且我们正在处理一个没有查询字符串参数的简单 GET)。

我们想知道挂起发生在代码中的确切位置,但我们不知道如何从应用程序/工作线程/IIS/Windows Server 中获取此类信息。没有异常保存到 Windows 日志中,我们内置的应用记录器也没有选择任何异常(可能与挂起有关)。

有什么方法可以知道 IIS 工作线程在给定时间到底在做什么(比如获取相应的文件和代码行)?


P.S. 我在另一个问题中描述了站点范围内挂起的确切症状,但这些与这个问题无关。另外,这个问题最终过于宽泛,只会带来一般性的答案。 P.S.2 我们通过查看 IIS 中的 Worker Processes\Requests 找到了违规页面。

【问题讨论】:

  • 附加调试器,或添加日志记录。
  • 第 1 步 - 获取完整的内存转储。可以使用任务浏览器或调试工具来完成。第 2 步 - 使用 Debug Diagnostic Tool 分析内存转储或使用 Windbg(如果您是新手,请先尝试第一个选项)。获取完整的线程列表并为每个线程调用堆栈相对简单。当您认为 Web 应用不再响应时,您必须创建内存转储。
  • 旁注 - Debug Diagnostic Tool 应用程序制作精良,下载并试用。它有一个完整的 GUI,因此无需开始学习命令。分析结果确实需要一些努力,但无论您如何处理,弄清楚这样的事情通常都不是一件容易的事。
  • @Igor 当站点挂起时,我们尝试使用 DebugDiag 进行完整的内存转储,但由于某种原因它失败了。我们能得到的最好的结果是来自 DebugDiag 的小型转储。我想我可以尝试使用任务管理器创建一个转储(应该可以为有问题的线程而不是整个应用程序创建一个转储)。下次应用遇到挂起时,我会尝试这样做。
  • 为什么会失败?也许您没有以管理员权限运行它,或者磁盘空间不足?或者,您可以连接到正在运行的应用程序,但这会为所有其他正在连接的用户冻结它(不确定这是否很重要)。或者尝试使用右键单击从 Windows 任务管理器创建转储。

标签: asp.net-mvc iis


【解决方案1】:

过去我也遇到过同样的问题,而真正帮助我的是能够使用 Mono.Cecil 在 DLL 中注入一行跟踪代码。下面的代码将跟踪注入到 DLL 的未签名版本中,并输出一个新的 DLL,该 DLL 可用于通过在开头注入日志行来在代码中创建“缩进跟踪”(如果需要,则结束......未在下面显示)每个方法的时间,以便您可以对每个调用的进入和存在进行计时。有很多第三方工具,但这很容易(最多工作一天),让开发人员完全控制,而且作为奖励,是免费的。

您还需要创建一个 DLL(下面是 IISStackTraceProvider),其中包含跟踪类和可用于记录数据的静态调用“TraceStep”。要跨工作进程和线程重建该数据,您可以使用“HttpContext.Items 属性”将 GUID 和秒表与每个 BeginRequest/EndRequest 相关联。由于您的通话挂起...您想跟踪任何“开始”但从不“结束”或以超时结束的通话,然后将其余的电话扔掉以保持快速。

我已经在我们的生产环境中的网络场中针对每台服务器每小时约 100 万次调用进行了测试,而不会影响性能,但请注意您要记录哪些请求以及哪些请求会被丢弃。另外,我使用 Redis 来捕获日志,因为写入时间非常快而且它是免费的,然后在我捕获问题后只需读取 Redis 数据。

class TraceInjection
{
    private ELogLevel logLevel;
    public enum ELogLevel
    {
        eLow,
        eMid,
        eHigh
    }

    public TraceInjection(ELogLevel logLevel)
    {
        this.logLevel = logLevel;
    }

    public bool InjectTracingLine(string assemblyPath, string outputDirectory)
    {
        CustomAttribute customAttr;
        AssemblyDefinition asmDef;

        // New assembly path
        string fileName = Path.GetFileName(assemblyPath);

        string newPath = outputDirectory + "\\" + fileName;

        // Check if Output directory already exists, if not, create one
        if (!Directory.Exists(outputDirectory))
        {
            Directory.CreateDirectory(outputDirectory);
        }

        ModuleDefinition modDefCopy = null;
        TypeDefinition typDefCopy = null;

        try
        {
            var resolver = new DefaultAssemblyResolver();
            resolver.AddSearchDirectory(System.IO.Path.GetDirectoryName(assemblyPath));

            var parameters = new ReaderParameters
            {
                AssemblyResolver = resolver,
            };

            // Load assembly
            asmDef = AssemblyDefinition.ReadAssembly(assemblyPath, parameters);

            String functionsFound = "";

            foreach (var modDef in asmDef.Modules)
            {
                modDefCopy = modDef;
                foreach (var typDef in modDef.Types)
                {
                    typDefCopy = typDef;                        
                    foreach (MethodDefinition metDef in typDef.Methods)
                    {
                        try
                        {
                            // Skipping things I personally don't want traced...
                            if (metDef.IsConstructor || 
                                metDef.IsAbstract ||
                                metDef.IsCompilerControlled ||
                                metDef.IsGetter ||
                                metDef.IsSetter
                                ) continue;

                            functionsFound += String.Format("{0}\r\n", metDef.Name.Trim());


                            // Get ILProcessor
                            ILProcessor ilProcessor = metDef.Body.GetILProcessor();

                            /*** Begin Method ******/
                            // Load fully qualified method name as string
                            Instruction i1 = ilProcessor.Create(
                                OpCodes.Ldstr,
                                String.Format(">,{0},{1}", metDef.Name.Replace(",", ""), asmDef.Name.Name)
                            );
                            ilProcessor.InsertBefore(metDef.Body.Instructions[0], i1);

                            // Call the method which would write tracing info
                            Instruction i2 = ilProcessor.Create(
                                OpCodes.Call,
                                metDef.Module.Import(
                                    typeof(IISStackTraceProvider).GetMethod("TraceStep", new[] { typeof(string) })
                                )
                            );
                            ilProcessor.InsertAfter(i1, i2);

                        }catch(Exception ex)
                        {
                            // ...
                        }
                    }
                }
            }

            Console.Write(functionsFound);
            Console.ReadKey();

            // Save modified assembly
            asmDef.Write(newPath, new WriterParameters() { WriteSymbols = true });
        }
        catch (Exception ex)
        {
            modDefCopy = null;
            typDefCopy = null;

            // Nothing to be done, just let the caller handle exception 
            // or do logging and so on
            throw;
        }

        return true;
    }

    public bool TryGetCustomAttribute(MethodDefinition type, string attributeType, out CustomAttribute result)
    {
        result = null;
        if (!type.HasCustomAttributes)
            return false;

        foreach (CustomAttribute attribute in type.CustomAttributes)
        {
            if (attribute.Constructor.DeclaringType.FullName != attributeType)
                continue;

            result = attribute;
            return true;
        }

        return false;
    }
}

【讨论】:

  • 这是一个非常好的答案。我们大大简化了它,只需将日志写入文本文件并跳过关于检测请求何时完成的部分并在那里写入所有内容(反正我们没有那么多请求)。多亏了这一点,我们才能查明问题所在。这是一个错误的自定义非侵入式验证器,导致 Razor 挂起(随后阻止了进一步的请求完成)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-22
  • 2020-08-18
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
相关资源
最近更新 更多