Fix disconnected network files hanging while saving

This PR prevent hanging when user: Open a network file, modify it. Disconnect the network, then save the file.
It also prevents the zombie process due to blocked CreateFile left behind.

Remove the timeout thread for CreateFile to prevent the zombie process. Use another way for the detection:
If the result of network file existent detection is false, and the network problem found (timeout reached), we just stop and don't call CreateFile routine.

Ref: 1445487

Improve #4306, #6178, #8055, #11388, #12553, #15540
This commit is contained in:
Don Ho 2024-10-11 18:27:20 +02:00
parent 0ca0348e7f
commit 3b6f22b357
4 changed files with 46 additions and 142 deletions

View File

@ -1771,70 +1771,6 @@ bool Version::isCompatibleTo(const Version& from, const Version& to) const
#define DEFAULT_MILLISEC 1000 #define DEFAULT_MILLISEC 1000
//----------------------------------------------------
struct GetAttrParamResult {
wstring _filePath;
DWORD _fileAttr = INVALID_FILE_ATTRIBUTES;
bool _isNetworkFailure = true;
};
DWORD WINAPI getFileAttributesWorker(void* data)
{
GetAttrParamResult* inAndOut = static_cast<GetAttrParamResult*>(data);
inAndOut->_fileAttr = ::GetFileAttributesW(inAndOut->_filePath.c_str());
inAndOut->_isNetworkFailure = false;
return ERROR_SUCCESS;
};
DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
GetAttrParamResult data(filePath);
HANDLE hThread = ::CreateThread(NULL, 0, getFileAttributesWorker, &data, 0, NULL);
if (!hThread)
{
return INVALID_FILE_ATTRIBUTES;
}
// wait for our worker thread to complete or terminate it when the required timeout has elapsed
DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milliSec2wait == 0 ? DEFAULT_MILLISEC : milliSec2wait);
switch (dwWaitStatus)
{
case WAIT_OBJECT_0: // Ok, the state of our worker thread is signaled, so it finished itself in the timeout given
// - nothing else to do here, except the thread handle closing later
break;
case WAIT_TIMEOUT: // the timeout interval elapsed, but the worker's state is still non-signaled
default: // any other dwWaitStatus is a BAD one here
// WAIT_FAILED or WAIT_ABANDONED
::TerminateThread(hThread, dwWaitStatus);
break;
}
CloseHandle(hThread);
if (isNetWorkProblem != nullptr)
*isNetWorkProblem = data._isNetworkFailure;
return data._fileAttr;
};
bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
DWORD attr = getFileAttrWaitSec(filePath, milliSec2wait, isNetWorkProblem);
return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY));
}
bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
DWORD attr = getFileAttrWaitSec(dirPath, milliSec2wait, isNetWorkProblem);
return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY));
}
bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isNetWorkProblem)
{
DWORD attr = getFileAttrWaitSec(path, milliSec2wait, isNetWorkProblem);
return (attr != INVALID_FILE_ATTRIBUTES);
}
//---------------------------------------------------- //----------------------------------------------------
@ -1855,7 +1791,7 @@ DWORD WINAPI getDiskFreeSpaceExWorker(void* data)
return ERROR_SUCCESS; return ERROR_SUCCESS;
}; };
DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milliSec2wait, bool* isNetWorkProblem) DWORD getDiskFreeSpaceWithTimeout(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milliSec2wait, bool* isNetWorkProblem)
{ {
GetDiskFreeSpaceParamResult data(dirPath); GetDiskFreeSpaceParamResult data(dirPath);
@ -1911,7 +1847,7 @@ DWORD WINAPI getFileAttributesExWorker(void* data)
return ERROR_SUCCESS; return ERROR_SUCCESS;
}; };
DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milliSec2wait, bool* isNetWorkProblem) DWORD getFileAttributesExWithTimeout(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milliSec2wait, bool* isNetWorkProblem)
{ {
GetAttrExParamResult data(filePath); GetAttrExParamResult data(filePath);
@ -1945,61 +1881,23 @@ DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_D
return data._result; return data._result;
} }
bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem)
//----------------------------------------------------
struct CreateFileParamResult
{ {
wstring _filePath; WIN32_FILE_ATTRIBUTE_DATA attributes{};
HANDLE _hFile = INVALID_HANDLE_VALUE; getFileAttributesExWithTimeout(filePath, &attributes, milliSec2wait, isNetWorkProblem);
DWORD _accessParam = GENERIC_READ | GENERIC_WRITE; return (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES && !(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
DWORD _shareParam = FILE_SHARE_READ | FILE_SHARE_WRITE;
DWORD _dispParam = CREATE_ALWAYS;
DWORD _attribParam = FILE_ATTRIBUTE_NORMAL;
bool _isNetworkFailure = true;
CreateFileParamResult(wstring filePath, DWORD accessParam, DWORD shareParam, DWORD dispParam, DWORD attribParam) :
_filePath(filePath), _accessParam(accessParam), _shareParam(shareParam), _dispParam(dispParam), _attribParam(attribParam) {};
};
DWORD WINAPI createFileWorker(void* data)
{
CreateFileParamResult* inAndOut = static_cast<CreateFileParamResult*>(data);
inAndOut->_hFile = ::CreateFileW(inAndOut->_filePath.c_str(), inAndOut->_accessParam, inAndOut->_shareParam, NULL, inAndOut->_dispParam, inAndOut->_attribParam, NULL);
inAndOut->_isNetworkFailure = false;
return ERROR_SUCCESS;
};
HANDLE createFileWaitSec(const wchar_t* filePath, DWORD accessParam, DWORD shareParam, DWORD dispParam, DWORD attribParam, DWORD milliSec2wait, bool* isNetWorkProblem)
{
CreateFileParamResult data(filePath, accessParam, shareParam, dispParam, attribParam);
HANDLE hThread = ::CreateThread(NULL, 0, createFileWorker, &data, 0, NULL);
if (!hThread)
{
return INVALID_HANDLE_VALUE;
} }
// wait for our worker thread to complete or terminate it when the required timeout has elapsed bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait, bool* isNetWorkProblem)
DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milliSec2wait == 0 ? DEFAULT_MILLISEC : milliSec2wait);
switch (dwWaitStatus)
{ {
case WAIT_OBJECT_0: // Ok, the state of our worker thread is signaled, so it finished itself in the timeout given WIN32_FILE_ATTRIBUTE_DATA attributes{};
// - nothing else to do here, except the thread handle closing later getFileAttributesExWithTimeout(dirPath, &attributes, milliSec2wait, isNetWorkProblem);
break; return (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES && (attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
case WAIT_TIMEOUT: // the timeout interval elapsed, but the worker's state is still non-signaled
default: // Timeout reached, or WAIT_FAILED or WAIT_ABANDONED
// attempt to cancel the operation
::CancelIoEx(data._hFile, NULL);
::TerminateThread(hThread, dwWaitStatus);
break;
}
CloseHandle(hThread);
if (isNetWorkProblem != nullptr)
*isNetWorkProblem = data._isNetworkFailure;
return data._hFile;
} }
bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isNetWorkProblem)
{
WIN32_FILE_ATTRIBUTE_DATA attributes{};
getFileAttributesExWithTimeout(path, &attributes, milliSec2wait, isNetWorkProblem);
return (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES);
}

View File

@ -282,11 +282,10 @@ private:
unsigned long _build = 0; unsigned long _build = 0;
}; };
DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getDiskFreeSpaceWithTimeout(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getFileAttributesExWithTimeout(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr); bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr); bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
bool doesPathExist(const wchar_t* path, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr); bool doesPathExist(const wchar_t* path, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
HANDLE createFileWaitSec(const wchar_t* filePath, DWORD accessParam, DWORD shareParam, DWORD dispParam, DWORD attribParam, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);

View File

@ -33,9 +33,9 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
WIN32_FILE_ATTRIBUTE_DATA attributes_original{}; WIN32_FILE_ATTRIBUTE_DATA attributes_original{};
DWORD dispParam = CREATE_ALWAYS; DWORD dispParam = CREATE_ALWAYS;
bool fileExists = false; bool fileExists = false;
bool hasNetworkProblem = false;
// Store the file creation date & attributes for a possible use later... // Store the file creation date & attributes for a possible use later...
if (getFileAttributesExWaitSec(fname, &attributes_original)) if (getFileAttributesExWithTimeout(fname, &attributes_original, 0, &hasNetworkProblem))
{ {
fileExists = (attributes_original.dwFileAttributes != INVALID_FILE_ATTRIBUTES); fileExists = (attributes_original.dwFileAttributes != INVALID_FILE_ATTRIBUTES);
} }
@ -51,16 +51,22 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
FindClose(hFind); FindClose(hFind);
} }
} }
else
{
bool isFromNetwork = PathIsNetworkPath(fname);
if (isFromNetwork && hasNetworkProblem) // The file doesn't exist, and the file is a network file, plus the network problem has been detected due to timeout
return; // In this case, we don't call createFile to prevent hanging
}
_hFile = ::createFileWaitSec(fname, _accessParam, _shareParam, dispParam, _attribParam); _hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
// Race condition management: // Race condition management:
// If file didn't exist while calling PathFileExistsW, but before calling CreateFileW, file is created: use CREATE_ALWAYS is OK // If file didn't exist while calling getFileAttributesExWithTimeout, but before calling CreateFileW, file is created: use CREATE_ALWAYS is OK
// If file did exist while calling PathFileExistsW, but before calling CreateFileW, file is deleted: use TRUNCATE_EXISTING will cause the error // If file did exist while calling getFileAttributesExWithTimeout, but before calling CreateFileW, file is deleted: use TRUNCATE_EXISTING will cause the error
if (dispParam == TRUNCATE_EXISTING && _hFile == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_FILE_NOT_FOUND) if (dispParam == TRUNCATE_EXISTING && _hFile == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_FILE_NOT_FOUND)
{ {
dispParam = CREATE_ALWAYS; dispParam = CREATE_ALWAYS;
_hFile = ::createFileWaitSec(fname, _accessParam, _shareParam, dispParam, _attribParam); _hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
} }
if (fileExists && (dispParam == CREATE_ALWAYS) && (_hFile != INVALID_HANDLE_VALUE)) if (fileExists && (dispParam == CREATE_ALWAYS) && (_hFile != INVALID_HANDLE_VALUE))

