Запрещает ли инструкция CLR .tail вытесняющий сборщик мусора?

Я пытаюсь отладить производственную проблему с помощью службы 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)

Итак, здесь вы можете увидеть несколько потоков, у которых отключен вытесняющий сборщик мусора (потоки 26,27,28,29), и один (поток 30), который ожидает от этих потоков выполнения сборщика мусора.

Мой Google-fu привел меня к это сообщение в блоге, в котором описывается похожая проблема, только в моем случае XML не использовался. Это дало мне достаточно информации, чтобы знать, где копать, и в конце концов я обнаружил, что одной из общих особенностей потоков с отключенным вытесняющим сборщиком мусора была трассировка стека, которая выглядела так вверху:

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 вызывает этот тупик? И если да: Что я могу с этим поделать?

Я могу отключить хвостовые вызовы в моих файлах .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 - это поток, который запустил сборщик мусора (поток 30 в частичном списке, который я включил выше).

И !syncblk показывает только элементы, принадлежащие клиенту ZooKeeper (который, хотя и раздражает, не участвует ни в одном из стеков, которые не позволяют запускать сборщик мусора).

!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

person ckramer    schedule 06.09.2013    source источник


Ответы (2)


Я сомневаюсь, что проблема заключается в обратных вызовах (в противном случае, я подозреваю, что гораздо больше пользователей F # столкнулись бы с этой проблемой). Судя по стеку вызовов, похоже, что ваш код ожидает критического раздела, который, скорее всего, является источником проблемы ... Есть идеи, на какие примитивы синхронизации может полагаться ваш код?

person kvb    schedule 06.09.2013
comment
У меня есть пара мест, где используются блокировки, поэтому стандартные классы .Net Monitor, но трассировки стека, где они появляются, не где-то рядом с этим кодом. Это все, в основном, обработка списков (например, List.iter, Map.find и т. Д.). Интересно то, что все потоки выполняют более или менее одно и то же действие, но из примерно 60 активных подключений только 6 отключили вытесняющий сборщик мусора. - person ckramer; 07.09.2013
comment
Возможно ли, что List.iter (fun _ -> .. lock ..) xs заставит трассировки стека отсылать вас к List.iter? - person t0yv0; 09.09.2013
comment
У меня нет блокировок в трассировках, которые показывают, что вытесняющий сборщик мусора отключен. Они также встречаются в разных оптимизированных для хвоста функциях (так, в одном случае - MapTreeInternal.mapi, в другом - Primitives.Basics.List.iter, а в третьем - в MapTreeModule.find). Все эти вызовы также работают с типами записей F #, поэтому, насколько я могу судить, здесь нет даже одноразовых экземпляров, не говоря уже о неуправляемых ресурсах. У всех потоков есть одна общая черта: они вызываются через операцию приема Async TCP. Я не знаю, замешано это как-то или нет. - person ckramer; 09.09.2013

Возможно, это немного поздно, и хотя проблема, которую вы описываете, кажется немного отличной от той, что была у меня, трассировка вызовов, которую вы указали, предполагает, что могут быть какие-то точки соприкосновения.

Вы можете найти более подробную информацию в моем ответе на мой собственный вопрос, но вкратце все сводится к комбинации Windows 7 и .NET 4.0-4.5, что делает хвостовую рекурсию в F # проблематичной, вызывая чрезмерную блокировку. Обновление .NET до 4.6 или обновление до Windows 8 решает проблему.

Кроме того, поскольку у вас возникла проблема со сборкой мусора, вы можете использовать сборка мусора на сервере. Это одна из вещей, которые я сделал до того, как обнаружил вышеупомянутую проблему, и она решила большую часть проблем с производительностью, с которыми мы сталкивались. Все, что необходимо, это следующее в вашем app.config:

<configuration>
  ...
  <runtime>
    ...
    <gcServer enabled="true"/>
    ...
  </runtime>
  ...
</configuration>
person mweerden    schedule 15.07.2016