0

我们最近遇到了一些可能与 CLR 的 GC 行为有关的问题。

我遇到的问题如下:

我们有一个用 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::Dispose+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!Thread::DoADCallBack+0xff
clr.dll!ManagedThreadBase_DispatchInner+0x
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 的模式打开:

我们使用 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 = new FileStream(fileHandle, fileAccess, Constants.XSMBFileSectorSize);

我们可以确定的一件事是,在压力测试应用程序的整个生命周期中,我们肯定会保留对 ScteFileHandle 对象的引用,因此它永远不会被 GC 清理。但是,我们确实观察到在 ScteFileHandle 的 FileStream 中引用的 SafeHandle 已在 CLR GC 线程中完成,如粘贴在上面的跟踪日志中。

所以我想知道是什么导致 SafeFileHandle 被 GC'ed 以及是否有任何方法可以避免这种情况?我不熟悉 CLR GC 行为,但从我的角度来看,SafeFileHandle 不应该被 GC 处理。

非常感谢任何指针或见解!如果您需要诊断此问题的任何其他详细信息,请告诉我:)

4

0 回答 0