aspnetcore/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp

328 lines
8.3 KiB
C++

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "stdafx.h"
#include "filewatcher.h"
#include "debugutil.h"
#include "AppOfflineTrackingApplication.h"
#include "exceptions.h"
FILE_WATCHER::FILE_WATCHER() :
m_hCompletionPort(NULL),
m_hChangeNotificationThread(NULL),
m_fThreadExit(FALSE)
{
}
FILE_WATCHER::~FILE_WATCHER()
{
StopMonitor();
if (m_hChangeNotificationThread != NULL)
{
DWORD dwRetryCounter = 20; // totally wait for 1s
DWORD dwExitCode = STILL_ACTIVE;
while (!m_fThreadExit && dwRetryCounter > 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--;
}
if (!m_fThreadExit)
{
TerminateThread(m_hChangeNotificationThread, 1);
}
}
}
HRESULT
FILE_WATCHER::Create(
_In_ PCWSTR pszDirectoryToMonitor,
_In_ PCWSTR pszFileNameToMonitor,
_In_ AppOfflineTrackingApplication *pApplication
)
{
RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0));
RETURN_LAST_ERROR_IF_NULL(m_hChangeNotificationThread = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ChangeNotificationThread,
this,
0,
NULL));
if (pszDirectoryToMonitor == NULL ||
pszFileNameToMonitor == NULL ||
pApplication == NULL)
{
DBG_ASSERT(FALSE);
return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
}
_pApplication = ReferenceApplication(pApplication);
RETURN_IF_FAILED(_strFileName.Copy(pszFileNameToMonitor));
RETURN_IF_FAILED(_strDirectoryName.Copy(pszDirectoryToMonitor));
RETURN_IF_FAILED(_strFullName.Append(_strDirectoryName));
RETURN_IF_FAILED(_strFullName.Append(_strFileName));
//
// Resize change buffer to something "reasonable"
//
RETURN_LAST_ERROR_IF(!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE));
_hDirectory = CreateFileW(
_strDirectoryName.QueryStr(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
RETURN_LAST_ERROR_IF_NULL(_hDirectory);
RETURN_LAST_ERROR_IF_NULL(CreateIoCompletionPort(
_hDirectory,
m_hCompletionPort,
NULL,
0));
RETURN_IF_FAILED(Monitor());
return S_OK;
}
DWORD
WINAPI
FILE_WATCHER::ChangeNotificationThread(
LPVOID pvArg
)
/*++
Routine Description:
IO completion thread
Arguments:
None
Return Value:
Win32 error
--*/
{
FILE_WATCHER * pFileMonitor;
BOOL fSuccess = FALSE;
DWORD cbCompletion = 0;
OVERLAPPED * pOverlapped = NULL;
DWORD dwErrorStatus;
ULONG_PTR completionKey;
LOG_INFO("Starting file watcher thread");
pFileMonitor = (FILE_WATCHER*)pvArg;
DBG_ASSERT(pFileMonitor != NULL);
while (TRUE)
{
fSuccess = GetQueuedCompletionStatus(
pFileMonitor->m_hCompletionPort,
&cbCompletion,
&completionKey,
&pOverlapped,
INFINITE);
DBG_ASSERT(fSuccess);
dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError();
if (completionKey == FILE_WATCHER_SHUTDOWN_KEY)
{
break;
}
DBG_ASSERT(pOverlapped != NULL);
if (pOverlapped != NULL)
{
pFileMonitor->HandleChangeCompletion(cbCompletion);
if (!pFileMonitor->_lStopMonitorCalled)
{
//
// Continue monitoring
//
pFileMonitor->Monitor();
}
}
pOverlapped = NULL;
cbCompletion = 0;
}
pFileMonitor->m_fThreadExit = TRUE;
LOG_INFO("Stopping file watcher thread");
ExitThread(0);
}
HRESULT
FILE_WATCHER::HandleChangeCompletion(
_In_ DWORD cbCompletion
)
/*++
Routine Description:
Handle change notification (see if any of associated config files
need to be flushed)
Arguments:
dwCompletionStatus - Completion status
cbCompletion - Bytes of completion
Return Value:
HRESULT
--*/
{
BOOL fFileChanged = FALSE;
// When directory handle is closed then HandleChangeCompletion
// happens with cbCompletion = 0 and dwCompletionStatus = 0
// From documentation it is not clear if that combination
// of return values is specific to closing handles or whether
// it could also mean an error condition. Hence we will maintain
// explicit flag that will help us determine if entry
// is being shutdown (StopMonitor() called)
//
if (_lStopMonitorCalled)
{
return S_OK;
}
//
// There could be a FCN overflow
// Let assume the file got changed instead of checking files
// Othersie we have to cache the file info
//
if (cbCompletion == 0)
{
fFileChanged = TRUE;
}
else
{
auto pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr();
DBG_ASSERT(pNotificationInfo != NULL);
while (pNotificationInfo != NULL)
{
//
// check whether the monitored file got changed
//
if (_wcsnicmp(pNotificationInfo->FileName,
_strFileName.QueryStr(),
pNotificationInfo->FileNameLength / sizeof(WCHAR)) == 0)
{
fFileChanged = TRUE;
break;
}
//
// Advance to next notification
//
if (pNotificationInfo->NextEntryOffset == 0)
{
pNotificationInfo = NULL;
}
else
{
pNotificationInfo = (FILE_NOTIFY_INFORMATION*)
((PBYTE)pNotificationInfo +
pNotificationInfo->NextEntryOffset);
}
}
}
if (fFileChanged && !_lStopMonitorCalled)
{
// Reference application before
_pApplication->ReferenceApplication();
RETURN_LAST_ERROR_IF(!QueueUserWorkItem(RunNotificationCallback, _pApplication.get(), WT_EXECUTEDEFAULT));
}
return S_OK;
}
DWORD
WINAPI
FILE_WATCHER::RunNotificationCallback(
LPVOID pvArg
)
{
// Recapture application instance into unique_ptr
auto pApplication = std::unique_ptr<AppOfflineTrackingApplication, IAPPLICATION_DELETER>(static_cast<AppOfflineTrackingApplication*>(pvArg));
DBG_ASSERT(pFileMonitor != NULL);
pApplication->OnAppOffline();
return 0;
}
HRESULT
FILE_WATCHER::Monitor(VOID)
{
HRESULT hr = S_OK;
DWORD cbRead;
ZeroMemory(&_overlapped, sizeof(_overlapped));
RETURN_LAST_ERROR_IF(!ReadDirectoryChangesW(_hDirectory,
_buffDirectoryChanges.QueryPtr(),
_buffDirectoryChanges.QuerySize(),
FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline
FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS,
&cbRead,
&_overlapped,
NULL));
// Check if file exist because ReadDirectoryChangesW would not fire events for existing files
if (GetFileAttributes(_strFullName.QueryStr()) != INVALID_FILE_ATTRIBUTES)
{
PostQueuedCompletionStatus(m_hCompletionPort, 0, 0, &_overlapped);
}
return hr;
}
VOID
FILE_WATCHER::StopMonitor()
{
//
// Flag that monitoring is being stopped so that
// we know that HandleChangeCompletion() call
// can be ignored
//
InterlockedExchange(&_lStopMonitorCalled, 1);
// signal the file watch thread to exit
PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL);
// Release application reference
_pApplication.reset(nullptr);
}