AppDomain卸载失败的原因

时间:2018-04-25 18:16:01

标签: .net azure appdomain azure-web-app-service domainunload

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

在ASP.NET WebAPI部署到Azure App Service期间会发生这种情况,我们观察到以下情况:

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

    "在w3wp_12396.dmp中,应用程序的HttpRuntime   / LM / W3SVC / 1523308129 / ROOT在关机过程中 。"

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

    2. 在内存转储中,我们还看到很多AppDomains" UNLOAD_REQUESTED "阶段,他们似乎永远不会完成卸载(!DumpDomain的完整输出在这里:https://pastebin.com/kahZQuWN

    3. 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()

      3. 我们现在解决问题的唯一方法是终止进程(停止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)可以解决问题({调用Appender _stopping时设置{1}},这对于log4net是一种正常关闭的方式)...

1 个答案:

答案 0 :(得分:1)

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

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

除非您手动重新抛出异常,否则您将观察到无限的异常链。为什么它是CLR / JIT中的错误?因为CLR / JIT负责在一个安全的地方注射投掷public void placeOrder(View view) { Stripe stripe = new Stripe(getApplicationContext(), "pk_test_..."); stripe.createToken( paymentCard, new TokenCallback() { public void onSuccess(Token token) { // Send token to your server tok = token; new StripeCharge(token.getId()).execute(); } public void onError(Exception error) { // Show localized error message Toast.makeText(getApplicationContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG ).show(); } } ); } public class StripeCharge extends AsyncTask<String, Void, String> { String token; public StripeCharge(String token) { this.token = token; } @Override protected String doInBackground(String... params) { new Thread() { @Override public void run() { postData(token); } }.start(); return "Done"; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); Log.e("Result",s); } } public void postData(String mToken) { FormBody.Builder formBuilder = new FormBody.Builder() .add("amount", "15000") .add("currency", "gbp") .add("token", mToken) .add("shipping", "Road") .add("receipt_email", "test@test.com") .add("description", "Purchased from Android"); RequestBody formBody = formBuilder.build(); Request request = new Request.Builder().url(Server.ENDPOINT + "/charge") .post(formBody) .build(); Server.getHttpClient().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { switch (response.code()) { case 200: JsonObject result = new JsonParser().parse(response.body().string()).getAsJsonObject(); response.body().close(); break; } } }); } &#34;何时设置了线程的AbortRequested标志。

来自&#34; CLR的Jeffrey Richter引用C#&#34; (违反以下代码):

  

即使代码捕获ThreadAbortException,CLR也不允许   被吞下的例外。换句话说,在捕获结束时   阻止,CLR自动重新抛出ThreadAbortException   异常。

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

ThreadAbortException