2013-09-06 11 views
8

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 

Odpowiedz

1

Wątpię tailcalls są problemem (w przeciwnym razie, podejrzewam, że wielu użytkowników F # trafiłoby na ten problem). Ze stosu wywołań wygląda na to, że twój kod czeka na krytyczną sekcję, co wydaje się być bardziej prawdopodobne, że jest źródłem problemu ... Czy jest jakiś pomysł na jaką synchronizację prymitywuje twój kod?

+0

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

+1

Czy to możliwe, że 'List.iter (fun _ ->. lock ...) xs' sprawiłoby, że ślady stosów odwoływałyby się do 'List.iter'? – t0yv0

+0

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

1

Być może jest trochę za późno i chociaż problem, który opisujesz, wydaje się trochę inny od tego, który miałem, śledzenie połączeń sugeruje, że może istnieć pewna wspólna podstawa.

Możesz znaleźć więcej szczegółów w my answer na moje własne pytanie, ale w skrócie sprowadza się do kombinacji systemu Windows 7 i .NET 4.0-4.5, który powoduje, że rekurencja ogona w F # problematyczna, powoduje nadmierne blokowanie. Aktualizacja programu .NET do wersji 4.6 lub aktualizacja do systemu Windows 8 rozwiązuje problem.

Ponadto, ponieważ występuje problem z czyszczeniem pamięci, możesz chcieć sprawdzić, czy używasz server garbage collection. Jest to jedna z rzeczy, którą zrobiłem, zanim znalazłem powyższy problem i rozwiązał dużą część problemów z wydajnością, których doświadczaliśmy. Wszystko, co potrzebne jest następujące w app.config:

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