我们最近遇到了一些可能与CLR的GC行为有关的问题。
我遇到的问题如下:
我们有一个使用C#编写的长期运行的压力测试应用程序,该应用程序会继续打开远程SMB文件共享(即Azure文件服务)上的文件句柄,并使用这些句柄执行文件系统操作(例如读/写等)。< / p>
通常,我们会长时间打开这些手柄,因为我们会反复使用它们。但是有时候,当我们尝试访问其中一些打开的手柄时,我们发现这些手柄已经关闭。并从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 + 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进行
我们使用P / Invoke打开文件句柄并获取SafeFileHandle,然后使用该SafeFileHandle构造FileStream,然后将FileStream对象保存在另一个定义如下的对象中:
public class ScteFileHandle
{
/// <summary>
/// local file handle
/// </summary>
[NonSerialized]
public FileStream FileStreamHandle;
/*
* Some other fields
*/
}
我们使用的P /调用:
[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清除。但是,我们确实观察到ScteFileHandle的FileStream中引用的SafeHandle已在CLR GC线程中完成,如上面的跟踪日志中所述。
所以我想知道是什么原因导致SafeFileHandle被GC填充,是否有任何方法可以避免这种情况?我不熟悉CLR GC的行为,但从我的角度来看,不应将SafeFileHandle进行GC。
任何指针或见识都将不胜感激!请让我知道您是否需要诊断此问题的其他详细信息:)