Commit 38541d69 authored by Kenton Varda's avatar Kenton Varda

Adjust win32 recursive delete logic.

Now that I know the actual reasoning behind the loop, I think it's not the best answer. Instead, we should retry removing the directory if it reports not-empty, but only a few times before we give up.
parent 609ea777
......@@ -142,49 +142,53 @@ private:
auto glob = join16(path, L"\\*");
for (;;) {
WIN32_FIND_DATAW data;
HANDLE handle = FindFirstFileW(glob.begin(), &data);
if (handle == INVALID_HANDLE_VALUE) {
auto error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) return;
KJ_FAIL_WIN32("FindFirstFile", error, path) { return; }
}
KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; });
bool foundAny = false;
do {
// Ignore "." and "..", ugh.
if (data.cFileName[0] == L'.') {
if (data.cFileName[1] == L'\0' ||
(data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) {
continue;
}
WIN32_FIND_DATAW data;
HANDLE handle = FindFirstFileW(glob.begin(), &data);
if (handle == INVALID_HANDLE_VALUE) {
auto error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) return;
KJ_FAIL_WIN32("FindFirstFile", error, path) { return; }
}
KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; });
do {
// Ignore "." and "..", ugh.
if (data.cFileName[0] == L'.') {
if (data.cFileName[1] == L'\0' ||
(data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) {
continue;
}
}
String utf8Name = decodeWideString(arrayPtr(data.cFileName, wcslen(data.cFileName)));
KJ_EXPECT(!utf8Name.startsWith(".kj-tmp."), "temp file not cleaned up", utf8Name);
auto child = join16(path, data.cFileName);
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
recursiveDelete(child);
} else {
KJ_WIN32(DeleteFileW(child.begin()));
}
} while (FindNextFileW(handle, &data));
String utf8Name = decodeWideString(arrayPtr(data.cFileName, wcslen(data.cFileName)));
KJ_EXPECT(!utf8Name.startsWith(".kj-tmp."), "temp file not cleaned up", utf8Name);
auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
KJ_FAIL_WIN32("FindNextFile", error, path) { return; }
auto child = join16(path, data.cFileName);
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
recursiveDelete(child);
} else {
KJ_WIN32(DeleteFileW(child.begin()));
}
} while (FindNextFileW(handle, &data));
if (!foundAny) {
return;
}
auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
KJ_FAIL_WIN32("FindNextFile", error, path) { return; }
}
KJ_WIN32(RemoveDirectoryW(path.begin()));
uint retryCount = 0;
retry:
KJ_WIN32_HANDLE_ERRORS(RemoveDirectoryW(path.begin())) {
case ERROR_DIR_NOT_EMPTY:
if (retryCount++ < 10) {
Sleep(10);
goto retry;
}
// fallthrough
default:
KJ_FAIL_WIN32("RemoveDirectory", error) { break; }
}
}
};
......
......@@ -151,54 +151,58 @@ static String dbgStr(ArrayPtr<const wchar_t> wstr) {
static void rmrfChildren(ArrayPtr<const wchar_t> path) {
auto glob = join16(path, L"*");
// According to Niall Douglas, on Windows, deleting all files in a directory requires repeatedly
// scanning the directory until it is found to be empty. I couldn't find an explanation why, but
// my guess is that deleting a file while a search is in progress could reorder the remaining
// files causing the iterator to possibly skip things?
//
// Anyway, hence the loop.
for (;;) {
WIN32_FIND_DATAW data;
HANDLE handle = FindFirstFileW(glob.begin(), &data);
if (handle == INVALID_HANDLE_VALUE) {
auto error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) return;
KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(glob)) { return; }
}
KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; });
WIN32_FIND_DATAW data;
HANDLE handle = FindFirstFileW(glob.begin(), &data);
if (handle == INVALID_HANDLE_VALUE) {
auto error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) return;
KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(glob)) { return; }
}
KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; });
bool foundAny = false;
do {
// Ignore "." and "..", ugh.
if (data.cFileName[0] == L'.') {
if (data.cFileName[1] == L'\0' ||
(data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) {
continue;
}
do {
// Ignore "." and "..", ugh.
if (data.cFileName[0] == L'.') {
if (data.cFileName[1] == L'\0' ||
(data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) {
continue;
}
}
foundAny = true;
auto child = join16(path, data.cFileName);
// For rmrf purposes, we assume any "reparse points" are symlink-like, even if they aren't
// actually the "symbolic link" reparse type, because we don't want to recursively delete any
// shared content.
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
rmrfChildren(child);
KJ_WIN32(RemoveDirectoryW(child.begin()));
} else {
KJ_WIN32(DeleteFileW(child.begin()));
auto child = join16(path, data.cFileName);
// For rmrf purposes, we assume any "reparse points" are symlink-like, even if they aren't
// actually the "symbolic link" reparse type, because we don't want to recursively delete any
// shared content.
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
rmrfChildren(child);
uint retryCount = 0;
retry:
KJ_WIN32_HANDLE_ERRORS(RemoveDirectoryW(child.begin())) {
case ERROR_DIR_NOT_EMPTY:
// On Windows, deleting a file actually only schedules it for deletion. Under heavy
// load it may take a bit for the deletion to go through. Or, if another process has
// the file open, it may not be deleted until that process closes it.
//
// We'll repeatedly retry for up to 100ms, then give up. This is awful but there's no
// way to tell for sure if the system is just being slow or if someone has the file
// open.
if (retryCount++ < 10) {
Sleep(10);
goto retry;
}
// fallthrough
default:
KJ_FAIL_WIN32("RemoveDirectory", error, dbgStr(child)) { break; }
}
} while (FindNextFileW(handle, &data));
auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
KJ_FAIL_WIN32("FindNextFile", error, dbgStr(path)) { return; }
} else {
KJ_WIN32(DeleteFileW(child.begin()));
}
} while (FindNextFileW(handle, &data));
if (!foundAny) {
return;
}
auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
KJ_FAIL_WIN32("FindNextFile", error, dbgStr(path)) { return; }
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment