99#include < EventLog.h>
1010
1111FILE_WATCHER::FILE_WATCHER () :
12- m_hCompletionPort(NULL ),
13- m_hChangeNotificationThread(NULL ),
14- m_fThreadExit(FALSE ),
15- m_fShadowCopyEnabled(FALSE ),
12+ m_hCompletionPort(nullptr ),
13+ m_hChangeNotificationThread(nullptr ),
14+ m_fThreadExit(false ),
15+ m_fShadowCopyEnabled(false ),
1616 m_copied(false )
1717{
1818 m_pDoneCopyEvent = CreateEvent (
1919 nullptr , // default security attributes
2020 TRUE , // manual reset event
2121 FALSE , // not set
2222 nullptr ); // name
23+
24+ m_pShutdownEvent = CreateEvent (
25+ nullptr , // default security attributes
26+ TRUE , // manual reset event
27+ FALSE , // not set
28+ nullptr ); // name
29+
30+ // Use of TerminateThread for the file watcher thread was eliminated in favor of an event-based
31+ // approach. Out of an abundance of caution, we are temporarily adding an environment variable
32+ // to allow falling back to TerminateThread usage. If all goes well, this will be removed in a
33+ // future release.
34+ m_fRudeThreadTermination = false ;
35+ auto enableThreadTerminationValue = Environment::GetEnvironmentVariableValue (L" ASPNETCORE_FILE_WATCHER_THREAD_TERMINATION" );
36+ if (enableThreadTerminationValue.has_value ())
37+ {
38+ m_fRudeThreadTermination = (enableThreadTerminationValue.value () == L" 1" );
39+ }
2340}
2441
2542FILE_WATCHER::~FILE_WATCHER ()
2643{
2744 StopMonitor ();
28- WaitForMonitor ( 20 ); // wait for 1 second total
45+ WaitForWatcherThreadExit ();
2946}
3047
31- void FILE_WATCHER::WaitForMonitor (DWORD dwRetryCounter )
48+ void FILE_WATCHER::WaitForWatcherThreadExit ( )
3249{
33- if (m_hChangeNotificationThread != NULL )
50+ if (m_hChangeNotificationThread == nullptr )
3451 {
35- DWORD dwExitCode = STILL_ACTIVE;
52+ return ;
53+ }
3654
37- while (!m_fThreadExit && dwRetryCounter > 0 )
55+ if (m_fRudeThreadTermination)
56+ {
57+ // This is the old behavior, which is now opt-in using an environment variable. Wait for
58+ // the thread to exit, but if it doesn't exit soon enough, terminate it.
59+ const int totalWaitTimeMs = 10000 ;
60+ const int waitIntervalMs = 50 ;
61+ const int iterations = totalWaitTimeMs / waitIntervalMs;
62+ for (int i = 0 ; i < iterations && !m_fThreadExit; i++)
3863 {
39- if (GetExitCodeThread (m_hChangeNotificationThread, &dwExitCode))
40- {
41- if (dwExitCode == STILL_ACTIVE)
42- {
43- // the file watcher thread will set m_fThreadExit before exit
44- WaitForSingleObject (m_hChangeNotificationThread, 50 );
45- }
46- }
47- else
64+ // Check if the thread has exited.
65+ DWORD result = WaitForSingleObject (m_hChangeNotificationThread, waitIntervalMs);
66+ if (result == WAIT_OBJECT_0)
4867 {
49- // fail to get thread status
50- // call terminitethread
51- TerminateThread (m_hChangeNotificationThread, 1 );
52- m_fThreadExit = TRUE ;
68+ // The thread has exited.
69+ m_fThreadExit = true ;
70+ break ;
5371 }
54- dwRetryCounter--;
5572 }
5673
5774 if (!m_fThreadExit)
5875 {
76+ LOG_INFO (L" File watcher thread did not exit. Forcing termination." );
5977 TerminateThread (m_hChangeNotificationThread, 1 );
6078 }
6179 }
80+ else
81+ {
82+ // Wait for the thread to exit.
83+ LOG_INFO (L" Waiting for file watcher thread to exit." );
84+ WaitForSingleObject (m_hChangeNotificationThread, INFINITE);
85+ }
6286}
6387
6488HRESULT
@@ -74,18 +98,18 @@ FILE_WATCHER::Create(
7498 m_fShadowCopyEnabled = !shadowCopyPath.empty ();
7599 m_shutdownTimeout = shutdownTimeout;
76100
77- RETURN_LAST_ERROR_IF_NULL (m_hCompletionPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL , 0 , 0 ));
101+ RETURN_LAST_ERROR_IF_NULL (m_hCompletionPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, nullptr , 0 , 0 ));
78102
79- RETURN_LAST_ERROR_IF_NULL (m_hChangeNotificationThread = CreateThread (NULL ,
103+ RETURN_LAST_ERROR_IF_NULL (m_hChangeNotificationThread = CreateThread (nullptr ,
80104 0 ,
81105 (LPTHREAD_START_ROUTINE)ChangeNotificationThread,
82106 this ,
83107 0 ,
84108 NULL ));
85109
86- if (pszDirectoryToMonitor == NULL ||
87- pszFileNameToMonitor == NULL ||
88- pApplication == NULL )
110+ if (pszDirectoryToMonitor == nullptr ||
111+ pszFileNameToMonitor == nullptr ||
112+ pApplication == nullptr )
89113 {
90114 DBG_ASSERT (FALSE );
91115 return HRESULT_FROM_WIN32 (ERROR_INVALID_PARAMETER);
@@ -107,7 +131,7 @@ FILE_WATCHER::Create(
107131 _strDirectoryName.QueryStr (),
108132 FILE_LIST_DIRECTORY,
109133 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
110- NULL ,
134+ nullptr ,
111135 OPEN_EXISTING,
112136 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
113137 NULL );
@@ -146,36 +170,41 @@ Win32 error
146170
147171--*/
148172{
149- FILE_WATCHER* pFileMonitor;
150- BOOL fSuccess = FALSE ;
151- DWORD cbCompletion = 0 ;
152- OVERLAPPED* pOverlapped = NULL ;
153- DWORD dwErrorStatus;
154- ULONG_PTR completionKey;
155-
173+ FILE_WATCHER* pFileMonitor = (FILE_WATCHER*)pvArg;
174+
156175 LOG_INFO (L" Starting file watcher thread" );
157- pFileMonitor = (FILE_WATCHER*)pvArg;
158- DBG_ASSERT (pFileMonitor != NULL );
176+ DBG_ASSERT (pFileMonitor != nullptr );
177+
178+ HANDLE events[2 ] = { pFileMonitor->m_hCompletionPort , pFileMonitor->m_pShutdownEvent };
159179
160- while (TRUE )
180+ DWORD dwEvent = 0 ;
181+ while (true )
161182 {
162- fSuccess = GetQueuedCompletionStatus (
183+ // Wait for either a change notification or a shutdown event.
184+ dwEvent = WaitForMultipleObjects (ARRAYSIZE (events), events, FALSE , INFINITE) - WAIT_OBJECT_0;
185+
186+ if (dwEvent == 1 )
187+ {
188+ // Shutdown event.
189+ break ;
190+ }
191+
192+ DWORD cbCompletion = 0 ;
193+ OVERLAPPED* pOverlapped = nullptr ;
194+ ULONG_PTR completionKey;
195+
196+ BOOL success = GetQueuedCompletionStatus (
163197 pFileMonitor->m_hCompletionPort ,
164198 &cbCompletion,
165199 &completionKey,
166200 &pOverlapped,
167201 INFINITE);
168202
169- DBG_ASSERT (fSuccess );
170- dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError ();
171-
172- if (completionKey == FILE_WATCHER_SHUTDOWN_KEY)
173- {
174- break ;
175- }
203+ DBG_ASSERT (success);
204+ (void )success;
176205
177- DBG_ASSERT (pOverlapped != NULL );
178- if (pOverlapped != NULL )
206+ DBG_ASSERT (pOverlapped != nullptr );
207+ if (pOverlapped != nullptr )
179208 {
180209 pFileMonitor->HandleChangeCompletion (cbCompletion);
181210
@@ -187,11 +216,9 @@ Win32 error
187216 pFileMonitor->Monitor ();
188217 }
189218 }
190- pOverlapped = NULL ;
191- cbCompletion = 0 ;
192219 }
193220
194- pFileMonitor->m_fThreadExit = TRUE ;
221+ pFileMonitor->m_fThreadExit = true ;
195222
196223 if (pFileMonitor->m_fShadowCopyEnabled )
197224 {
@@ -205,7 +232,6 @@ Win32 error
205232 ExitThread (0 );
206233}
207234
208-
209235HRESULT
210236FILE_WATCHER::HandleChangeCompletion (
211237 _In_ DWORD cbCompletion
@@ -247,7 +273,7 @@ HRESULT
247273 //
248274 // There could be a FCN overflow
249275 // Let assume the file got changed instead of checking files
250- // Othersie we have to cache the file info
276+ // Otherwise we have to cache the file info
251277 //
252278 if (cbCompletion == 0 )
253279 {
@@ -256,9 +282,9 @@ HRESULT
256282 else
257283 {
258284 auto pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr ();
259- DBG_ASSERT (pNotificationInfo != NULL );
285+ DBG_ASSERT (pNotificationInfo != nullptr );
260286
261- while (pNotificationInfo != NULL )
287+ while (pNotificationInfo != nullptr )
262288 {
263289 //
264290 // check whether the monitored file got changed
@@ -276,19 +302,23 @@ HRESULT
276302 //
277303 // Look for changes to dlls when shadow copying is enabled.
278304 //
279- std::wstring notification (pNotificationInfo->FileName , pNotificationInfo->FileNameLength / sizeof (WCHAR));
280- std::filesystem::path notificationPath (notification);
281- if (m_fShadowCopyEnabled && notificationPath.extension ().compare (L" .dll" ) == 0 )
305+
306+ if (m_fShadowCopyEnabled)
282307 {
283- fDllChanged = TRUE ;
308+ std::wstring notification (pNotificationInfo->FileName , pNotificationInfo->FileNameLength / sizeof (WCHAR));
309+ std::filesystem::path notificationPath (notification);
310+ if (notificationPath.extension ().compare (L" .dll" ) == 0 )
311+ {
312+ fDllChanged = TRUE ;
313+ }
284314 }
285315
286316 //
287317 // Advance to next notification
288318 //
289319 if (pNotificationInfo->NextEntryOffset == 0 )
290320 {
291- pNotificationInfo = NULL ;
321+ pNotificationInfo = nullptr ;
292322 }
293323 else
294324 {
@@ -326,8 +356,8 @@ FILE_WATCHER::TimerCallback(
326356 _In_ PTP_TIMER Timer
327357)
328358{
329- Instance;
330- Timer;
359+ UNREFERENCED_PARAMETER ( Instance) ;
360+ UNREFERENCED_PARAMETER ( Timer) ;
331361 CopyAndShutdown ((FILE_WATCHER*)Context);
332362}
333363
@@ -342,7 +372,7 @@ DWORD WINAPI FILE_WATCHER::CopyAndShutdown(FILE_WATCHER* watcher)
342372
343373 watcher->m_copied = true ;
344374
345- LOG_INFO (L" Starting copy on shutdown in filewatcher , creating directory." );
375+ LOG_INFO (L" Starting copy on shutdown in file watcher , creating directory." );
346376
347377 auto directoryNameInt = 0 ;
348378 auto currentShadowCopyDirectory = std::filesystem::path (watcher->m_shadowCopyPath );
@@ -402,9 +432,7 @@ FILE_WATCHER::RunNotificationCallback(
402432HRESULT
403433FILE_WATCHER::Monitor (VOID)
404434{
405- HRESULT hr = S_OK;
406435 DWORD cbRead;
407-
408436 ZeroMemory (&_overlapped, sizeof (_overlapped));
409437
410438 RETURN_LAST_ERROR_IF (!ReadDirectoryChangesW (_hDirectory,
@@ -414,15 +442,15 @@ FILE_WATCHER::Monitor(VOID)
414442 FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS,
415443 &cbRead,
416444 &_overlapped,
417- NULL ));
445+ nullptr ));
418446
419447 // Check if file exist because ReadDirectoryChangesW would not fire events for existing files
420448 if (GetFileAttributes (_strFullName.QueryStr ()) != INVALID_FILE_ATTRIBUTES)
421449 {
422450 PostQueuedCompletionStatus (m_hCompletionPort, 0 , 0 , &_overlapped);
423451 }
424452
425- return hr ;
453+ return S_OK ;
426454}
427455
428456VOID
@@ -440,9 +468,9 @@ FILE_WATCHER::StopMonitor()
440468
441469 LOG_INFO (L" Stopping file watching." );
442470
443- // signal the file watch thread to exit
444- PostQueuedCompletionStatus (m_hCompletionPort, 0 , FILE_WATCHER_SHUTDOWN_KEY, NULL );
445- WaitForMonitor ( 200 );
471+ // Signal the file watcher thread to exit
472+ SetEvent (m_pShutdownEvent );
473+ WaitForWatcherThreadExit ( );
446474
447475 if (m_fShadowCopyEnabled)
448476 {
0 commit comments