最近关闭的文件上的DeleteFile操作失败

11

我有一个单线程程序(C ++,Win32,NTFS),首先创建一个相当长的临时文件,关闭它,以只读方式打开,读取后再次关闭并尝试使用DeleteFile()删除它。

通常情况下,这个过程顺利完成,但有时候DeleteFile()失败,GetLastError()返回ERROR_ACCESS_DENIED。确保文件不是只读的。这种情况会发生在任何大小的文件上,但是随着文件大小的增加,概率也会增加。

有什么想法可能会锁定该文件吗?我尝试使用WinInternals工具进行检查,并没有发现任何可疑的情况。


你确定在尝试删除文件之前已经正确关闭了它吗?有没有遗漏任何句柄? - RageZ
正如我所说的,我甚至使用WinInternals工具进行了检查。所有开放操作都与关闭操作配对,但删除操作失败了。添加1秒的休眠时间可以解决这个问题。 - Dmitry Shkolnik
可能是Windows出了点问题,但我对此有些怀疑。如果加上“sleep”可以让它正常工作的话,那应该没什么问题啦 ^^ - RageZ
3
通常情况下,如果在某个位置添加一个sleep()函数调用可以解决问题,那么你需要摆脱sleep()函数并正确地解决问题。否则它会再次出现。我从未见过这个规则的例外。 - Ori Pessach
这并没有提供问题的答案。如果你想对作者进行批评或请求澄清,请在他们的帖子下方留言。 - Fred
7个回答

12

Windows系统因此问题而臭名昭著。SQLite通过每100毫秒重试删除操作,最多重试一定次数来处理此问题。

我认为,如果您确定没有打开的句柄,在您的代码中这样做可以避免一些头疼的问题,例如杀毒软件打开了该文件。

供参考,以下是SQLite源代码中的注释:

/*                                                                     
** Delete the named file.                                              
**                                                                     
** Note that windows does not allow a file to be deleted if some other
** process has it open.  Sometimes a virus scanner or indexing program
** will open a journal file shortly after it is created in order to do
** whatever it does.  While this other process is holding the          
** file open, we will be unable to delete it.  To work around this     
** problem, we delay 100 milliseconds and try to delete again.  Up     
** to MX_DELETION_ATTEMPTs deletion attempts are run before giving     
** up and returning an error.                                          
*/

10

猜测一下 - 您是否安装了任何反病毒软件?如果您有,是否尝试禁用其中的实时保护功能?


5
我相信这在Windows Internals中已经涵盖了。简单来说,即使您已经在文件句柄上调用了CloseHandle,内核仍可能有未完成的引用需要几毫秒才能关闭。
当您使用FILE_FLAG_DELETE_ON_CLOSE标志打开最后一个句柄时,更可靠的删除文件的方法是。如果您可以在读/写之间避免关闭文件,则效果更好。
#include <windows.h>
#include <stdio.h>

int wmain(int argc, wchar_t** argv)
{
    LPCWSTR fileName = L"c:\\temp\\test1234.bin";

    HANDLE h1 = CreateFileW(
        fileName,
        GENERIC_WRITE,
        // make sure the next call to CreateFile can succeed if this handle hasn't been closed yet
        FILE_SHARE_READ | FILE_SHARE_DELETE,
        NULL,
        CREATE_ALWAYS,
        FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_TEMPORARY,
        NULL);
    if (h1 == INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "h1 failed: 0x%x\n", GetLastError());
        return GetLastError();
    }

    HANDLE h2 = CreateFileW(
        fileName,
        GENERIC_READ,
        // FILE_SHARE_WRITE is required in case h1 with GENERIC_WRITE access is still open
        FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        // tell the OS to delete the file as soon as it is closed, no DeleteFile call needed
        FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_TEMPORARY,
        NULL);
    if (h2 == INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "h2 failed: 0x%x\n", GetLastError());
        return GetLastError();
    }

    return 0;
}

如果根据文档的以下描述,另一个进程已经打开了同一个文件,那么这个操作不会成功吗?“如果文件已经存在打开的句柄,除非它们都是使用FILE_SHARE_DELETE共享模式打开的,否则调用将失败。” - Ori Pessach
是的,这就是为什么我建议在写入和读取之间不要关闭文件句柄。使用 FILE_FLAG_DELETE_ON_CLOSE 创建第一个句柄,如果您确实需要一个没有写入权限的文件句柄,则使用 ReOpenFile 或 DuplicateHandle。 - Nathan Howell
也许我今天有点慢,但如果有人在最后一次调用CreateFile之前偷偷打开文件,这仍然会是一个问题,因为最后一次调用仍将失败。 - Ori Pessach
如果您在CreateFile的单个调用上使用FILE_FLAG_DELETE_ON_CLOSE,则不会有文件泄漏的可能性。否则就有一切皆有可能的情况。如果您需要具有不同标志或权限的文件句柄,则应使用ReOpenFile。 - Nathan Howell
明白了,这不是问题的重点,但似乎有一个很好的理由来构建代码。 - Ori Pessach

4
在调用DeleteFile()之前添加MessageBox()调用,当它出现时,请运行sysinternals工具Process Explorer。搜索文件的打开句柄。很可能您没有关闭所有文件句柄...

1
那就是我开始的地方。没有句柄。所以,我记录了文件的所有访问,但没发现什么特别的。 - Dmitry Shkolnik
听起来像是竞争条件(可能在毫秒级别),所以除非你冻结所有东西,否则你可能无法以这种方式重现错误。(但尝试肯定有助于缩小可能性。) - Roger Pate

1

也许更改仍然被缓存,尚未保存?

您可以通过在文件句柄上添加WaitForSingleObject来进行检查以确保。


4
写缓存对应用程序是透明的。它不应该导致行为上的改变。 - rep_movsd

1
你可能遇到了竞态条件。 1. 操作系统被要求写入数据。 2. 操作系统被要求关闭文件。这会提示最终的缓冲区刷新。在缓冲区刷新完成之前,文件不会被关闭。同时,操作系统将在处理缓冲区刷新时将控制权返回给程序。 3. 操作系统被要求删除文件。如果刷新尚未完成,则文件仍然处于打开状态,请求将被拒绝。

-3
#include <iostream>
#include <windows.h>

int main(int argc, const char * argv[])
{
    // Get a pointer to the file name/path
    const char * pFileToDelete = "h:\\myfile.txt";
    bool RemoveDirectory("h:\\myfile.txt");

    // try deleting it using DeleteFile
    if(DeleteFile(pFileToDelete ))
    {
        // succeeded
        std::cout << "Deleted file"  << std::endl;
    }
    else
    {
        // failed
        std::cout << "Failed to delete the file" << std::endl;
    }
    std::cin.get();
    return 0;
}  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接