Инструкцията CLR .tail деактивира ли превантивния GC?

Опитвам се да отстраня грешки в производствен проблем с услуга на 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 ме доведе до тази публикация в блог, която описва това, което звучи като подобен проблем, само че в моя случай не е включен 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 е причината за това блокиране? И ако е така: Какво мога да направя по въпроса?

Мога да деактивирам tailcalls на моите .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

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


Отговори (2)


Съмнявам се, че задните извиквания са проблемът (в противен случай подозирам, че много повече потребители на F# биха засегнали този проблем). От стека на повикванията изглежда, че кодът ви чака в критична секция, което изглежда много по-вероятно да е източникът на проблема... Някаква идея на какви примитиви за синхронизация може да разчита вашият код?

person kvb    schedule 06.09.2013
comment
Имам няколко места, които използват ключалки, така че стандартните класове .Net Monitor, но следите на стека, където те се показват, не са близо до този код. Всичко това е основно обработка на списъци (така List.iter, Map.find и т.н.). Интересното е, че всички нишки извършват повече или по-малко едно и също действие, но от около 60 активни връзки, само 6 имат превантивен GC деактивиран - person ckramer; 07.09.2013
comment
Възможно ли е List.iter (fun _ -> .. lock ..) xs да накара проследяванията на стека да ви насочат към List.iter? - person t0yv0; 09.09.2013
comment
Нямам никакво заключване в следите, което показва, че превантивният GC е деактивиран. Те също се срещат в различни оптимизирани функции (така че в един случай е 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