C++/Win32:如何等待挂起的删除完成

2021-12-17 00:00:00 file-io winapi c++ ntfs

已解决:

  • 可行的解决方案:sbi的回答
  • 对真实情况的解释:汉斯的回答
  • OpenFile 不通过DELETE PENDING"的解释:本杰明的回答

问题:

我们的软件在很大程度上是一种专有脚本语言的解释器引擎.该脚本语言能够创建文件、处理文件,然后删除文件.这些都是独立的操作,在这些操作之间没有文件句柄保持打开状态.

Our software is in large part an interpreter engine for a proprietary scripting language. That scripting language has the ability to create a file, process it, and then delete the file. These are all separate operations, and no file handles are kept open in between these operations.

(即在文件创建过程中,创建了一个句柄,用于写入,然后关闭.在文件处理部分,一个单独的文件句柄打开文件,从中读取,并在EOF时关闭.并且最后,delete 使用 ::DeleteFile,它只使用文件名,根本不使用文件句柄).

(I.e. during the file creation, a handle is created, used for writing, then closed. During the file processing portion, a separate file handle opens the file, reads from it, and is closed at EOF. And finally, delete uses ::DeleteFile which only has use of a filename, not a file handle at all).

最近我们开始意识到一个特定的宏(脚本)有时无法在随机的后续时间创建文件(即它在创建、处理、删除"的前一百次迭代中成功,但是回到第一百次创建它时,Windows 回复拒绝访问").

Recently we've come to realize that a particular macro (script) fails sometimes to be able to create the file at some random subsequent time (i.e. it succeeds during the first hundred iterations of "create, process, delete", but when it comes back to creating it a hundred and first time, Windows replies "Access Denied").

深入研究这个问题,我编写了一个非常简单的程序,它循环如下:

Looking deeper into the issue, I have written a very simple program that loops over something like this:

while (true) {
    HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ,
                               NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return OpenFailed;

    const DWORD dwWrite = strlen(pszFilename);
    DWORD dwWritten;

    if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
        return WriteFailed;

    if (!CloseHandle(hFile))
        return CloseFailed;

    if (!DeleteFileA(pszFilename))
        return DeleteFailed;
}

如您所见,这直接针对 Win32 API,而且非常简单.我创建一个文件,写入它,关闭句柄,删除它,冲洗,重复......

As you can see, this is direct to the Win32 API and is pretty darn simple. I create a file, write to it, close the handle, delete it, rinse, repeat...

但在此过程中的某个地方,我会在 CreateFile() 调用期间收到拒绝访问 (5) 错误.查看 sysinternal 的 ProcessMonitor,我可以看到潜在的问题是,当我尝试再次创建文件时,文件上存在挂起的删除.

But somewhere along the line, I'll get an Access Denied (5) error during the CreateFile() call. Looking at sysinternal's ProcessMonitor, I can see that the underlying issue is that there is a pending delete on the file while I'm trying to create it again.

问题:

  • 有没有办法等待删除完成?
  • 有没有办法检测文件是否处于待删除状态?

我们已经尝试了第一个选项,只需在 HFILE 上使用 WaitForSingleObject().但是 HFILE 总是在 WaitForSingleObject 执行之前关闭,因此 WaitForSingleObject 总是返回 WAIT_FAILED.显然,试图等待关闭的句柄是行不通的.

We have tried the first option, by simply WaitForSingleObject() on the HFILE. But the HFILE is always closed before the WaitForSingleObject executes, and so WaitForSingleObject always returns WAIT_FAILED. Clearly, trying to wait for the closed handle doesn't work.

我可以等待文件所在文件夹的更改通知.但是,这似乎是一个非常耗费开销的问题,只是偶尔出现问题(即:在我对 Windows 7 x64 的测试中E6600 PC 通常在迭代 12000+ 次时失败――在其他机器上,它可能在迭代 7、15 或 56 次或永远不会发生).

