Подтвердить что ты не робот

С++/Win32: как ждать завершения удаления, ожидающего завершения

Решено:

Проблема:

Наше программное обеспечение в значительной степени является механизмом интерпретации для проприетарного языка сценариев. Этот язык сценариев имеет возможность создавать файл, обрабатывать его, а затем удалять файл. Все это отдельные операции, и между этими операциями нет открытых файловых дескрипторов.

(Т.е. во время создания файла дескриптор создается, используется для записи, затем закрывается. Во время части обработки файла отдельный дескриптор файла открывает файл, читает из него и закрывается в EOF. И, наконец, delete использует :: DeleteFile, который использует только имя файла, а не дескриптор файла).

Недавно мы поняли, что конкретный макрос (скрипт) иногда не может создать файл в случайное время (то есть, он успешно выполняется во время первых сотен итераций "создать, обработать, удалить"), но когда это происходит Вернувшись к созданию его в первый раз, Windows отвечает "Отказано в доступе").

Если углубиться в проблему, я написал очень простую программу, которая выглядит примерно так:

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 и чертовски просто. Я создаю файл, пишу в него, закрываю ручку, удаляю его, промываю, повторяю...

Но где-то на линии я получу ошибку Access Denied (5) во время вызова CreateFile(). Глядя на sysinternal ProcessMonitor, я вижу, что основная проблема заключается в ожидающем удалении файла, пока я пытаюсь создать его снова.

Вопросы:

  • Можно ли дождаться завершения удаления?
  • Есть ли способ определить, что файл ожидает удаления?

Мы опробовали первый вариант, просто WaitForSingleObject() в HFILE. Но HFILE всегда закрывается до выполнения WaitForSingleObject, и поэтому WaitForSingleObject всегда возвращает WAIT_FAILED. Очевидно, что попытка дождаться закрытой ручки не работает.

Я мог бы подождать уведомления об изменении папки, в которой находится файл. Однако это кажется чрезвычайно трудоемким вмешательством в проблему, которая возникает лишь изредка (например: в моих тестах на моем компьютере с Windows 7 x64 E6600 это обычно не удается на итерации 12000+ - на других машинах это может произойти, как только итерация 7 или 15 или 56 или никогда).

Мне не удалось различить аргументы CreateFile(), которые явно разрешают этот эфир. Независимо от того, какие аргументы есть у CreateFile, на самом деле не совсем нормально открывать файл для любого доступа, когда файл ожидает удаления.

И так как я могу видеть это поведение как на Windows XP, так и на 64-битной Windows 7, я совершенно уверен, что это основное поведение NTFS "как задумано" Microsoft. Поэтому мне нужно решение, которое позволит ОС завершить удаление до того, как я попытаюсь продолжить, предпочтительно без необходимости загружать циклы ЦП и без чрезмерных затрат на просмотр папки, в которой находится этот файл (если это возможно).

1 Да, этот цикл возвращает сообщение об ошибке записи или невозможности закрыть утечки, но поскольку это простое консольное тестовое приложение, само приложение закрывается, и Windows гарантирует, что все дескрипторы закрыты ОС, когда Процесс завершен. Так что здесь нет утечек.

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;
}
4b9b3361

Ответ 1

Сначала переименуйте файл, который нужно удалить, а затем удалите его.

Используйте GetTempFileName(), чтобы получить уникальное имя, а затем используйте MoveFile(), чтобы переименовать файл. Затем удалите переименованный файл. Если фактическое удаление действительно является асинхронным и может конфликтовать с созданием того же файла (как показывают ваши тесты), это должно решить проблему.

Конечно, если ваш анализ верен и файловые операции выполняются в некоторой степени асинхронно, это может привести к проблеме, с которой вы пытаетесь удалить файл до того, как будет выполнено переименование. Но тогда вы всегда можете попытаться удалить в фоновом потоке.

Если Ганс прав (и я склонен верить его анализу), то перемещение может не очень помочь, потому что вы не сможете переименовать файл, открытый другим процессом. (Но тогда вы можете, я не знаю этого.) Если это действительно так, то единственный способ, которым я могу придумать, - это "продолжать пытаться". Вам придется подождать несколько миллисекунд и повторить попытку. Держите тайм-аут, чтобы сдаться, когда это не помогает.

Ответ 2

Есть другие процессы в Windows, которые хотят часть этого файла. Индекс поиска является очевидным кандидатом. Или вирусный сканер. Они откроют файл для полного совместного использования, включая FILE_SHARE_DELETE, чтобы другие процессы не сильно повлияли на их открытие файла.

Это обычно работает хорошо, если вы не создаете/пишете/удаляете с высокой скоростью. Удаление будет успешно выполнено, но файл не может исчезнуть из файловой системы, пока последний дескриптор к нему не будет закрыт. Дескриптор, удерживаемый, скажем, поисковым индексатором. Любая программа, которая пытается открыть этот файл, ожидающий удаления, будет заблокирована ошибкой 5.

В противном случае это общая проблема в многозадачной операционной системе, вы не можете знать, что другой процесс может испортить ваши файлы. Ваш образец использования кажется необычным, рассмотрите это сначала. Обходной путь должен был бы поймать ошибку, спать и попытаться снова. Или переместите файл в корзину с помощью SHFileOperation().

Ответ 3

Глупое предположение - поскольку это происходит так редко, просто подождите несколько миллисекунд после сбоя и попробуйте снова.

