C ++ / Win32: как ждать завершения отложенного удаления? - PullRequest
31 голосов
/ 22 сентября 2010

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

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

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

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

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

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

Ответы [ 12 ]

20 голосов
/ 22 сентября 2010

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

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

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

15 голосов
/ 22 сентября 2010

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

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

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

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

5 голосов
/ 22 сентября 2010

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

4 голосов
/ 07 декабря 2010

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

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

4 голосов
/ 23 сентября 2010
  • Есть ли способ обнаружить, что файл ожидает удаления?

Использовать GetFileInformationByHandleEx со структурой FILE_STANDARD_INFO .

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

3 голосов
/ 22 сентября 2010

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

2 голосов
/ 13 февраля 2015

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

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

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

1 голос
/ 09 мая 2019

Лучший ответ дал sbi, но в интересах полноты некоторые люди могут также захотеть узнать о новом способе, доступном в Windows 10 RS1 / 1603.Он включает вызов API-интерфейса SetFileInformationByHandle с классом FileDispositionInfoEx и установку флагов FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS.См. Полный ответ от RbMm .

1 голос
/ 26 ноября 2015

Согласно [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

1 голос
/ 01 октября 2011

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

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

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

...