From 8ec5807ef194b3f0599ab0176fed01fbfd188be2 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Thu, 27 Jul 2023 19:53:26 -0700 Subject: [PATCH 1/7] File watcher thread management improvements. --- .../RequestHandlerLib/filewatcher.cpp | 113 +++++++++--------- .../RequestHandlerLib/filewatcher.h | 5 +- 2 files changed, 61 insertions(+), 57 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 6c48750d181c..eba398c664fd 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -11,7 +11,7 @@ FILE_WATCHER::FILE_WATCHER() : m_hCompletionPort(NULL), m_hChangeNotificationThread(NULL), - m_fThreadExit(FALSE), + m_fThreadExit(false), m_fShadowCopyEnabled(FALSE), m_copied(false) { @@ -20,6 +20,12 @@ FILE_WATCHER::FILE_WATCHER() : TRUE, // manual reset event FALSE, // not set nullptr); // name + + m_pShutdownEvent = CreateEvent( + nullptr, // default security attributes + TRUE, // manual reset event + FALSE, // not set + nullptr); // name } FILE_WATCHER::~FILE_WATCHER() @@ -30,33 +36,24 @@ FILE_WATCHER::~FILE_WATCHER() void FILE_WATCHER::WaitForMonitor(DWORD dwRetryCounter) { - if (m_hChangeNotificationThread != NULL) + if (m_hChangeNotificationThread == nullptr) { - DWORD dwExitCode = STILL_ACTIVE; + return; + } - while (!m_fThreadExit && dwRetryCounter > 0) + while (!m_fThreadExit && dwRetryCounter-- > 0) + { + // Check if the thread has exited. + DWORD result = WaitForSingleObject(m_hChangeNotificationThread, 0); + if (result == WAIT_OBJECT_0) { - if (GetExitCodeThread(m_hChangeNotificationThread, &dwExitCode)) - { - if (dwExitCode == STILL_ACTIVE) - { - // the file watcher thread will set m_fThreadExit before exit - WaitForSingleObject(m_hChangeNotificationThread, 50); - } - } - else - { - // fail to get thread status - // call terminitethread - TerminateThread(m_hChangeNotificationThread, 1); - m_fThreadExit = TRUE; - } - dwRetryCounter--; + // The thread has exited. + m_fThreadExit = true; } - - if (!m_fThreadExit) + else { - TerminateThread(m_hChangeNotificationThread, 1); + // The thread is still alive. Wait for 50ms. + WaitForSingleObject(m_hChangeNotificationThread, 50); } } } @@ -146,36 +143,41 @@ Win32 error --*/ { - FILE_WATCHER* pFileMonitor; - BOOL fSuccess = FALSE; - DWORD cbCompletion = 0; - OVERLAPPED* pOverlapped = NULL; - DWORD dwErrorStatus; - ULONG_PTR completionKey; - + FILE_WATCHER* pFileMonitor = (FILE_WATCHER*)pvArg; + LOG_INFO(L"Starting file watcher thread"); - pFileMonitor = (FILE_WATCHER*)pvArg; - DBG_ASSERT(pFileMonitor != NULL); + DBG_ASSERT(pFileMonitor != nullptr); + + HANDLE events[2] = { pFileMonitor->m_hCompletionPort, pFileMonitor->m_pShutdownEvent }; - while (TRUE) + DWORD dwEvent = 0; + while (true) { - fSuccess = GetQueuedCompletionStatus( + // Wait for either a change notification or a shutdown event. + dwEvent = WaitForMultipleObjects(2, events, FALSE, INFINITE) - WAIT_OBJECT_0; + + if (dwEvent == 1) + { + // Shutdown event. + break; + } + + DWORD cbCompletion = 0; + OVERLAPPED* pOverlapped = nullptr; + ULONG_PTR completionKey; + + BOOL success = GetQueuedCompletionStatus( pFileMonitor->m_hCompletionPort, &cbCompletion, &completionKey, &pOverlapped, INFINITE); - DBG_ASSERT(fSuccess); - dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); + DBG_ASSERT(success); + (void)success; - if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) - { - break; - } - - DBG_ASSERT(pOverlapped != NULL); - if (pOverlapped != NULL) + DBG_ASSERT(pOverlapped != nullptr); + if (pOverlapped != nullptr) { pFileMonitor->HandleChangeCompletion(cbCompletion); @@ -187,11 +189,9 @@ Win32 error pFileMonitor->Monitor(); } } - pOverlapped = NULL; - cbCompletion = 0; } - pFileMonitor->m_fThreadExit = TRUE; + pFileMonitor->m_fThreadExit = true; if (pFileMonitor->m_fShadowCopyEnabled) { @@ -205,7 +205,6 @@ Win32 error ExitThread(0); } - HRESULT FILE_WATCHER::HandleChangeCompletion( _In_ DWORD cbCompletion @@ -276,11 +275,15 @@ HRESULT // // Look for changes to dlls when shadow copying is enabled. // - std::wstring notification(pNotificationInfo->FileName, pNotificationInfo->FileNameLength / sizeof(WCHAR)); - std::filesystem::path notificationPath(notification); - if (m_fShadowCopyEnabled && notificationPath.extension().compare(L".dll") == 0) + + if (m_fShadowCopyEnabled) { - fDllChanged = TRUE; + std::wstring notification(pNotificationInfo->FileName, pNotificationInfo->FileNameLength / sizeof(WCHAR)); + std::filesystem::path notificationPath(notification); + if (notificationPath.extension().compare(L".dll") == 0) + { + fDllChanged = TRUE; + } } // @@ -326,8 +329,8 @@ FILE_WATCHER::TimerCallback( _In_ PTP_TIMER Timer ) { - Instance; - Timer; + UNREFERENCED_PARAMETER(Instance); + UNREFERENCED_PARAMETER(Timer); CopyAndShutdown((FILE_WATCHER*)Context); } @@ -342,7 +345,7 @@ DWORD WINAPI FILE_WATCHER::CopyAndShutdown(FILE_WATCHER* watcher) watcher->m_copied = true; - LOG_INFO(L"Starting copy on shutdown in filewatcher, creating directory."); + LOG_INFO(L"Starting copy on shutdown in file watcher, creating directory."); auto directoryNameInt = 0; auto currentShadowCopyDirectory = std::filesystem::path(watcher->m_shadowCopyPath); @@ -441,7 +444,7 @@ FILE_WATCHER::StopMonitor() LOG_INFO(L"Stopping file watching."); // signal the file watch thread to exit - PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); + SetEvent(m_pShutdownEvent); WaitForMonitor(200); if (m_fShadowCopyEnabled) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index 470edbb5eb21..cfd7b4bbd772 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -59,8 +59,9 @@ class FILE_WATCHER{ HandleWrapper m_hCompletionPort; HandleWrapper m_hChangeNotificationThread; HandleWrapper _hDirectory; - HandleWrapper m_pDoneCopyEvent; - volatile BOOL m_fThreadExit; + HandleWrapper m_pDoneCopyEvent; + HandleWrapper m_pShutdownEvent; + std::atomic m_fThreadExit; STTIMER m_Timer; SRWLOCK m_copyLock{}; BOOL m_copied; From 7d1cf689a46ed6b996d58378c0628d73dddcd79a Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Thu, 27 Jul 2023 20:38:02 -0700 Subject: [PATCH 2/7] wip --- .../RequestHandlerLib/filewatcher.cpp | 35 +++++++++---------- .../RequestHandlerLib/filewatcher.h | 2 +- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index eba398c664fd..59cc2161a223 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -9,10 +9,10 @@ #include FILE_WATCHER::FILE_WATCHER() : - m_hCompletionPort(NULL), - m_hChangeNotificationThread(NULL), + m_hCompletionPort(nullptr), + m_hChangeNotificationThread(nullptr), m_fThreadExit(false), - m_fShadowCopyEnabled(FALSE), + m_fShadowCopyEnabled(false), m_copied(false) { m_pDoneCopyEvent = CreateEvent( @@ -49,6 +49,7 @@ void FILE_WATCHER::WaitForMonitor(DWORD dwRetryCounter) { // The thread has exited. m_fThreadExit = true; + break; } else { @@ -71,18 +72,18 @@ FILE_WATCHER::Create( m_fShadowCopyEnabled = !shadowCopyPath.empty(); m_shutdownTimeout = shutdownTimeout; - RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)); + RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0)); - RETURN_LAST_ERROR_IF_NULL(m_hChangeNotificationThread = CreateThread(NULL, + RETURN_LAST_ERROR_IF_NULL(m_hChangeNotificationThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)ChangeNotificationThread, this, 0, NULL)); - if (pszDirectoryToMonitor == NULL || - pszFileNameToMonitor == NULL || - pApplication == NULL) + if (pszDirectoryToMonitor == nullptr || + pszFileNameToMonitor == nullptr || + pApplication == nullptr) { DBG_ASSERT(FALSE); return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); @@ -104,7 +105,7 @@ FILE_WATCHER::Create( _strDirectoryName.QueryStr(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); @@ -154,7 +155,7 @@ Win32 error while (true) { // Wait for either a change notification or a shutdown event. - dwEvent = WaitForMultipleObjects(2, events, FALSE, INFINITE) - WAIT_OBJECT_0; + dwEvent = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, INFINITE) - WAIT_OBJECT_0; if (dwEvent == 1) { @@ -255,9 +256,9 @@ HRESULT else { auto pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); - DBG_ASSERT(pNotificationInfo != NULL); + DBG_ASSERT(pNotificationInfo != nullptr); - while (pNotificationInfo != NULL) + while (pNotificationInfo != nullptr) { // // check whether the monitored file got changed @@ -291,7 +292,7 @@ HRESULT // if (pNotificationInfo->NextEntryOffset == 0) { - pNotificationInfo = NULL; + pNotificationInfo = nullptr; } else { @@ -405,9 +406,7 @@ FILE_WATCHER::RunNotificationCallback( HRESULT FILE_WATCHER::Monitor(VOID) { - HRESULT hr = S_OK; DWORD cbRead; - ZeroMemory(&_overlapped, sizeof(_overlapped)); RETURN_LAST_ERROR_IF(!ReadDirectoryChangesW(_hDirectory, @@ -417,7 +416,7 @@ FILE_WATCHER::Monitor(VOID) FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, &cbRead, &_overlapped, - NULL)); + nullptr)); // Check if file exist because ReadDirectoryChangesW would not fire events for existing files if (GetFileAttributes(_strFullName.QueryStr()) != INVALID_FILE_ATTRIBUTES) @@ -425,7 +424,7 @@ FILE_WATCHER::Monitor(VOID) PostQueuedCompletionStatus(m_hCompletionPort, 0, 0, &_overlapped); } - return hr; + return S_OK; } VOID @@ -443,7 +442,7 @@ FILE_WATCHER::StopMonitor() LOG_INFO(L"Stopping file watching."); - // signal the file watch thread to exit + // Signal the file watcher thread to exit SetEvent(m_pShutdownEvent); WaitForMonitor(200); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index cfd7b4bbd772..c7d992f99e33 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -61,7 +61,7 @@ class FILE_WATCHER{ HandleWrapper _hDirectory; HandleWrapper m_pDoneCopyEvent; HandleWrapper m_pShutdownEvent; - std::atomic m_fThreadExit; + std::atomic_bool m_fThreadExit; STTIMER m_Timer; SRWLOCK m_copyLock{}; BOOL m_copied; From c7f0f65964efaf9b72f2eaf7fc827a0151cbf3d1 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Fri, 28 Jul 2023 16:41:09 -0700 Subject: [PATCH 3/7] Add logging for when WaitForMonitor doesn't see thread exit. --- .../AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 59cc2161a223..9b26d4c154d7 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -57,6 +57,11 @@ void FILE_WATCHER::WaitForMonitor(DWORD dwRetryCounter) WaitForSingleObject(m_hChangeNotificationThread, 50); } } + + if (!m_fThreadExit) + { + LOG_INFO(L"File watcher thread didn't seem to exit."); + } } HRESULT @@ -247,7 +252,7 @@ HRESULT // // There could be a FCN overflow // Let assume the file got changed instead of checking files - // Othersie we have to cache the file info + // Otherwise we have to cache the file info // if (cbCompletion == 0) { From 2dfd1c1b63d6607c8cf14bb28111f81a3dd552a4 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Fri, 28 Jul 2023 17:06:26 -0700 Subject: [PATCH 4/7] Add env var to fall back to termination. --- .../RequestHandlerLib/filewatcher.cpp | 17 +++++++++++++++++ .../RequestHandlerLib/filewatcher.h | 1 + 2 files changed, 18 insertions(+) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 9b26d4c154d7..a1d94e0d2374 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -26,6 +26,17 @@ FILE_WATCHER::FILE_WATCHER() : TRUE, // manual reset event FALSE, // not set nullptr); // name + + // Use of TerminateThread for the file watcher thread was eliminated in favor of an event-based + // approach. Out of an abundance of caution, we are temporarily adding an environment variable + // to allow falling back to TerminateThread usage. If all goes well, this will be removed in a + // future release. + m_fRudeThreadTermination = false; + auto enableThreadTerminationValue = Environment::GetEnvironmentVariableValue(L"ASPNETCORE_FILE_WATCHER_THREAD_TERMINATION"); + if (enableThreadTerminationValue.has_value()) + { + m_fRudeThreadTermination = (enableThreadTerminationValue.value() == L"1"); + } } FILE_WATCHER::~FILE_WATCHER() @@ -61,6 +72,12 @@ void FILE_WATCHER::WaitForMonitor(DWORD dwRetryCounter) if (!m_fThreadExit) { LOG_INFO(L"File watcher thread didn't seem to exit."); + + if (m_fRudeThreadTermination) + { + LOG_INFO(L"File watcher thread was terminated."); + TerminateThread(m_hChangeNotificationThread, 1); + } } } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index c7d992f99e33..185dfc33f7b0 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -76,4 +76,5 @@ class FILE_WATCHER{ DWORD m_shutdownTimeout; OVERLAPPED _overlapped; std::unique_ptr _pApplication; + bool m_fRudeThreadTermination; }; From 384559f18237f53049ffe1261959c333cc968303 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Tue, 1 Aug 2023 13:50:26 -0700 Subject: [PATCH 5/7] Wait for watcher thread indefinitely if not using rude termination. --- .../RequestHandlerLib/filewatcher.cpp | 47 ++++++++++--------- .../RequestHandlerLib/filewatcher.h | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index a1d94e0d2374..b9359ed5d0eb 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -42,43 +42,46 @@ FILE_WATCHER::FILE_WATCHER() : FILE_WATCHER::~FILE_WATCHER() { StopMonitor(); - WaitForMonitor(20); // wait for 1 second total + WaitForWatcherThreadExit(); } -void FILE_WATCHER::WaitForMonitor(DWORD dwRetryCounter) +void FILE_WATCHER::WaitForWatcherThreadExit() { if (m_hChangeNotificationThread == nullptr) { return; } - while (!m_fThreadExit && dwRetryCounter-- > 0) + if (m_fRudeThreadTermination) { - // Check if the thread has exited. - DWORD result = WaitForSingleObject(m_hChangeNotificationThread, 0); - if (result == WAIT_OBJECT_0) + // This is the old behavior, which is now opt-in using an environment variable. Wait for + // the thread to exit, but if it doesn't exit soon enough, terminate it. + const int totalWaitTimeMs = 10000; + const int waitIntervalMs = 50; + const int iterations = totalWaitTimeMs / waitIntervalMs; + for (int i = 0; i < iterations && !m_fThreadExit; i++) { - // The thread has exited. - m_fThreadExit = true; - break; - } - else - { - // The thread is still alive. Wait for 50ms. - WaitForSingleObject(m_hChangeNotificationThread, 50); + // Check if the thread has exited. + DWORD result = WaitForSingleObject(m_hChangeNotificationThread, waitIntervalMs); + if (result == WAIT_OBJECT_0) + { + // The thread has exited. + m_fThreadExit = true; + break; + } } - } - - if (!m_fThreadExit) - { - LOG_INFO(L"File watcher thread didn't seem to exit."); - if (m_fRudeThreadTermination) + if (!m_fThreadExit) { - LOG_INFO(L"File watcher thread was terminated."); + LOG_INFO(L"File watcher thread did not exit. Forcing termination."); TerminateThread(m_hChangeNotificationThread, 1); } } + else + { + // Wait for the thread to exit. + WaitForSingleObject(m_hChangeNotificationThread, INFINITE); + } } HRESULT @@ -466,7 +469,7 @@ FILE_WATCHER::StopMonitor() // Signal the file watcher thread to exit SetEvent(m_pShutdownEvent); - WaitForMonitor(200); + WaitForWatcherThreadExit(10000); if (m_fShadowCopyEnabled) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index 185dfc33f7b0..dffa48f71021 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -24,7 +24,7 @@ class FILE_WATCHER{ ~FILE_WATCHER(); - void WaitForMonitor(DWORD dwRetryCounter); + void WaitForWatcherThreadExit(); HRESULT Create( _In_ PCWSTR pszDirectoryToMonitor, From 4dd8091e66d8773ef3622025d5861f67dd664700 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Tue, 1 Aug 2023 13:54:06 -0700 Subject: [PATCH 6/7] Wish our local build wasn't broken. --- .../IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index b9359ed5d0eb..eb464e3bf01c 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -469,7 +469,7 @@ FILE_WATCHER::StopMonitor() // Signal the file watcher thread to exit SetEvent(m_pShutdownEvent); - WaitForWatcherThreadExit(10000); + WaitForWatcherThreadExit(); if (m_fShadowCopyEnabled) { From 6382c2b430b5fd32b6b7b5b6ccf993c2adecd9f3 Mon Sep 17 00:00:00 2001 From: Aditya Mandaleeka Date: Thu, 3 Aug 2023 14:54:33 -0700 Subject: [PATCH 7/7] Add log message while waiting. --- .../IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index eb464e3bf01c..f6fc3ab67b6e 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -80,6 +80,7 @@ void FILE_WATCHER::WaitForWatcherThreadExit() else { // Wait for the thread to exit. + LOG_INFO(L"Waiting for file watcher thread to exit."); WaitForSingleObject(m_hChangeNotificationThread, INFINITE); } }