Browse Source

Fix network files hanging while the network disconnected (part 2)

Refactoring for reducing the I/O calls, fix typos.

Reduce the startup time (while the a dirty disconnected network file is in the session) from about 12-15 seconds to about 6 seconds (on my laptop).

Note that there are 2 cases are not improved by the commit:

* STR 1: Open a network file, modify it. Disconnect the network, then save the file.

There will be a huge hanging time (around 1 minute) to get the warning dialog.
I tried to remedy with thread for CreateFileW in the constructor of Win32_IO_File, however it leads crash due to the lock guard in the caller.

* STR 2:
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 doesn't see the resource of network anymore - that makes hanging.

Ref #15658
Improve #4306, #6178, #8055, #11388, #12553, #15540
Close #15669
pull/15677/head
Don Ho 2 months ago
parent
commit
a3535f385f
  1. 58
      PowerEditor/src/MISC/Common/Common.cpp
  2. 11
      PowerEditor/src/MISC/Common/Common.h
  3. 14
      PowerEditor/src/MISC/Common/FileInterface.cpp
  4. 16
      PowerEditor/src/Notepad_plus.cpp
  5. 24
      PowerEditor/src/NppIO.cpp
  6. 15
      PowerEditor/src/ScintillaComponent/AutoCompletion.cpp
  7. 31
      PowerEditor/src/ScintillaComponent/Buffer.cpp
  8. 2
      PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp

58
PowerEditor/src/MISC/Common/Common.cpp

@ -1764,9 +1764,12 @@ bool Version::isCompatibleTo(const Version& from, const Version& to) const
return false;
}
#define DEFAULT_MILLISEC 1000
//----------------------------------------------------
struct GetAttrParamResult {
std::wstring _filePath;
wstring _filePath;
DWORD _fileAttr = INVALID_FILE_ATTRIBUTES;
bool _isNetworkFailure = true;
};
@ -1776,15 +1779,12 @@ DWORD WINAPI getFileAttributesWorker(void* data)
GetAttrParamResult* inAndOut = static_cast<GetAttrParamResult*>(data);
inAndOut->_fileAttr = ::GetFileAttributesW(inAndOut->_filePath.c_str());
inAndOut->_isNetworkFailure = false;
return TRUE;
return ERROR_SUCCESS;
};
DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milleSec2wait, bool* isNetWorkProblem)
DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
GetAttrParamResult data;
data._fileAttr = INVALID_FILE_ATTRIBUTES;
data._filePath = filePath;
data._isNetworkFailure = true;
GetAttrParamResult data(filePath);
HANDLE hThread = ::CreateThread(NULL, 0, getFileAttributesWorker, &data, 0, NULL);
if (!hThread)
@ -1793,7 +1793,7 @@ DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milleSec2wait, bool* isN
}
// wait for our worker thread to complete or terminate it when the required timeout has elapsed
DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milleSec2wait == 0 ? 1000 : milleSec2wait);
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
@ -1814,21 +1814,21 @@ DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milleSec2wait, bool* isN
return data._fileAttr;
};
bool doesFileExist(const wchar_t* filePath, DWORD milleSec2wait, bool* isNetWorkProblem)
bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
DWORD attr = getFileAttrWaitSec(filePath, milleSec2wait, isNetWorkProblem);
DWORD attr = getFileAttrWaitSec(filePath, milliSec2wait, isNetWorkProblem);
return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY));
}
bool doesDirectoryExist(const wchar_t* dirPath, DWORD milleSec2wait, bool* isNetWorkProblem)
bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait, bool* isNetWorkProblem)
{
DWORD attr = getFileAttrWaitSec(dirPath, milleSec2wait, isNetWorkProblem);
DWORD attr = getFileAttrWaitSec(dirPath, milliSec2wait, isNetWorkProblem);
return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY));
}
bool doesPathExist(const wchar_t* path, DWORD milleSec2wait, bool* isNetWorkProblem)
bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isNetWorkProblem)
{
DWORD attr = getFileAttrWaitSec(path, milleSec2wait, isNetWorkProblem);
DWORD attr = getFileAttrWaitSec(path, milliSec2wait, isNetWorkProblem);
return (attr != INVALID_FILE_ATTRIBUTES);
}
@ -1840,6 +1840,7 @@ struct GetDiskFreeSpaceParamResult
ULARGE_INTEGER _freeBytesForUser {};
DWORD _result = FALSE;
bool _isNetworkFailure = true;
GetDiskFreeSpaceParamResult(wstring dirPath) : _dirPath(dirPath) {};
};
DWORD WINAPI getDiskFreeSpaceExWorker(void* data)
@ -1847,16 +1848,12 @@ DWORD WINAPI getDiskFreeSpaceExWorker(void* data)
GetDiskFreeSpaceParamResult* inAndOut = static_cast<GetDiskFreeSpaceParamResult*>(data);
inAndOut->_result = ::GetDiskFreeSpaceExW(inAndOut->_dirPath.c_str(), &(inAndOut->_freeBytesForUser), nullptr, nullptr);
inAndOut->_isNetworkFailure = false;
return TRUE;
return ERROR_SUCCESS;
};
DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milleSec2wait, bool* isNetWorkProblem)
DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milliSec2wait, bool* isNetWorkProblem)
{
GetDiskFreeSpaceParamResult data;
data._dirPath = dirPath;
data._freeBytesForUser = {};
data._result = FALSE;
data._isNetworkFailure = true;
GetDiskFreeSpaceParamResult data(dirPath);
HANDLE hThread = ::CreateThread(NULL, 0, getDiskFreeSpaceExWorker, &data, 0, NULL);
if (!hThread)
@ -1865,7 +1862,7 @@ DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesF
}
// wait for our worker thread to complete or terminate it when the required timeout has elapsed
DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milleSec2wait == 0 ? 1000 : milleSec2wait);
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
@ -1893,10 +1890,13 @@ DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesF
struct GetAttrExParamResult
{
std::wstring _filePath;
wstring _filePath;
WIN32_FILE_ATTRIBUTE_DATA _attributes{};
DWORD _result = FALSE;
bool _isNetworkFailure = true;
GetAttrExParamResult(wstring filePath): _filePath(filePath) {
_attributes.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
}
};
DWORD WINAPI getFileAttributesExWorker(void* data)
@ -1904,16 +1904,12 @@ DWORD WINAPI getFileAttributesExWorker(void* data)
GetAttrExParamResult* inAndOut = static_cast<GetAttrExParamResult*>(data);
inAndOut->_result = ::GetFileAttributesEx(inAndOut->_filePath.c_str(), GetFileExInfoStandard, &(inAndOut->_attributes));
inAndOut->_isNetworkFailure = false;
return TRUE;
return ERROR_SUCCESS;
};
DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milleSec2wait, bool* isNetWorkProblem)
DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milliSec2wait, bool* isNetWorkProblem)
{
GetAttrExParamResult data;
data._filePath = filePath;
data._attributes = {};
data._result = FALSE;
data._isNetworkFailure = true;
GetAttrExParamResult data(filePath);
HANDLE hThread = ::CreateThread(NULL, 0, getFileAttributesExWorker, &data, 0, NULL);
if (!hThread)
@ -1922,7 +1918,7 @@ DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_D
}
// wait for our worker thread to complete or terminate it when the required timeout has elapsed
DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milleSec2wait == 0 ? 1000 : milleSec2wait);
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

