Решено:
- Работоспособное решение: sbi answer
- Объяснение того, что на самом деле происходит: Ганс отвечает
- Объяснение того, почему OpenFile не проходит через "УДАЛИТЬ ОЖИДАНИЕ": ответ бенджамина
Проблема:
Наше программное обеспечение в значительной степени является механизмом интерпретации для проприетарного языка сценариев. Этот язык сценариев имеет возможность создавать файл, обрабатывать его, а затем удалять файл. Все это отдельные операции, и между этими операциями нет открытых файловых дескрипторов.
(Т.е. во время создания файла дескриптор создается, используется для записи, затем закрывается. Во время части обработки файла отдельный дескриптор файла открывает файл, читает из него и закрывается в 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;
}