【发布时间】:2018-04-25 18:16:00
【问题描述】:
我仍在尝试了解持续存在的问题,但它几乎可以概括为无法卸载 AppDomain。
它发生在将 ASP.NET WebAPI 部署到 Azure 应用服务的过程中,我们观察到以下情况:
- 进程 ID 不变,新部署托管在同一个进程中(AFAIU 是通过卸载旧 AppDomain 并使用更新的二进制文件启动新 AppDomain 来完成的)
- Azure PaaS 诊断在错误部分显示以下内容:
"在 w3wp_12396.dmp 中,应用程序的 HttpRuntime /LM/W3SVC/1523308129/ROOT 正在关闭。”
分析内存转储,我们看到设置了 IsAbortRequested 标志的线程,但它们似乎从未完成(WinDbg
!threads的输出:https://pastebin.com/7CXYcffy)在内存转储中,我们还看到许多带有“UNLOAD_REQUESTED”阶段的 AppDomain,它们似乎从未完成卸载(
!DumpDomain的完整输出在这里:https://pastebin.com/kahZQuWN)
未检测到死锁(至少通过 WinDbg SOSEX 插件的
!dlk命令,通常涵盖大多数死锁情况)没有代码取消线程中止(没有调用
Thread.ResetAbort())
我们现在解决问题的唯一方法是终止进程(停止 Azure AppService)。
AppDomain 无法卸载的可能原因有哪些?
更新。在线程堆栈中,我们得到一个提示,它可能与我们的自定义 Azure Blob Log4net appender 有关,我发现当创建此类 appender(每个应用一次)时,它会产生具有以下结构的新线程。
while (true)
{
try
{
Flush(); // pseudocode
Thread.Sleep(10000);
}
catch(Exception)
{
}
}
我不确定为什么它会导致完全无法停止的线程(因为ThreadAbortException 不会被catch 停止),但看起来将while (true) 更改为while (!Environment.HasShutdownStarted && !_stopping) 解决了这个问题(_stopping 在 Appender 时设置OnClose 被调用,这是 log4net 的一种优雅关闭)...
【问题讨论】:
-
AppDomain.Unload 非常棘手,CLR 必须对仍在该域中执行代码的任何线程执行“粗鲁中止”。这实际上并没有那么粗鲁,线程必须在一个安全的地方,这样杀死它才能有一个好的结局。您遇到的第一个问题是该域中仍然有线程在执行代码,这根本不是一个好的情况。您遇到的下一个问题是您不知道这些线程在做什么,隐含不知道它们在做什么。只有非托管调试器才能向您显示麻烦的堆栈跟踪。
-
@HansPassant,感谢您,如此著名的 SO 世界人物,提供您的意见。请查看我刚刚添加的答案。该问题似乎是由讨厌的 JIT 错误引起的,可以通过下面答案中的小代码 sn-p 轻松重现。
-
您只是看到了 CLR 必须执行哪些操作才能在一个不容易处理的线程上执行中止。它反复尝试,希望在执行托管代码或调用可警告的 OS 函数时最终命中它。可报警的 OS 函数是允许 QueueUserApc() 执行的函数。像 SleepEx()。 CLR 非常努力地提供这种保证。但这并不是说,反恶意软件是反的不能给它带来困难。您只能通过非托管调试器看到这样的操作系统功能。
-
@HansPassant,这是有道理的,感谢您提供深入的解释性参考!但是来吧,你有没有想过
ThreadAbortException也就是claimed by MSDN会被自动重新抛出而不是被重新抛出? :) -
粗鲁的中止不使用 TAE。请一位不怕粗鲁和不受管理的调试的团队成员来帮助您。
标签: .net azure appdomain azure-web-app-service domainunload