Поведение потока CLR GC: SafeFileHandle неожиданно завершен
Недавно мы затронули некоторые проблемы, которые могут быть связаны с поведением GC в CLR.
Проблема, с которой я столкнулся, заключается в следующем:
У нас есть долгое приложение для стресс-тестирования, написанное на C#, которое продолжает открывать файловые дескрипторы на удаленном общем файловом ресурсе SMB (который является службой файлов Azure) и использует эти дескрипторы для выполнения операций файловой системы, таких как чтение / запись и т. Д.
Как правило, мы будем держать эти ручки открытыми в течение достаточно долгого времени, так как будем использовать их неоднократно. Но иногда, когда мы пытаемся получить доступ к некоторым из этих открытых дескрипторов, мы обнаруживаем, что эти дескрипторы уже закрыты. А из журналов трассировки, записанных Process Monitor (один пример ниже):
fltmgr.sys! FltpPerformPreCallbacks + 0x324
fltmgr.sys! FltpPassThroughInternal + 0x8c
fltmgr.sys! FltpPassThrough + 0x169
fltmgr.sys! FltpDispatch + 0x9e
ntoskrnl.exeIopCloseFile + 0x146
ntoskrnl.exeObpDecrementHandleCount + 0x9A
ntoskrnl.exeNtClose + 0x3d9
ntoskrnl.exeKiSystemServiceCopyEnd + 0x13
ntdll.dll! ZwClose + 0xa
KERNELBASE.dll! CloseHandle + 0x17
mscorlib.ni.dll! mscorlib.ni.dll! + 0x566038
clr.dll! CallDescrWorkerInternal + 0x83
clr.dll! CallDescrWorkerWithHandler + 0x4a
clr.dll! DispatchCallSimple + 0x60
clr.dll! SafeHandle:: RunReleaseMethod + 0x69
clr.dll! SafeHandle:: Release + 0x152
clr.dll! SafeHandle:: Утилизировать + 0x5a
clr.dll! SafeHandle:: DisposeNative + 0x9b
mscorlib.ni.dll! mscorlib.ni.dll! + 0x48d9d1
mscorlib.ni.dll! mscorlib.ni.dll! + 0x504b83
clr.dll! FastCallFinalizeWorker + 0x6
clr.dll! FastCallFinalize + 0x55
clr.dll! MethodTable:: CallFinalizer + 0xac
clr.dll! WKS:: CallFinalizer + 0x61
clr.dll! WKS:: DoOneFinalization + 0x92
clr.dll! WKS:: FinalizeAllObjects + 0x8F
clr.dll! WKS:: FinalizeAllObjects_Wrapper + 0x18
clr.dll! ManagedThreadBase_DispatchInner + 0x2d
clr.dll! ManagedThreadBase_DispatchMiddle + 0x6c
clr.dll! ManagedThreadBase_DispatchOuter + 0x75
clr.dll! ManagedThreadBase_DispatchInCorrectAD + 0x15
clr.dll! Автор:: DoADCallBack + 0xff
clr.dll! ManagedThreadBase_DispatchInner + 0x1d822c
clr.dll! WKS:: DoOneFinalization + 0x145
clr.dll! WKS:: FinalizeAllObjects + 0x8F
clr.dll! WKS:: GCHeap:: FinalizerThreadWorker + 0xa1
clr.dll! ManagedThreadBase_DispatchInner + 0x2d
clr.dll! ManagedThreadBase_DispatchMiddle + 0x6c
clr.dll! ManagedThreadBase_DispatchOuter + 0x75
clr.dll! WKS:: GCHeap:: FinalizerThreadStart + 0xd7
clr.dll! Thread:: intermediateThreadProc + 0x7d
KERNEL32.dll! BaseThreadInitThunk + 0x1a
ntdll.dll! RtlUserThreadStart + 0x1D
Кажется, что ручки были закрыты в потоке CLR GC Finalizer. Однако наши дескрипторы открываются по следующему шаблону, который не должен быть GC'ed:
Мы используем P/Invoke, чтобы открыть дескриптор файла и получить SafeFileHandle, и используем этот SafeFileHandle для создания FileStream, и мы сохраним объект FileStream в другом объекте, определенном следующим образом:
public class ScteFileHandle
{
/// <summary>
/// local file handle
/// </summary>
[NonSerialized]
public FileStream FileStreamHandle;
/*
* Some other fields
*/
}
P/Invoke мы используем:
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
Win32FileAccess dwDesiredAccess,
Win32FileShare dwShareMode,
IntPtr lpSecurityAttributes,
Win32FileMode dwCreationDisposition,
Win32FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
SafeFileHandle fileHandle = Win32FileIO.CreateFile (fullFilePath, win32FileAccess, win32FileShare, IntPtr.Zero, win32FileMode, win32FileAttr, IntPtr.Zero);
FileStream fileStream = новый FileStream(fileHandle, fileAccess, Constants.XSMBFileSectorSize);
В одном мы уверены, что в течение всего жизненного цикла нашего приложения для стресс-тестирования мы обязательно сохраняем ссылку на объект ScteFileHandle, поэтому он никогда не будет очищен GC. Однако мы наблюдали, как SafeHandle, на который ссылается FileStream ScteFileHandle, был завершен в потоке GC CLR, как вставлено в журнал трассировки выше.
Поэтому мне интересно, что заставило SafeFileHandle быть GC'едированным и есть ли какой-нибудь подход, чтобы избежать этого? Я не знаком с поведением CLR GC, но с моей точки зрения SafeFileHandle не должен быть GC'ed.
Любой указатель или понимание очень ценится! Пожалуйста, дайте мне знать, если какие-либо другие детали вам нужны для диагностики этой проблемы:)