【问题标题】:Does the CLR .tail instruction disable preemptive GC?CLR .tail 指令是否禁用抢先式 GC?
【发布时间】:2013-09-11 01:05:18
【问题描述】:

我正在尝试调试 Windows 服务的生产问题,一旦多个并发连接处于活动状态,该服务就会迅速崩溃。通过核心转储和 DebugDiag 的魔力,我能够发现有一个挂起的 GC 操作,直到多个禁用 Preemptive GC 的线程完成它们的工作后才能启动。

这是一个来自 WinDbg 的示例线程转储,显示了有问题的线程:

26   6e  1444 00..440   8009222 Disabled 00..200:00..f88 00..7a0     0 MTA (Threadpool Completion Port)
27   c1  1a0c 00..fe0   8009222 Disabled 00..e90:00..f88 00..7a0     0 MTA (Threadpool Completion Port)
28   b5  17bc 00..6f0   8009222 Disabled 00..268:00..f88 00..7a0     0 MTA (Threadpool Completion Port)
29   89  1f1c 00..ab0   8009222 Disabled 00..a30:00..f88 00..7a0     0 MTA (Threadpool Completion Port)
30   ac  2340 00..f70   8009220 Disabled 00..d00:00..d08 00..7a0     1 MTA (GC) (Threadpool Completion Port)
31   88  1b64 00..fd0   8009220 Enabled  00..b28:00..b48 00..7a0     0 MTA (Threadpool Completion Port)

因此,您可以在此处看到多个禁用了抢先式 GC 的线程(线程 26、27、28、29)和一个(线程 30)正在等待这些线程执行 GC。

我的 Google-fu 将我带到 this blog post,它描述了听起来类似的问题,只是在我的情况下没有涉及 XML。它给了我足够的信息,让我知道在哪里挖掘,最终我发现禁用抢占式 GC 的线程的一个共同特征是顶部看起来像这样的堆栈跟踪:

ntdll!NtWaitForSingleObject+a
ntdll!RtlpWaitOnCriticalSection+e8
ntdll!RtlEnterCriticalSection+d1
ntdll!RtlpLookupDynamicFunctionEntry+58
ntdll!RtlLookupFunctionEntry+a3
clr!JIT_TailCall+db
...

DebugDiag 还警告过我有关 CriticalSection 的信息,而恰巧带有 JIT_TailCall 的线程也是唯一带有 RtlEnterCriticalSection 的线程

所以我的问题是:实际上是 .tail 指令导致了这个死锁吗? 如果是这样:我该怎么办? p>

我可以在我的 .fsproj 文件上禁用尾调用,但看起来其中至少有一个来自 FSharp.Core.dll,并且反编译器中的一些探索似乎证实了 .tail 指令的存在。所以我不知道更改项目配置会删除所有.tail 指令。

以前有没有人处理过这样的事情?

更新: 更多可能有用的信息。

这是此转储的!locks 的输出:

!locks

CritSec +401680 at 0000000000401680
WaiterWoken        No
LockCount          0
RecursionCount     1
OwningThread       2340
EntryCount         0
ContentionCount    bf
*** Locked

Scanned 1657 critical sections

线程 2340 是启动 GC 的线程(我上面包含的部分列表中的线程 30)。

!syncblk 仅显示 ZooKeeper 客户端拥有的项目(虽然令人讨厌,但它不涉及任何阻止 GC 启动的堆栈)

!syncblk
Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
11 0000000019721a38            1         1 0000000019766e20 638   7   0000000000fb2950 System.Collections.Generic.LinkedList`1[[ZooKeeperNet.Packet, ZooKeeperNet]]
    Waiting threads:
18 0000000019721c68            1         1 000000001ae71420 8ac  13   00000000012defc8 System.Collections.Generic.LinkedList`1[[ZooKeeperNet.Packet, ZooKeeperNet]]
    Waiting threads:
-----------------------------
Total           64
CCW             0
RCW             0
ComClassFactory 0
Free            5

【问题讨论】:

    标签: .net f# garbage-collection clr tail-recursion


    【解决方案1】:

    我怀疑尾调用是问题所在(否则,我怀疑更多的 F# 用户会遇到这个问题)。从调用堆栈来看,您的代码似乎正在等待关键部分,这似乎更有可能是问题的根源......您知道您的代码可能依赖于哪些同步原语吗?

    【讨论】:

    • 我有几个地方正在使用锁,所以标准的 .Net Monitor 类,但是这些显示的堆栈跟踪在该代码附近没有。这基本上都是列表处理(如 List.iter、Map.find 等)。有趣的是,所有线程都在执行或多或少相同的操作,但在 60 个左右的活动连接中,只有 6 个禁用了抢占式 GC
    • List.iter (fun _ -> .. lock ..) xs 是否有可能使堆栈跟踪将您引至List.iter
    • 我在显示抢占式 GC 已禁用的跟踪中没有任何锁定。它们也出现在不同的尾部优化函数中(因此在一种情况下是 MapTreeInternal.mapi,另一种是 Primitives.Basics.List.iter,还有一种是在 MapTreeModule.find 中)。这些调用也都适用于 F# 记录类型,据我所知,这里甚至没有任何一次性实例,更不用说非托管资源了。所有线程的共同点是它们是通过异步 TCP 接收操作调用的。我不知道这是否与此有关。
    【解决方案2】:

    可能有点晚了,虽然你描述的问题似乎与我遇到的问题有点不同,但你提供的呼叫跟踪表明可能存在一些共同点。

    您可以在my answer 我自己的问题中找到更多详细信息,但简而言之,它归结为 Windows 7 和 .NET 4.0-4.5 的组合,这使得 F# 中的尾递归出现问题,导致过度锁定。将 .NET 更新到 4.6 或升级到 Windows 8 即可解决问题。

    此外,由于您在垃圾收集方面遇到问题,您可能想看看使用server garbage collection。这是我在发现上述问题之前所做的事情之一,它解决了我们遇到的大部分性能问题。所需要的只是您的 app.config 中的以下内容:

    <configuration>
      ...
      <runtime>
        ...
        <gcServer enabled="true"/>
        ...
      </runtime>
      ...
    </configuration>
    

    【讨论】:

      猜你喜欢
      • 2019-08-16
      • 2011-04-30
      • 2020-09-01
      • 2018-07-22
      • 1970-01-01
      • 2012-08-21
      • 2023-03-12
      • 1970-01-01
      • 2013-06-01
      相关资源
      最近更新 更多