9
9
#include < EventLog.h>
10
10
11
11
FILE_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 ),
16
16
m_copied(false )
17
17
{
18
18
m_pDoneCopyEvent = CreateEvent (
19
19
nullptr , // default security attributes
20
20
TRUE , // manual reset event
21
21
FALSE , // not set
22
22
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
+ }
23
40
}
24
41
25
42
FILE_WATCHER::~FILE_WATCHER ()
26
43
{
27
44
StopMonitor ();
28
- WaitForMonitor ( 20 ); // wait for 1 second total
45
+ WaitForWatcherThreadExit ();
29
46
}
30
47
31
- void FILE_WATCHER::WaitForMonitor (DWORD dwRetryCounter )
48
+ void FILE_WATCHER::WaitForWatcherThreadExit ( )
32
49
{
33
- if (m_hChangeNotificationThread != NULL )
50
+ if (m_hChangeNotificationThread == nullptr )
34
51
{
35
- DWORD dwExitCode = STILL_ACTIVE;
52
+ return ;
53
+ }
36
54
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++)
38
63
{
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)
48
67
{
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 ;
53
71
}
54
- dwRetryCounter--;
55
72
}
56
73
57
74
if (!m_fThreadExit)
58
75
{
76
+ LOG_INFO (L" File watcher thread did not exit. Forcing termination." );
59
77
TerminateThread (m_hChangeNotificationThread, 1 );
60
78
}
61
79
}
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
+ }
62
86
}
63
87
64
88
HRESULT
@@ -74,18 +98,18 @@ FILE_WATCHER::Create(
74
98
m_fShadowCopyEnabled = !shadowCopyPath.empty ();
75
99
m_shutdownTimeout = shutdownTimeout;
76
100
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 ));
78
102
79
- RETURN_LAST_ERROR_IF_NULL (m_hChangeNotificationThread = CreateThread (NULL ,
103
+ RETURN_LAST_ERROR_IF_NULL (m_hChangeNotificationThread = CreateThread (nullptr ,
80
104
0 ,
81
105
(LPTHREAD_START_ROUTINE)ChangeNotificationThread,
82
106
this ,
83
107
0 ,
84
108
NULL ));
85
109
86
- if (pszDirectoryToMonitor == NULL ||
87
- pszFileNameToMonitor == NULL ||
88
- pApplication == NULL )
110
+ if (pszDirectoryToMonitor == nullptr ||
111
+ pszFileNameToMonitor == nullptr ||
112
+ pApplication == nullptr )
89
113
{
90
114
DBG_ASSERT (FALSE );
91
115
return HRESULT_FROM_WIN32 (ERROR_INVALID_PARAMETER);
@@ -107,7 +131,7 @@ FILE_WATCHER::Create(
107
131
_strDirectoryName.QueryStr (),
108
132
FILE_LIST_DIRECTORY,
109
133
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
110
- NULL ,
134
+ nullptr ,
111
135
OPEN_EXISTING,
112
136
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
113
137
NULL );
@@ -146,36 +170,41 @@ Win32 error
146
170
147
171
--*/
148
172
{
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
+
156
175
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 };
159
179
160
- while (TRUE )
180
+ DWORD dwEvent = 0 ;
181
+ while (true )
161
182
{
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 (
163
197
pFileMonitor->m_hCompletionPort ,
164
198
&cbCompletion,
165
199
&completionKey,
166
200
&pOverlapped,
167
201
INFINITE);
168
202
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;
176
205
177
- DBG_ASSERT (pOverlapped != NULL );
178
- if (pOverlapped != NULL )
206
+ DBG_ASSERT (pOverlapped != nullptr );
207
+ if (pOverlapped != nullptr )
179
208
{
180
209
pFileMonitor->HandleChangeCompletion (cbCompletion);
181
210
@@ -187,11 +216,9 @@ Win32 error
187
216
pFileMonitor->Monitor ();
188
217
}
189
218
}
190
- pOverlapped = NULL ;
191
- cbCompletion = 0 ;
192
219
}
193
220
194
- pFileMonitor->m_fThreadExit = TRUE ;
221
+ pFileMonitor->m_fThreadExit = true ;
195
222
196
223
if (pFileMonitor->m_fShadowCopyEnabled )
197
224
{
@@ -205,7 +232,6 @@ Win32 error
205
232
ExitThread (0 );
206
233
}
207
234
208
-
209
235
HRESULT
210
236
FILE_WATCHER::HandleChangeCompletion (
211
237
_In_ DWORD cbCompletion
@@ -247,7 +273,7 @@ HRESULT
247
273
//
248
274
// There could be a FCN overflow
249
275
// 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
251
277
//
252
278
if (cbCompletion == 0 )
253
279
{
@@ -256,9 +282,9 @@ HRESULT
256
282
else
257
283
{
258
284
auto pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr ();
259
- DBG_ASSERT (pNotificationInfo != NULL );
285
+ DBG_ASSERT (pNotificationInfo != nullptr );
260
286
261
- while (pNotificationInfo != NULL )
287
+ while (pNotificationInfo != nullptr )
262
288
{
263
289
//
264
290
// check whether the monitored file got changed
@@ -276,19 +302,23 @@ HRESULT
276
302
//
277
303
// Look for changes to dlls when shadow copying is enabled.
278
304
//
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)
282
307
{
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
+ }
284
314
}
285
315
286
316
//
287
317
// Advance to next notification
288
318
//
289
319
if (pNotificationInfo->NextEntryOffset == 0 )
290
320
{
291
- pNotificationInfo = NULL ;
321
+ pNotificationInfo = nullptr ;
292
322
}
293
323
else
294
324
{
@@ -326,8 +356,8 @@ FILE_WATCHER::TimerCallback(
326
356
_In_ PTP_TIMER Timer
327
357
)
328
358
{
329
- Instance;
330
- Timer;
359
+ UNREFERENCED_PARAMETER ( Instance) ;
360
+ UNREFERENCED_PARAMETER ( Timer) ;
331
361
CopyAndShutdown ((FILE_WATCHER*)Context);
332
362
}
333
363
@@ -342,7 +372,7 @@ DWORD WINAPI FILE_WATCHER::CopyAndShutdown(FILE_WATCHER* watcher)
342
372
343
373
watcher->m_copied = true ;
344
374
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." );
346
376
347
377
auto directoryNameInt = 0 ;
348
378
auto currentShadowCopyDirectory = std::filesystem::path (watcher->m_shadowCopyPath );
@@ -402,9 +432,7 @@ FILE_WATCHER::RunNotificationCallback(
402
432
HRESULT
403
433
FILE_WATCHER::Monitor (VOID)
404
434
{
405
- HRESULT hr = S_OK;
406
435
DWORD cbRead;
407
-
408
436
ZeroMemory (&_overlapped, sizeof (_overlapped));
409
437
410
438
RETURN_LAST_ERROR_IF (!ReadDirectoryChangesW (_hDirectory,
@@ -414,15 +442,15 @@ FILE_WATCHER::Monitor(VOID)
414
442
FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS,
415
443
&cbRead,
416
444
&_overlapped,
417
- NULL ));
445
+ nullptr ));
418
446
419
447
// Check if file exist because ReadDirectoryChangesW would not fire events for existing files
420
448
if (GetFileAttributes (_strFullName.QueryStr ()) != INVALID_FILE_ATTRIBUTES)
421
449
{
422
450
PostQueuedCompletionStatus (m_hCompletionPort, 0 , 0 , &_overlapped);
423
451
}
424
452
425
- return hr ;
453
+ return S_OK ;
426
454
}
427
455
428
456
VOID
@@ -440,9 +468,9 @@ FILE_WATCHER::StopMonitor()
440
468
441
469
LOG_INFO (L" Stopping file watching." );
442
470
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 ( );
446
474
447
475
if (m_fShadowCopyEnabled)
448
476
{
0 commit comments