2013-09-06 10 views
8

Sto tentando di eseguire il debug di un problema di produzione con un servizio di Windows che tende a ridursi rapidamente dopo l'attivazione di un numero di connessioni simultanee. Attraverso la magia di un core dump e DebugDiag sono stato in grado di scoprire che c'era un'operazione GC in sospeso, che non poteva essere avviata fino a quando diversi thread con Preemptive GC disabilitato completavano il loro lavoro.L'istruzione CLR .tail disabilita il GC preemptive?

Ecco una discarica filo campione WinDbg mostrando i fili incriminate:

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) 

Così qui si possono vedere diverse discussioni che hanno GC preventiva disabilitato (fili 26,27,28,29) e uno (filo 30) che è in attesa su quei thread per eseguire il GC.

Il mio Google-fu mi porta a this blog post che descrive quello che sembra un problema simile, solo nel mio caso non c'era coinvolto XML. Mi ha dato abbastanza informazioni per sapere dove scavare, però, e alla fine ho scoperto che una delle caratteristiche comuni dei filetti con GC preventiva disabilitati era una traccia dello stack che si presentava così in alto:

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

DebugDiag anche mi ha avvertito circa il CriticalSection, e si dà il caso che i fili con il JIT_TailCall sono gli anche l'unico thread con RtlEnterCriticalSection

quindi la mia domanda è: e ', infatti, l'istruzione .tail che sta causando questa impasse? E in caso affermativo: Cosa posso fare a riguardo?

posso disabilitare tailcalls sul mio file .fsproj ma sembra che almeno uno di questi proviene da FSharp.Core.dll e alcuni speleologia nel decompilatore sembra confermare l'esistenza dell'istruzione .tail. Quindi non so che modificare la configurazione del progetto rimuoverà tutte le istruzioni .tail.

Qualcuno ha mai trattato qualcosa del genere?

Aggiornamento: Altre informazioni che potrebbero essere utili.

Ecco l'output di !locks per questa discarica:

!locks 

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

Scanned 1657 critical sections 

Discussione 2340 è il thread che ha iniziato il GC (filo 30 nella lista parziale ho incluso sopra).

E !syncblk sta mostrando solo gli elementi di proprietà del cliente ZooKeeper (che, pur fastidioso, non è coinvolto in nessuna delle pile, che stanno mantenendo GC dalla partenza)

!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 

risposta

1

dubito tailcalls sono il problema (altrimenti, sospetto che molti altri utenti di F # avrebbero colto questo problema). Dallo stack delle chiamate, sembra che il tuo codice sia in attesa di una sezione critica, che sembra molto più probabile che sia la fonte del problema ... Qual è l'idea su cosa si possa basare il primitivo di sincronizzazione sul tuo codice?

+0

Ho un paio di posizioni che utilizzano i blocchi, quindi le classi standard .Net Monitor, ma le tracce dello stack in cui vengono visualizzate non sono dove vicino a quel codice. Questo è fondamentalmente un processo di lista (quindi List.iter, Map.find, ecc.). La cosa interessante è che tutti i thread eseguono più o meno la stessa azione, ma dei 60 circa che sono connessioni attive, solo 6 hanno GC preemptive disabilitato – ckramer

+1

E 'possibile che 'List.iter (fun _ ->. . lock ..) xs' farebbe in modo che le tracce dello stack facciano riferimento a 'List.iter'? – t0yv0

+0

Non ho alcun blocco nelle tracce che mostrano che GC preventiva è disabilitato. Si verificano anche in diverse funzioni ottimizzate per la coda (quindi in un caso è MapTreeInternal.mapi, un'altra è Primitives.Basics.List.iter, un'altra ancora è in MapTreeModule.find). Queste chiamate funzionano anche sui tipi di record F #, per quel che posso dire non ci sono nemmeno istanze usa e getta qui, per non parlare delle risorse non gestite. Una cosa che tutti i thread hanno in comune è che vengono richiamati tramite un'operazione di ricezione TCP asincrona. Non so se questo sia in qualche modo coinvolto o no. – ckramer

1

Forse è un po 'tardi e anche se il problema che stai descrivendo sembra un po' diverso da quello che ho avuto, la traccia di chiamata che hai suggerito potrebbe esserci un terreno comune.

È possibile trovare ulteriori dettagli in my answer alla mia domanda, ma in breve si riduce alla combinazione di Windows 7 e .NET 4.0-4.5 che rende la ricorsione della coda in F # problematica, causando un blocco eccessivo. L'aggiornamento di .NET a 4.6 o l'aggiornamento a Windows 8 risolve il problema.

Inoltre, poiché si verifica un problema con la garbage collection, è possibile utilizzare server garbage collection. Questa è una delle cose che ho fatto prima di trovare il problema di cui sopra e ha risolto gran parte dei problemi di prestazioni che stavamo vivendo. Tutto ciò che è necessario è il seguente nella tua app.config:

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