Skip to content

Commit

Permalink
Fix network files hanging while the network disconnected (part 4)
Browse files Browse the repository at this point in the history
Fix network file hanging issue due to win32API GetFileAttributes cache (unsynchronized).

STR:
1. Open a network file.
2. Close Notepad++ to have it in the session.
3. Disconnect the network, and launch Notepad++ immediately.
4. Around more than 1 minute's delay, then the "Error" dialog displayed.

The reason of hanging is that the network file was detected by "doesFileExist" as true, so Notepad++ was trying to open non-existent file (by _wfopen).
I believe that there's some kind of cache during the very short period for the IO function (here's our case GetFileAttributes), and such cache is not immediately synchronized (cleared) while network disconnected. As a result, when we launch Notepad++ after the disconnection of network, GetFileAttributes keeps its memory & responds "FileExists". However for _wfopen it can't find the resource of network anymore - that makes hanging.

Ref: notepad-plus-plus#15658 (comment)

Improve notepad-plus-plus#4306, notepad-plus-plus#6178, notepad-plus-plus#8055, notepad-plus-plus#11388, notepad-plus-plus#12553, notepad-plus-plus#15540
  • Loading branch information
donho committed Oct 7, 2024
1 parent d2fb03e commit 5e24e1d
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 16 deletions.
52 changes: 52 additions & 0 deletions PowerEditor/src/MISC/Common/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,58 @@ bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isNetWorkProb
return (attr != INVALID_FILE_ATTRIBUTES);
}


//----------------------------------------------------

struct wfopenParamResult
{
std::wstring _filePath;
FILE* _pFile = nullptr;
bool _isNetworkFailure = true;
wfopenParamResult(wstring filePath) : _filePath(filePath) {};
};

DWORD WINAPI wfopenWorker(void* data)
{
wfopenParamResult* inAndOut = static_cast<wfopenParamResult*>(data);
inAndOut->_pFile = _wfopen(inAndOut->_filePath.c_str(), L"rb");
inAndOut->_isNetworkFailure = false;
return ERROR_SUCCESS;
};

FILE* wfopenWaitSec(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
wfopenParamResult data(filePath);

HANDLE hThread = ::CreateThread(NULL, 0, wfopenWorker, &data, 0, NULL);
if (!hThread)
{
return FALSE;
}

// 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._pFile;
}


//----------------------------------------------------

struct GetDiskFreeSpaceParamResult
Expand Down
1 change: 1 addition & 0 deletions PowerEditor/src/MISC/Common/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,5 +287,6 @@ bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait = 0, bool* isNet
bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
bool doesPathExist(const wchar_t* path, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);

FILE* wfopenWaitSec(const wchar_t* filePath, 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);
78 changes: 62 additions & 16 deletions PowerEditor/src/ScintillaComponent/Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -702,22 +702,42 @@ BufferID FileManager::loadFile(const wchar_t* filename, Document doc, int encodi
//Get file size
int64_t fileSize = -1;
const wchar_t* pPath = filename;
std::wstring msg = pPath;
msg += L"\t BEFORE doesFileExist";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());
if (!doesFileExist(pPath))
{
pPath = backupFileName;
}
msg = pPath;
msg += L"\t AFTER doesFileExist";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());



if (pPath)
{
msg = pPath;
msg += L"\t BEFORE getFileAttributesExWaitSec";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());

WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (getFileAttributesExWaitSec(pPath, &attributes) != FALSE)
{
msg = pPath;
msg += L"\t getFileAttributesExWaitSec OK - let's retrieve file length";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());

LARGE_INTEGER size{};
size.LowPart = attributes.nFileSizeLow;
size.HighPart = attributes.nFileSizeHigh;

fileSize = size.QuadPart;
}

msg = pPath;
msg += L"\t AFTER getFileAttributesExWaitSec";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());
}

// * the auto-completion feature will be disabled for large files
Expand Down Expand Up @@ -777,8 +797,16 @@ BufferID FileManager::loadFile(const wchar_t* filename, Document doc, int encodi
loadedFileFormat._eolFormat = EolType::unknown;
loadedFileFormat._language = L_TEXT;

msg = pPath;
msg += L"\t BEFORE loadFileData";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());