View File

@ -142,7 +142,7 @@ void Buffer::updateTimeStamp()
{ {
FILETIME timeStampLive {}; FILETIME timeStampLive {};
WIN32_FILE_ATTRIBUTE_DATA attributes{}; WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (getFileAttributesExWaitSec(_fullPathName.c_str(), &attributes) != FALSE) if (getFileAttributesExWithTimeout(_fullPathName.c_str(), &attributes) != FALSE)
{ {
timeStampLive = attributes.ftLastWriteTime; timeStampLive = attributes.ftLastWriteTime;
} }
@ -311,7 +311,7 @@ bool Buffer::checkFileState() // returns true if the status has been changed (it
isOK = true; isOK = true;
} }
} }
else if (getFileAttributesExWaitSec(_fullPathName.c_str(), &attributes) != FALSE) else if (getFileAttributesExWithTimeout(_fullPathName.c_str(), &attributes) != FALSE)
{ {
int mask = 0; //status always 'changes', even if from modified to modified int mask = 0; //status always 'changes', even if from modified to modified
bool isFileReadOnly = attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY; bool isFileReadOnly = attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY;
@ -710,7 +710,7 @@ BufferID FileManager::loadFile(const wchar_t* filename, Document doc, int encodi
if (pPath) if (pPath)
{ {
WIN32_FILE_ATTRIBUTE_DATA attributes{}; WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (getFileAttributesExWaitSec(pPath, &attributes) != FALSE) if (getFileAttributesExWithTimeout(pPath, &attributes) != FALSE)
{ {
LARGE_INTEGER size{}; LARGE_INTEGER size{};
size.LowPart = attributes.nFileSizeLow; size.LowPart = attributes.nFileSizeLow;
@ -845,7 +845,7 @@ bool FileManager::reloadBuffer(BufferID id)
//Get file size //Get file size
int64_t fileSize = 0; int64_t fileSize = 0;
WIN32_FILE_ATTRIBUTE_DATA attributes{}; WIN32_FILE_ATTRIBUTE_DATA attributes{};
getFileAttributesExWaitSec(buf->getFullPathName(), &attributes); getFileAttributesExWithTimeout(buf->getFullPathName(), &attributes);
if (attributes.dwFileAttributes == INVALID_FILE_ATTRIBUTES) if (attributes.dwFileAttributes == INVALID_FILE_ATTRIBUTES)
{ {
return false; return false;
@ -1193,7 +1193,7 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
const wchar_t* currentBufFilePath = buffer->getFullPathName(); const wchar_t* currentBufFilePath = buffer->getFullPathName();
ULARGE_INTEGER freeBytesForUser; ULARGE_INTEGER freeBytesForUser;
BOOL getFreeSpaceSuccessful = getDiskFreeSpaceWaitSec(dirDest, &freeBytesForUser); BOOL getFreeSpaceSuccessful = getDiskFreeSpaceWithTimeout(dirDest, &freeBytesForUser);
if (getFreeSpaceSuccessful) if (getFreeSpaceSuccessful)
{ {
int64_t fileSize = buffer->getFileLength(); int64_t fileSize = buffer->getFileLength();
@ -1208,12 +1208,13 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
return SavingStatus::NotEnoughRoom; return SavingStatus::NotEnoughRoom;
} }
DWORD attrib = getFileAttrWaitSec(fullpath); WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (attrib != INVALID_FILE_ATTRIBUTES) getFileAttributesExWithTimeout(fullpath, &attributes);
if (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES)
{ {
isHiddenOrSys = (attrib & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0; isHiddenOrSys = (attributes.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0;
if (isHiddenOrSys) if (isHiddenOrSys)
::SetFileAttributes(filename, attrib & ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)); ::SetFileAttributes(filename, attributes.dwFileAttributes & ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM));
} }
UniMode mode = buffer->getUnicodeMode(); UniMode mode = buffer->getUnicodeMode();
@ -1274,7 +1275,7 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
} }
if (isHiddenOrSys) if (isHiddenOrSys)
::SetFileAttributes(fullpath, attrib); ::SetFileAttributes(fullpath, attributes.dwFileAttributes);
if (isCopy) // "Save a Copy As..." command if (isCopy) // "Save a Copy As..." command
{ {