AppDomain Unload 卡住的原因

Reasons for stuck AppDomain Unload

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

它发生在将 ASP.NET WebAPI 部署到 Azure 应用服务的过程中,我们观察到以下内容:

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

"In w3wp_12396.dmp, the HttpRuntime for the application /LM/W3SVC/1523308129/ROOT is in the middle of a shutdown."

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

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

Domain 7:           000001c67062c800
LowFrequencyHeap:   000001c67062cff8
HighFrequencyHeap:  000001c67062d088
StubHeap:           000001c67062d118
Stage:              UNLOAD_REQUESTED
SecurityDescriptor: 000001c6705c5680
Name:               /LM/W3SVC/1523308129/ROOT-6-131687140950004974
  1. 未检测到死锁(至少通过 WinDbg SOSEX 插件的 !dlk 命令,通常涵盖大多数死锁情况)

  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) 可以解决问题(_stopping 在调用 Appender OnClose 时设置,这是 log4net 的一种正常关闭)...

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

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

除非您手动重新抛出异常,否则您将观察到记录的异常链是无穷无尽的。为什么它是 CLR/JIT 中的错误?因为 CLR/JIT 负责在设置线程的 AbortRequested 标志时在 "safe places" 中注入抛出 ThreadAbortException

Jeffrey Richter 引自 "CLR via C#"(以下代码违反):

Even when code catches the ThreadAbortException, the CLR doesn’t allow the exception to be swallowed. In other words, at the end of the catch block, the CLR automatically rethrows the ThreadAbortException exception.

还有 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();
}