C ++ / Win32: Как дождаться завершения отложенного удаления?

Решено:
* Работоспособное решение: @sbi
* Объяснение того, что на самом деле происходит: @Hans
* Объяснение, почему OpenFile не проходит через «УДАЛИТЬ ОЖИДАНИЕ»: @Benjamin

Проблема:
Наше программное обеспечение в значительной степени является механизмом интерпретатора для собственного языка сценариев. Этот язык сценариев имеет возможность создавать файл, обрабатывать его, а затем удалять файл. Это все отдельные операции, и никакие файлы не поддерживаются между этими операциями. (т. е. во время создания файла создается дескриптор, который используется для записи, затем закрывается. Во время обработки файла отдельный дескриптор файла открывает файл, читает его и закрывается в EOF. И, наконец, delete uses :: DeleteFile который использует только имя файла, а не дескриптор файла вообще).

Недавно мы поняли, что конкретный макрос (скрипт) иногда не может создать файл в какое-то случайное последующее время (т. Е. Он преуспевает в течение первых сотен итераций «создавать, обрабатывать, удалять», но когда он приходит назад к созданию его в первый раз, Windows отвечает «Access Denied»).

Глядя глубже в проблему, я написал очень простую программу, которая перебирает что-то вроде этого:

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 (). Посмотрев на ProcessMonitor sysinternal, я вижу, что основная проблема заключается в том, что в файле есть ожидающее удаления, когда я пытаюсь его создать снова.

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

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

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

Я не смог распознать любые аргументы CreateFile (), которые явно разрешали бы этот эфир. Независимо от того, какие аргументы имеет CreateFile, это действительно не нормально с открытием файла для любого доступа, когда файл ожидает удаления. И так как я могу видеть это поведение как в поле XP, так и в окне x64 Win7, я вполне уверен, что это основное поведение 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; } 

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

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

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

Редактировать # 2: Если Ханс прав (и я склонен верить его анализу), то перемещение может не помочь, потому что вы не сможете фактически переименовать файл, открытый другим процессом. (Но тогда вы могли бы, я не знаю этого.) Если это действительно так, единственный способ, который я могу придумать, – «продолжать пытаться». Вам придется подождать несколько миллисекунд и повторить попытку. Храните тайм-аут, чтобы отказаться, когда это не поможет.

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

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

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

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

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

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

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

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

У меня была точно такая же проблема, и я обнаружил, что Comodo Internet Security ( cmdagent.exe ) вносит свой вклад в эту проблему. Раньше у меня была двухъядерная машина, но когда я впервые перешел на Intel i7, мое рабочее программное обеспечение ( jam.exe от программного обеспечения Perfore) больше не работало, потому что оно имело тот же шаблон (затем удалите, затем создайте, но не проверите). После отладки проблемы я обнаружил, что 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 (это петля в петле). Вы можете видеть, что cmdagent.exe предположительно открыл файл, когда он закрыл свой дескриптор, а затем вдруг jam.exe теперь может открыть файл.

Конечно, предлагаемое решение ждать и повторять попытку, и все работает отлично.

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

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

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

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

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

редактировать

Будет ли флаг FILE_FLAG_DELETE_ON_CLOSE покупать что-нибудь?

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

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

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

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

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

[1] http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FFile%2FNtDeleteFile.html [2] https://msdn.microsoft.com/en-us /library/windows/hardware/ff566435(v=vs.85).aspx

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

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

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

  • Есть ли эквивалент Windows для fdopen для HANDLE?
  • Как подключиться к библиотеке с помощью Code :: Blocks?
  • Запуск процесса мониторинга в системе
  • Как открыть встроенный диалог копирования файлов?
  • Можно ли называть pthread_exit из main?
  • Как приложение Metro в Windows 8 взаимодействует с настольным настольным окном на одном компьютере?
  • получить имя процесса из идентификатора процесса (win32)
  • C # эквивалент DllMain в C (WinAPI)
  • Что такое __stdcall?
  • Как отправить текст в Блокнот в C # / Win32?
  • Динамически загружать функцию из DLL
  • Давайте будем гением компьютера.