4

我仍在尝试了解持续存在的问题,但它几乎可以概括为无法卸载 AppDomain

它发生在将 ASP.NET WebAPI 部署到 Azure App Service 的过程中,我们观察到以下情况:

  1. 进程 ID 不变,新部署托管在同一个进程中(AFAIU 是通过卸载旧 AppDomain 并使用更新的二进制文件启动新 AppDomain 来完成的)
  2. Azure PaaS 诊断在错误部分显示以下内容:

“在 w3wp_12396.dmp 中,应用程序 /LM/W3SVC/1523308129/ROOT 的 HttpRuntime 正在关闭中。”

  1. 分析内存转储,我们看到设置了IsAbortRequested标志的线程,但它们似乎从未完成(!threads此处 WinDbg 的输出:https ://pastebin.com/7CXYcffy )

  2. 在内存转储中,我们还看到很多带有“ UNLOAD_REQUESTED ”阶段的 AppDomain,它们似乎从未完成卸载(完整输出在!DumpDomain这里:https ://pastebin.com/kahZQuWN )

域 7:000001c67062c800
低频堆:000001c67062cff8
高频堆:000001c67062d088
存根堆:000001c67062d118
阶段:UNLOAD_REQUESTED
安全描述符:000001c6705c5680
名称:/LM/W3SVC/1523308129/ROOT-6-131687140950004974
  1. 未检测到死锁!dlk(至少通过 WinDbg SOSEX 插件的命令,通常涵盖大多数死锁情况)

  2. 没有代码取消线程中止(没有Thread.ResetAbort()调用)

我们现在可以解决问题的唯一方法是终止进程(停止 Azure AppService)。

AppDomain 无法卸载的可能原因有哪些?

更新。在线程堆栈中,我们得到一个提示,它可能与我们的自定义 Azure Blob Log4net 附加程序有关,我发现当创建此类附加程序时(每个应用程序一次),它会产生具有以下结构的新线程。

while (true)
{
   try
   {
        Flush(); // pseudocode
        Thread.Sleep(10000);
   }
   catch(Exception)
   {
   }
}

不确定我是否理解为什么它会导致完全无法停止的线程(因为ThreadAbortException不会被 catch 停止),但它看起来像改变while (true)while (!Environment.HasShutdownStarted && !_stopping)解决问题(在调用_stoppingAppender 时设置,OnClose这是 log4net 的一种优雅关闭)......

4

1 回答 1

2

这似乎是一个 JIT 错误。是的,JIT 中的错误!我在那里发现了几乎相同的故事:http: //labs.criteo.com/2017/04/ryujit-never-ending-threadabortexception/

为了演示该问题,您可以运行以下代码。仅适用于发布模式,仅适用于 x64 平台(我的目标是 .NET 4.5.2)。

除非您手动重新抛出异常,否则您将观察到无穷无尽的异常链。为什么它是 CLR/JIT 中的错误?因为当设置了线程的 AbortRequested 标志时,CLR/JIT 负责将 throw 注入ThreadAbortException“安全位置”。

Jeffrey Richter 从“CLR via C#”中引用(违反以下代码):

即使代码捕获到ThreadAbortException,CLR 也不允许吞下异常。换句话说,在 catch 块的末尾,CLR 会自动重新抛出ThreadAbortException 异常。

还有 GitHub 中的错误:https ://github.com/dotnet/coreclr/issues/16122 。

static void Main(string[] args)
{
    var mutex = new ManualResetEventSlim();

    var t = new Thread(() =>
    {
        while (true)
        {
            try
            {
                if (!mutex.IsSet)
                {
                    mutex.Set();
                }

                // Do some stuff

                Thread.Sleep(100);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex.Message);

                // the lines below FIX the issue
                //if (ex is ThreadAbortException)
                //    throw;
            }

            // FIXES the issue as well
            //Thread.Sleep(0);
        }
    });

    t.Start();

    // Wait for the thread to start
    mutex.Wait();

    t.Abort();

    Console.ReadLine();
}
于 2018-04-25T22:30:49.737 回答