Причины зависания AppDomain Unload - PullRequest
0 голосов
/ 25 апреля 2018

Я все еще пытаюсь понять текущую проблему, но ее можно в значительной степени суммировать как невозможность выгрузки AppDomain .

Это происходит во время развертывания ASP.NET WebAPI в службе приложений Azure, и мы наблюдаем следующее:

  1. Идентификатор процесса не изменяется , новое развертывание размещается в том же процессе (AFAIU выполняется через выгрузку старого AppDomain и раскрутку нового AppDomain с обновленными двоичными файлами)
  2. Диагностика Azure PaaS показывает следующее в разделе ошибок:

"В w3wp_12396.dmp HttpRuntime для приложения / LM / W3SVC / 1523308129 / ROOT равен в середине останова . "

  1. Анализируя дампы памяти, мы видим потоки с установленным флагом IsAbortRequested , но они, похоже, никогда не заканчивают (вывод WinDbg !threads здесь: https://pastebin.com/7CXYcffy)

  2. В дампах памяти мы также видим множество доменов приложений со стадией " 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. Не обнаружено взаимоблокировок (по крайней мере, с помощью команды !dlk плагина WinDbg SOSEX, которая обычно охватывает большинство случаев взаимоблокировки)

  2. Нет кода отменяет прерывание потока (нет Thread.ResetAbort() вызывается)

Единственный способ решить проблему сейчас - это остановить процесс (остановить Azure AppService).

Каковы возможные причины невозможности выгрузки AppDomain?

UPDATE . В стеках потоков мы получили подсказку, что это может иметь отношение к нашему пользовательскому приложению 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) ...

1 Ответ

0 голосов
/ 26 апреля 2018

Кажется, это ошибка JIT.Да, Ошибка в JIT !Я нашел почти ту же историю, задокументированную там: http://labs.criteo.com/2017/04/ryujit-never-ending-threadabortexception/.

Чтобы продемонстрировать проблему, вы можете запустить следующий код.Работает только в режиме Release, только для платформы x64 (и я нацеливаюсь на .NET 4.5.2).

Вы увидите бесконечную цепочку зарегистрированных исключений, если только вы вручную не сбросите исключение.Почему это ошибка в CLR / JIT?Потому что CLR / JIT отвечает за внедрение throw ThreadAbortException в «безопасных местах», когда установлен флаг AbortRequested потока.

Цитата Джеффри Рихтера из «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();
}
...