CloseHandle 函数调用和 SMB 关闭请求之间的延迟

Delay between CloseHandle function call and SMB Close request

下面的这段代码打开了 SMB 共享上的一个文件并立即将其关闭。出于某种原因,我发现 CloseHandle 调用和通过网络发送的 SMB 关闭请求之间存在延迟。

#include <Windows.h>
#include <stdio.h>

typedef NTSTATUS (__stdcall *NtQuerySecurityObjectPtr)(
    _In_  HANDLE               Handle,
    _In_  SECURITY_INFORMATION SecurityInformation,
    _Out_ PSECURITY_DESCRIPTOR SecurityDescriptor,
    _In_  ULONG                Length,
    _Out_ PULONG               LengthNeeded
    );

void printTime() {
    SYSTEMTIME time;
    GetSystemTime(&time);
    int required = GetTimeFormat(LOCALE_INVARIANT, 0, &time, nullptr, nullptr, 0);
    LPTSTR buffer = (LPTSTR)GlobalAlloc(GPTR, required * sizeof(TCHAR));
    GetTimeFormat(LOCALE_INVARIANT, 0, &time, nullptr, buffer, required);
    wprintf(L"%s\n", buffer);
}

int main()
{
    LPCTSTR file = L"\\192.168.13.163\share\file1";
    HANDLE f = INVALID_HANDLE_VALUE;


    f = CreateFile(
        file,
        GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        nullptr,
        CREATE_ALWAYS,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
        INVALID_HANDLE_VALUE
        );
    CloseHandle(f);
    printTime();

    return 0;
}

我 运行 使用调试器编写此代码并在 printTime 调用后立即放置一个断点。在此 printTime 的一项测试中 运行 输出 12:18:11。 在 Wireshark 中,我看到在 12:18:20 发送了相应的关闭请求。所以在函数调用和消息发送出去之间有大约10秒的延迟。

我认为这可能是由于句柄在某处泄露或其他进程保持文件句柄打开,但我认为情况并非如此。如果我在 CloseHandle 调用之前暂停程序,系统用户 shell 中执行的 sysinternals 句柄工具会显示我的进程有文件句柄

C:\Users\pepijn\Desktop>Handle.exe file1

Handle v4.0
Copyright (C) 1997-2014 Mark Russinovich
Sysinternals - www.sysinternals.com

SmbClose.exe       pid: 7020   type: File            C8: \Device\Mup2.168.13.163\DMTest_share\file1

运行 CloseHandle 调用后的相同命令导致找不到句柄

C:\Users\pepijn\Desktop>Handle.exe file1

Handle v4.0
Copyright (C) 1997-2014 Mark Russinovich
Sysinternals - www.sysinternals.com

No matching handles found.

有人知道延迟的原因吗?

我能找到的与此主题最相关的问题是 。接受的答案是其他一些进程可能正在保留该文件。如上所述,我不认为这里是这种情况。我没有任何病毒扫描程序 运行ning,我希望任何打开的句柄都显示在 Handle 的输出中,因为它是 运行 提升的权限。

这显然是由于文件打开时服务器授予的 Batch Oplock(或相关 Lease)所致。您可以通过使用 Wireshark 检查创建响应来证明这一点。

避免授予批量 oplock 的推荐技巧之一是在您最初的 CreateFile 调用之前打开同一文件以在另一个应用程序中读取。

多亏了第一个答案,我对去哪里看有了更好的想法。据我所知,这是 Windows SMB 重定向器中的故意行为。它有一个打开文件句柄的缓存,每 x 秒收集一次。这样做是为了透明地优化经常 open/close/reopen 文件的应用程序。有许多注册表项应该会影响这一点(请参阅 https://support.microsoft.com/en-us/kb/102981, https://msdn.microsoft.com/en-us/library/windows/hardware/dn567661%28v=vs.85%29.aspx, https://social.msdn.microsoft.com/Forums/en-US/832d395b-6e6f-4658-8dbb-120138a4cd7c/smb2-registry-settings?forum=os_fileservices),但其中 none 似乎会导致立即对 CloseHandle 发出 SMB 关闭请求。 我能得到的最好结果是将 CacheFileTimeout 设置为 0。这仍然会留下 3-5 秒之间的延迟(可能取决于触发清理计时器的频率)。

不完全是答案(抱歉),但我认为这也可能发生在本地文件上。我正在研究在 Windows 和 Linux 上运行的 backup software,它有一个测试套件,在此期间它打开一个旧文件,读取其内容,写入一个新文件,关闭旧的然后重命名它。

在 Unix 上这很好,因为我们可以重命名现有文件,甚至是打开的文件。在 Windows 上,如果我不先关闭文件句柄,这些测试就会可靠地失败。然而,即使我这样做了,测试也会随机失败大约 10% 的时间,比如 this:

TRACE: Trying to create testfiles/0_0\backup234567\refcount.rdb.rfw shared using dwShareMode FILE_SHARE_READ | FILE_SHARE_WRITE [check fix] WARNING: Exception thrown: CommonException(OSFileError) (Failed to rename temporary refcount database file from testfiles/0_0\backup234567\refcount.rdb.rfwX to testfiles/0_0\backup234567\refcount.rdb.rfw: Access is denied. (5)) at C:\projects\boxbackup\lib\backupstore\BackupStoreRefCountDatabase.cpp:200

我知道在重命名之前我用 CloseHandle() 关闭了文件。这些症状可以用 CloseHandle() 异步发生来完美解释,但到目前为止,我的经验和这个问题是我拥有的唯一证据表明这可能会发生。这是在本地文件系统上,而不是 SMB(我认为,因为 AppVeyor 的内部结构对我来说有点像黑盒子)。

编辑: 我在 another Whosebug question and answer 中找到了确认和解释:

I believe this is covered in Windows Internals. The short story is that even though you've called CloseHandle on the file handle, the kernel may still have outstanding references that take a few milliseconds to close.