Поведение потока 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.

Любой указатель или понимание очень ценится! Пожалуйста, дайте мне знать, если какие-либо другие детали вам нужны для диагностики этой проблемы:)

0 ответов

Другие вопросы по тегам