Или, если важна задержка, переключитесь на другое имя файла, оставив старый файл для последующего удаления.

Ответ 4

  Есть ли способ определить, что файл ожидает удаления?

Используйте функцию GetFileInformationByHandleEx со структурой FILE_STANDARD_INFO.

Но функция не может решить вашу проблему. sbi решение ни того, ни другого.

Ответ 5

Возможно, это не ваша конкретная проблема, но возможно, поэтому я предлагаю вам выйти Process Monitor (Sysinternals) и посмотреть.

У меня была точно такая же проблема, и я обнаружил, что Comodo Internet Security (cmdagent.exe) способствует этой проблеме. Раньше у меня был двухъядерный компьютер, но когда я обновился до Intel i7, внезапно мое рабочее программное обеспечение (jam.exe от Perfore Software) перестало работать, потому что у него был тот же шаблон (удаление, затем создание, но не проверка). После отладки проблемы я обнаружил, что GetLastError() возвращает отказ в доступе, но Process Monitor обнаруживает "отложенное удаление". Вот след:

10:39:10.1738151 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Read Attributes, Delete, Disposition: Open, Options: Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
10:39:10.1738581 AM jam.exe 5032    QueryAttributeTagFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Attributes: ANCI, ReparseTag: 0x0
10:39:10.1738830 AM jam.exe 5032    SetDispositionInformationFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Delete: True
10:39:10.1739216 AM jam.exe 5032    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1739438 AM jam.exe 5032    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1744837 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1788811 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1838276 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1888407 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1936323 AM System  4   FASTIO_ACQUIRE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS SyncType: SyncTypeOther
10:39:10.1936531 AM System  4   FASTIO_RELEASE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1936647 AM System  4   IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1939064 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1945733 AM cmdagent.exe    1188    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1946532 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1947020 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1948945 AM cfp.exe 1832    QueryOpen   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   FAST IO DISALLOWED  
10:39:10.1949781 AM cfp.exe 1832    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   NAME NOT FOUND  Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
10:39:10.1989720 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0, OpenResult: Created

Как вы можете видеть, есть запрос на удаление, после чего несколько попыток открыть файл снова jam.exe (это fopen в цикле). Вы можете видеть, что cmdagent.exe предположительно открыл файл, когда закрывает свой дескриптор, а затем внезапно jam.exe может открыть файл.

Конечно, предлагаемое решение подождать и попробовать еще раз, и оно работает просто отлично.

Ответ 6

Поскольку вы создаете новый файл, обрабатывая его, а затем удаляя его, это звучит так, будто вам не важно, что такое имя файла. Если это действительно так, вы должны всегда учитывать временный файл. Таким образом, каждый раз через процесс вам не нужно заботиться о том, чтобы файл еще не удалился.

Ответ 7

У меня на самом деле была та же проблема при использовании LoadLibrary (путь). Я не могу удалить файл в пути.

Решением было "закрыть дескриптор" или использовать метод FreeLibrary (путь).

ПРИМЕЧАНИЕ. Пожалуйста, прочтите "Замечания" на MSDN относительно FreeLibrary().

Ответ 8

Если CreateFile возвращает INVALID_HANDLE_VALUE, вы должны определить, что возвращает GetLastError в вашей конкретной ситуации (в ожидании удаления), и вернуться к CreateFile только на основе этого кода ошибки.

Флаг FILE_FLAG_DELETE_ON_CLOSE может вам что-то купить.

Ответ 9

В Windows Vista/Windows 7 есть DeleteFileTransacted, который удаляет файл с помощью транзакций, который гарантирует их удаление (очищает буферы файлов и т.д.). Для совместимости с Windows XP это не вариант.

Другая идея, как это можно сделать, - это использовать OpenFile() с флагом OF_CREATE, который устанавливает нулевую длину, если файл существует, или создает его, если его нет, и затем вызывает FlushFileBuffers для дескриптора файла, чтобы дождаться этой операции (делая файл нулевой длины) для завершения. По завершении файл имеет размер 0, а затем просто вызывает DeleteFile.

Позже вы можете проверить, существует ли файл или имеет нулевую длину, чтобы обработать его таким же образом.

Ответ 10

Согласно [1], вы можете использовать NtDeleteFile, чтобы избежать асинхронной природы DeleteFile. Также [1] дает некоторые подробности о том, как работает DeleteFile.

К сожалению, официальная документация NtDeleteFile [2] не содержит каких-либо конкретных подробностей по этому вопросу.

[1] Недокументированные функции NTDLL

[2] функция ZwDeleteFile

Ответ 11

Лучший ответ был предоставлен sbi, но в интересах полноты, некоторые люди могут также захотеть узнать о новом способе, доступном в Windows 10 RS1/1603.

Он включает вызов API SetFileInformationByHandle с классом FileDispositionInfoEx и установку флагов FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS. Смотрите полный ответ RbMm.

Ответ 12

Я думаю, что это просто плохой дизайн в файловой системе. Я видел ту же проблему, когда работал с портами связи, открывая/закрывая их.

К сожалению, я думаю, что самым простым решением было бы просто повторить попытку создания файла несколько раз, если вы получите INVALID_HANDLE_VALUE. GetLastError() также может дать вам лучший способ обнаружения именно этого INVALID_HANDLE_VALUE.

Я бы предпочел перекрывающийся ввод-вывод, но их CloseHandle() и DeleteFile() не обрабатывают перекрывающиеся операции :(