Skip to content

Commit 125033f

Browse files
Improve ANCM file watcher thread management (#49696)
1 parent 5cb9dc1 commit 125033f

File tree

2 files changed

+104
-74
lines changed

2 files changed

+104
-74
lines changed

src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp

Lines changed: 99 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,80 @@
99
#include <EventLog.h>
1010

1111
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),
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

2542
FILE_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

6488
HRESULT
@@ -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-
209235
HRESULT
210236
FILE_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(
402432
HRESULT
403433
FILE_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

428456
VOID
@@ -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
{

src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class FILE_WATCHER{
2424

2525
~FILE_WATCHER();
2626

27-
void WaitForMonitor(DWORD dwRetryCounter);
27+
void WaitForWatcherThreadExit();
2828

2929
HRESULT Create(
3030
_In_ PCWSTR pszDirectoryToMonitor,
@@ -59,8 +59,9 @@ class FILE_WATCHER{
5959
HandleWrapper<NullHandleTraits> m_hCompletionPort;
6060
HandleWrapper<NullHandleTraits> m_hChangeNotificationThread;
6161
HandleWrapper<NullHandleTraits> _hDirectory;
62-
HandleWrapper<NullHandleTraits> m_pDoneCopyEvent;
63-
volatile BOOL m_fThreadExit;
62+
HandleWrapper<NullHandleTraits> m_pDoneCopyEvent;
63+
HandleWrapper<NullHandleTraits> m_pShutdownEvent;
64+
std::atomic_bool m_fThreadExit;
6465
STTIMER m_Timer;
6566
SRWLOCK m_copyLock{};
6667
BOOL m_copied;
@@ -75,4 +76,5 @@ class FILE_WATCHER{
7576
DWORD m_shutdownTimeout;
7677
OVERLAPPED _overlapped;
7778
std::unique_ptr<AppOfflineTrackingApplication, IAPPLICATION_DELETER> _pApplication;
79+
bool m_fRudeThreadTermination;
7880
};

0 commit comments

Comments
 (0)