I could wait on a change notification for the folder that the file exists in. However, that seems like an extremely overhead-intensive kludge to what is a problem only occasionally (to wit: in my tests on my Windows 7 x64 E6600 PC it typically fails on iteration 12000+ -- on other machines, it can happen as soon as iteration 7 or 15 or 56 or never).

我一直无法辨别任何明确允许此以太的 CreateFile() 参数.无论 CreateFile 有什么参数,当文件待删除时打开文件以供任何访问确实是不合适的.

I have been unable to discern any CreateFile() arguments that would explicitly allow for this ether. No matter what arguments CreateFile has, it really is not okay with opening a file for any access when the file is pending deletion.

而且由于我可以在 Windows XP 机器和 x64 Windows 7 机器上看到这种行为,我很确定这是微软按预期"的核心 NTFS 行为.所以我需要一个解决方案,允许操作系统在我尝试继续之前完成删除,最好不要不必要地占用 CPU 周期,并且没有查看该文件所在文件夹的极端开销(如果可能).

And since I can see this behavior on both an Windows XP box and on an x64 Windows 7 box, I am quite certain that this is core NTFS behavior "as intended" by Microsoft. So I need a solution that allows the OS to complete the delete before I attempt to proceed, preferably without tying up CPU cycles needlessly, and without the extreme overhead of watching the folder that this file is in (if possible).

1 是的,这个循环在写入失败或关闭失败时返回,但由于这是一个简单的控制台测试应用程序,应用程序本身退出,Windows 保证在进程完成时操作系统关闭所有句柄.所以这里不存在泄漏.

1 Yes, this loop returns on a failure to write or a failure to close which leaks, but since this is a simple console test application, the application itself exits, and Windows guarantees that all handles are closed by the OS when a process completes. So no leaks exist here.

bool DeleteFileNowA(const char * pszFilename)
{
    // Determine the path in which to store the temp filename
    char szPath[MAX_PATH];
    strcpy(szPath, pszFilename);
    PathRemoveFileSpecA(szPath);

    // Generate a guaranteed to be unique temporary filename to house the pending delete
    char szTempName[MAX_PATH];
    if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
        return false;

    // Move the real file to the dummy filename
    if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
        return false;

    // Queue the deletion (the OS will delete it when all handles (ours or other processes) close)
    if (!DeleteFileA(szTempName))
        return false;

    return true;
}

推荐答案

先把要删除的文件重命名,再删除.

First rename the file to be deleted, and then delete it.

使用GetTempFileName() 获取唯一名称,然后使用MoveFile() 重命名文件.然后删除重命名的文件.如果实际删除确实是异步的,并且可能与同一文件的创建发生冲突(正如您的测试所表明的那样),这应该可以解决问题.

Use GetTempFileName() to obtain a unique name, and then use MoveFile() to rename the file. Then delete the renamed file. If the actual deletion is indeed asynchronous and might conflict with the creation of the same file (as your tests seems to indicate), this should solve the problem.

当然,如果你的分析是对的,文件操作有点异步,这可能会导致你在重命名完成之前尝试删除文件的问题.但是你可以一直尝试在后台线程中删除.

Of course, if your analysis is right and file operations happen somewhat asynchronous, this might introduce the problem that you attempt to delete the file before the renaming is done. But then you could always keep trying to delete in a background thread.

如果 Hans 是对的(我倾向于相信他的分析),那么移动可能没有真正的帮助,因为您可能无法真正重命名由另一个进程打开的文件.(但是你可能会,我不知道这一点.)如果确实如此,我能想出的唯一其他方法就是继续尝试".您将不得不等待几毫秒并重试.如果这没有帮助,请保持暂停以放弃.

If Hans is right (and I'm inclined to believe his analysis), then moving might not really help, because you might not be able to actually rename a file that's open by another process. (But then you might, I don't know this.) If that's indeed the case, the only other way I can come up with is "keep trying". You would have to wait for a few milliseconds and retry. Keep a timeout to give up when this doesn't help.

相关文章