11
PowerEditor/src/MISC/Common/Common.h

@ -281,9 +281,10 @@ private:
unsigned long _build = 0;
};
bool doesFileExist(const wchar_t* filePath, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr);
bool doesDirectoryExist(const wchar_t* dirPath, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr);
bool doesPathExist(const wchar_t* path, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getFileAttrWaitSec(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 doesPathExist(const wchar_t* path, DWORD milliSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr);
DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milleSec2wait = 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);

14
PowerEditor/src/MISC/Common/FileInterface.cpp

@ -32,12 +32,16 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
WIN32_FILE_ATTRIBUTE_DATA attributes_original{};
DWORD dispParam = CREATE_ALWAYS;
bool fileExists = doesFileExist(fname);
if (fileExists)
bool fileExists = false;
// Store the file creation date & attributes for a possible use later...
if (::GetFileAttributesEx(fname, GetFileExInfoStandard, &attributes_original)) // No thread (GetFileAttributesExWaitSec) to prevent eventual crash
{
// Store the file creation date & attributes for a possible use later...
::GetFileAttributesExW(fname, GetFileExInfoStandard, &attributes_original);
fileExists = (attributes_original.dwFileAttributes != INVALID_FILE_ATTRIBUTES);
}
if (fileExists)
{
// Check the existence of Alternate Data Streams
WIN32_FIND_STREAM_DATA findData;
HANDLE hFind = FindFirstStreamW(fname, FindStreamInfoStandard, &findData, 0);
@ -48,7 +52,7 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
}
}
_hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
_hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL); // No thread (CreateFileWaitSec) due to the lock guard in the caller which leads crash
// Race condition management:
// If file didn't exist while calling PathFileExistsW, but before calling CreateFileW, file is created: use CREATE_ALWAYS is OK

16
PowerEditor/src/Notepad_plus.cpp

