Próbuję debugować problem produkcyjny z usługą systemu Windows, która ma tendencję do szybkiego spadania, gdy kilka współbieżnych połączeń są aktywne. Dzięki magii zrzutu pamięci i DebugDiag udało mi się odkryć, że doszło do oczekującej operacji GC, która nie mogła się rozpocząć, dopóki kilka wątków z funkcją Preemptive GC disabled nie zakończyło pracy.Czy instrukcja CLR .tail wyłącza wyprzedzające GC?
Oto przykładowy zrzut wątek z WinDbg przedstawiający wątki naruszające:
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)
Więc tutaj można zobaczyć kilka wątków, które mają poboru GC wyłączone (wątki 26,27,28,29) i jeden (gwint 30), który czeka na te wątki, aby wykonać GC.
Moje Google-fu doprowadziło mnie do this blog post, która opisuje, co brzmi jak podobny problem, tylko w moim przypadku nie było w nim zaangażowanego XML. To dało mi wystarczająco dużo informacji, aby wiedzieć gdzie kopać, choć i w końcu odkryłem, że jedną z cech wspólnych wątków z poboru GC wyłączone było śladu stosu, który wyglądał jak ten na górze:
ntdll!NtWaitForSingleObject+a
ntdll!RtlpWaitOnCriticalSection+e8
ntdll!RtlEnterCriticalSection+d1
ntdll!RtlpLookupDynamicFunctionEntry+58
ntdll!RtlLookupFunctionEntry+a3
clr!JIT_TailCall+db
...
DebugDiag także ostrzegł mnie o sekcja krytyczna, a tak się składa, że nici z JIT_TailCall
są również jedynym nici z RtlEnterCriticalSection
więc moje pytanie brzmi: Czy to w rzeczywistości dyspozycja .tail
który jest przyczyną tego impasu? A jeśli tak, to: Co mogę z tym zrobić?
mogę wyłączyć tailcalls na moich plików .fsproj ale wygląda na co najmniej jeden z nich pochodzi z FSharp.Core.dll
a niektóre spelunking w dekompilator zdaje się potwierdzać istnienie instrukcji .tail
. Więc nie wiem, że zmiana konfiguracji projektu spowoduje usunięcie wszystkich instrukcji .tail
.
Czy ktoś wcześniej miał do czynienia z czymś takim?
Aktualizacja: Więcej informacji, które mogą być przydatne.
Oto wyjściowy !locks
na to wysypisko:
!locks
CritSec +401680 at 0000000000401680
WaiterWoken No
LockCount 0
RecursionCount 1
OwningThread 2340
EntryCount 0
ContentionCount bf
*** Locked
Scanned 1657 critical sections
Wątek 2340 jest wątek, który rozpoczął GC (gwint 30 na liście częściowej I zawarte powyżej).
I !syncblk
pokazuje tylko przedmioty należące do klienta Heca (które, choć irytujące, nie uczestniczy w żadnej ze stosów, które trzymają GC od startu)
!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
Mam kilka miejsc, które są za pomocą zamków, więc standardowych klas Net Monitor, ale ślady stosu, gdzie one są wyświetlane nie jest gdzieś w pobliżu tego kodu. To wszystko jest zasadniczo przetwarzaniem list (więc List.iter, Map.find, itp.). Interesujące jest to, że wszystkie wątki wykonują mniej więcej tę samą akcję, ale z 60 lub mniej, które są aktywnymi połączeniami, tylko 6 ma zapobiegawcze GC wyłączone – ckramer
Czy to możliwe, że 'List.iter (fun _ ->. lock ...) xs' sprawiłoby, że ślady stosów odwoływałyby się do 'List.iter'? – t0yv0
Nie mam żadnych blokad w ślady, które pokazują zapobiegawcze GC wyłączone. Występują również w różnych funkcjach zoptymalizowanych pod względem ogona (w jednym przypadku MapTreeInternal.mapi, innym jest Primitives.Basics.List.iter, Jeszcze inny znajduje się w MapTreeModule.find). Wszystkie te połączenia pracują również nad typami rekordów F #, więc o ile wiem, nie ma tu nawet żadnych jednorazowych instancji, nie wspominając o niezarządzanych zasobach. Wspólną cechą wszystkich wątków jest to, że są one wywoływane przez operację odbierania ASync TCP. Nie wiem, czy to jest w jakiś sposób zaangażowane, czy nie. – ckramer