bool loadRes = loadFileData(doc, fileSize, backupFileName ? backupFileName : fullpath, data, &UnicodeConvertor, loadedFileFormat);

msg = pPath;
msg += L"\t AFTER loadFileData";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());

delete[] data;

if (loadRes)
Expand Down Expand Up @@ -843,12 +871,22 @@ bool FileManager::reloadBuffer(BufferID id)
// Set _isLoadedDirty false before calling "_pscratchTilla->execute(SCI_CLEARALL);" in loadFileData() to avoid setDirty in SCN_SAVEPOINTREACHED / SCN_SAVEPOINTLEFT

//Get file size
FILE* fp = _wfopen(buf->getFullPathName(), L"rb");
if (!fp)
int64_t fileSize = 0;
WIN32_FILE_ATTRIBUTE_DATA attributes{};
getFileAttributesExWaitSec(buf->getFullPathName(), &attributes);
if (attributes.dwFileAttributes == INVALID_FILE_ATTRIBUTES)
{
return false;
_fseeki64(fp, 0, SEEK_END);
int64_t fileSize = _ftelli64(fp);
fclose(fp);
}
else
{
LARGE_INTEGER size{};
size.LowPart = attributes.nFileSizeLow;
size.HighPart = attributes.nFileSizeHigh;

fileSize = size.QuadPart;
}


char* data = new char[blockSize + 8]; // +8 for incomplete multibyte char

Expand Down Expand Up @@ -1561,10 +1599,7 @@ LangType FileManager::detectLanguageFromTextBegining(const unsigned char *data,

bool FileManager::loadFileData(Document doc, int64_t fileSize, const wchar_t * filename, char* data, Utf8_16_Read * unicodeConvertor, LoadedFileFormat& fileFormat)
{
FILE *fp = _wfopen(filename, L"rb");
if (!fp)
return false;

// Check file size firstly
// size/6 is the normal room Scintilla keeps for editing, but here we limit it to 1MiB when loading (maybe we want to load big files without editing them too much)
int64_t bufferSizeRequested = fileSize + std::min<int64_t>(1LL << 20, fileSize / 6);

Expand All @@ -1582,7 +1617,6 @@ bool FileManager::loadFileData(Document doc, int64_t fileSize, const wchar_t * f
L"File size problem",
MB_OK | MB_APPLMODAL);

fclose(fp);
return false;
}
else // x64
Expand All @@ -1602,13 +1636,25 @@ bool FileManager::loadFileData(Document doc, int64_t fileSize, const wchar_t * f
}
else
{
fclose(fp);
return false;
}
}
}
}

wstring msg = filename;
msg += L"\t BEFORE _wfopen";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());

FILE* fp = wfopenWaitSec(filename, 5000);

msg = filename;
msg += L"\t AFTER _wfopen";
writeLog(L"c:\\tmp\\QQQ", msg.c_str());

if (!fp)
return false;

//Setup scratchtilla for new filedata
_pscratchTilla->execute(SCI_SETSTATUS, SC_STATUS_OK); // reset error status
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, doc);
Expand Down Expand Up @@ -1641,8 +1687,8 @@ bool FileManager::loadFileData(Document doc, int64_t fileSize, const wchar_t * f
bool success = true;
EolType format = EolType::unknown;
int sciStatus = SC_STATUS_OK;
wchar_t szException[64] = { '\0' };
__try
/*wchar_t szException[64] = {'\0'};
__try*/
{
// First allocate enough memory for the whole file (this will reduce memory copy during loading)
_pscratchTilla->execute(SCI_ALLOCATE, WPARAM(bufferSizeRequested));
Expand Down Expand Up @@ -1730,7 +1776,7 @@ bool FileManager::loadFileData(Document doc, int64_t fileSize, const wchar_t * f
}
while (lenFile > 0);
}
__except(EXCEPTION_EXECUTE_HANDLER)
/*__except (EXCEPTION_EXECUTE_HANDLER)
{
switch (sciStatus)
{
Expand Down Expand Up @@ -1769,8 +1815,8 @@ bool FileManager::loadFileData(Document doc, int64_t fileSize, const wchar_t * f
szException);
}
success = false;
}

}*/
fclose(fp);

// broadcast the format
Expand Down

0 comments on commit 5e24e1d

Please sign in to comment.