@ -7792,11 +7792,11 @@ static const QuoteParams quotes[] =
{L"Mark Zuckerberg", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"\"Black lives matter\" doesn't mean other lives don't - it's simply asking that the black community also achieves the justice they deserve."},
{L"Michael Feldman", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"Java is, in many ways, C++--."},
{L"Don Ho", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"Je mange donc je chie."},
{L"Don Ho #2", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"RTFM is the true path of every developer.\nBut it would happen only if there's no way out."},
{L"Don Ho #3", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"The smartphone is the best invention of the 21st century for avoiding eye contact with people you know while crossing the street."},
{L"Don Ho #4", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"Poor countries' museums vs. rich countries' museums:\nThe first show what they have left.\nThe second show what they have stolen."},
{L"Don Ho #2", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"RTFM is the true path for every developer.\nHowever, it only happens when there's no other way out."},
{L"Don Ho #3", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"The smartphone is the best invention of the 21st century for avoiding eye contact with acquaintances while crossing the street."},
{L"Don Ho #4", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"Museums in poor countries vs. museums in rich countries:\nThe former display what they have left.\nThe latter display what they have taken."},
{L"Don Ho #5", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"With great refactoring comes great regressions."},
{L"Don Ho #6", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"Naming a variable always reminds me the effort I put into my existence,\nfor giving some sense to my meaningless life."},
{L"Don Ho #6", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"Naming a variable always reminds me of the effort I put into my existence,\nfor giving some sense to my meaningless life."},
{L"Anonymous #1", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"An opinion without 3.14 is just an onion."},
{L"Anonymous #2", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"Before sex, you help each other get naked, after sex you only dress yourself.\nMoral of the story: in life no one helps you once you're fucked."},
{L"Anonymous #3", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"I'm not totally useless. I can be used as a bad example."},
@ -8216,7 +8216,7 @@ DWORD WINAPI Notepad_plus::threadTextPlayer(void *params)
BufferID currentBufID = pCurrentView->getCurrentBufferID();
if (currentBufID != targetBufID)
return TRUE;
return ERROR_SUCCESS;
char charToShow[4] = { '\0' };
::WideCharToMultiByte(CP_UTF8, 0, quoter + i, 1, charToShow, sizeof(charToShow), NULL, NULL);
@ -8227,7 +8227,7 @@ DWORD WINAPI Notepad_plus::threadTextPlayer(void *params)
}
}
return TRUE;
return ERROR_SUCCESS;
}
@ -8310,7 +8310,7 @@ DWORD WINAPI Notepad_plus::threadTextTroller(void *params)
}
ReleaseMutex(textTrollerParams->_mutex);
return TRUE;
return ERROR_SUCCESS;
}
@ -8643,7 +8643,7 @@ DWORD WINAPI Notepad_plus::backupDocument(void * /*param*/)
::SendMessage(Notepad_plus_Window::gNppHWND, NPPM_INTERNAL_SAVEBACKUP, 0, 0);
}
return TRUE;
return ERROR_SUCCESS;
}

24
PowerEditor/src/NppIO.cpp

@ -129,7 +129,7 @@ DWORD WINAPI Notepad_plus::monitorFileOnChange(void * params)
dirChanges.Terminate();
fileChanges.Terminate();
delete monitorInfo;
return EXIT_SUCCESS;
return ERROR_SUCCESS;
}
bool resolveLinkFile(std::wstring& linkFilePath)
@ -261,7 +261,7 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is
}
}
bool isSnapshotMode = backupFileName != NULL && doesFileExist(backupFileName);
bool isSnapshotMode = (backupFileName != NULL) && doesFileExist(backupFileName);
bool longFileNameExists = doesFileExist(longFileName);
if (isSnapshotMode && !longFileNameExists) // UNTITLED
{
@ -423,18 +423,14 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is
if (buffer != BUFFER_INVALID)
{
isSnapshotMode = (backupFileName != NULL && doesFileExist(backupFileName));
if (isSnapshotMode)
{
// To notify plugins that a snapshot dirty file is loaded on startup
SCNotification scnN2{};
scnN2.nmhdr.hwndFrom = 0;
scnN2.nmhdr.idFrom = (uptr_t)buffer;
scnN2.nmhdr.code = NPPN_SNAPSHOTDIRTYFILELOADED;
_pluginsManager.notify(&scnN2);
buffer->setLoadedDirty(true);
}
// To notify plugins that a snapshot dirty file is loaded on startup
SCNotification scnN2{};
scnN2.nmhdr.hwndFrom = 0;
scnN2.nmhdr.idFrom = (uptr_t)buffer;
scnN2.nmhdr.code = NPPN_SNAPSHOTDIRTYFILELOADED;
_pluginsManager.notify(&scnN2);
buffer->setLoadedDirty(true);
}
}
else

15
PowerEditor/src/ScintillaComponent/AutoCompletion.cpp

@ -532,17 +532,6 @@ static wstring removeTrailingSlash(const wstring& path)
return path;
}
static bool isDirectory(const wstring& path)
{
DWORD type = ::GetFileAttributes(path.c_str());
return type != INVALID_FILE_ATTRIBUTES && (type & FILE_ATTRIBUTE_DIRECTORY);
}
static bool isFile(const wstring& path)
{
DWORD type = ::GetFileAttributes(path.c_str());
return type != INVALID_FILE_ATTRIBUTES && ! (type & FILE_ATTRIBUTE_DIRECTORY);
}
static bool isAllowedBeforeDriveLetter(wchar_t c)
{
@ -578,11 +567,11 @@ static bool getPathsForPathCompletion(const wstring& input, wstring &rawPath_out
{
return false;
}
else if (isFile(rawPath) || isFile(removeTrailingSlash(rawPath)))
else if (doesFileExist(removeTrailingSlash(rawPath).c_str()))
{
return false;
}
else if (isDirectory(rawPath))
else if (doesDirectoryExist(rawPath.c_str()))
{
rawPath_out = rawPath;
pathToMatch_out = rawPath;

31
PowerEditor/src/ScintillaComponent/Buffer.cpp

@ -709,12 +709,14 @@ BufferID FileManager::loadFile(const wchar_t* filename, Document doc, int encodi
if (pPath)
{
FILE* fp = _wfopen(pPath, L"rb");
if (fp)
WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (getFileAttributesExWaitSec(pPath, &attributes) != FALSE)
{
_fseeki64(fp, 0, SEEK_END);
fileSize = _ftelli64(fp);
fclose(fp);
LARGE_INTEGER size{};
size.LowPart = attributes.nFileSizeLow;
size.HighPart = attributes.nFileSizeHigh;
fileSize = size.QuadPart;
}
}
@ -746,7 +748,7 @@ BufferID FileManager::loadFile(const wchar_t* filename, Document doc, int encodi
}
WCHAR fullpath[MAX_PATH] = { 0 };
if (isWin32NamespacePrefixedFileName(filename))
if (isWin32NamespacePrefixedFileName(filename)) // This function checks for the \\?\ prefix
{
// use directly the raw file name, skip the GetFullPathName WINAPI
wcsncpy_s(fullpath, _countof(fullpath), filename, _TRUNCATE);
@ -760,7 +762,7 @@ BufferID FileManager::loadFile(const wchar_t* filename, Document doc, int encodi
}
}
bool isSnapshotMode = backupFileName != NULL && doesFileExist(backupFileName);
bool isSnapshotMode = (backupFileName != NULL) && doesFileExist(backupFileName);
if (isSnapshotMode && !doesFileExist(fullpath)) // if backup mode and fullpath doesn't exist, we guess is UNTITLED
{
wcscpy_s(fullpath, MAX_PATH, filename); // we restore fullpath with filename, in our case is "new #"
@ -1158,7 +1160,6 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
Buffer* buffer = getBufferByID(id);
bool isHiddenOrSys = false;
DWORD attrib = 0;
WCHAR fullpath[MAX_PATH] = { 0 };
if (isWin32NamespacePrefixedFileName(filename))
@ -1197,16 +1198,12 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
return SavingStatus::NotEnoughRoom;
}
if (doesFileExist(fullpath))
DWORD attrib = getFileAttrWaitSec(fullpath);
if (attrib != INVALID_FILE_ATTRIBUTES)
{
attrib = ::GetFileAttributes(fullpath);
if (attrib != INVALID_FILE_ATTRIBUTES)
{
isHiddenOrSys = (attrib & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0;
if (isHiddenOrSys)
::SetFileAttributes(filename, attrib & ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM));
}
isHiddenOrSys = (attrib & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0;
if (isHiddenOrSys)
::SetFileAttributes(filename, attrib & ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM));
}
UniMode mode = buffer->getUnicodeMode();

2
PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp

@ -1714,7 +1714,7 @@ DWORD WINAPI FolderUpdater::watching(void *params)
// call Terminate() automatically.
changes.Terminate();
//printStr(L"Quit watching thread");
return EXIT_SUCCESS;
return ERROR_SUCCESS;
}
void FolderUpdater::processChange(DWORD dwAction, std::vector<wstring> filesToChange, FolderUpdater* thisFolderUpdater)

Loading…
Cancel
Save