diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index a65af02467..307d669dbf 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -64,8 +64,9 @@ APPLICATION_INFO::GetOrCreateApplication( if (m_pApplication->QueryStatus() == RECYCLED) { LOG_INFO("Application went offline"); - // Application that went offline - // are supposed to recycle themselves + + // Call to wait for application to complete stopping + m_pApplication->Stop(/* fServerInitiated */ false); m_pApplication = nullptr; } else diff --git a/src/AspNetCoreModuleV2/CommonLib/application.h b/src/AspNetCoreModuleV2/CommonLib/application.h index 9761851002..1bc3d02f37 100644 --- a/src/AspNetCoreModuleV2/CommonLib/application.h +++ b/src/AspNetCoreModuleV2/CommonLib/application.h @@ -26,8 +26,19 @@ public: APPLICATION() : m_cRefs(1) { + InitializeSRWLock(&m_stateLock); } + + VOID + Stop(bool fServerInitiated) override + { + UNREFERENCED_PARAMETER(fServerInitiated); + + m_fStopCalled = true; + } + + VOID ReferenceApplication() override { @@ -49,6 +60,9 @@ public: protected: volatile APPLICATION_STATUS m_status = APPLICATION_STATUS::UNKNOWN; + SRWLOCK m_stateLock; + bool m_fStopCalled; + private: mutable LONG m_cRefs; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp index d271a3a30a..3152b75cca 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include "InProcessApplicationBase.h" -#include "SRWExclusiveLock.h" hostfxr_main_fn InProcessApplicationBase::s_fMainCallback = NULL; @@ -11,32 +10,14 @@ InProcessApplicationBase::InProcessApplicationBase( IHttpApplication& pHttpApplication) : AppOfflineTrackingApplication(pHttpApplication), m_fRecycleCalled(FALSE), - m_srwLock(), m_pHttpServer(pHttpServer) { - InitializeSRWLock(&m_srwLock); } VOID InProcessApplicationBase::Stop(bool fServerInitiated) { - // We need to guarantee that recycle is only called once, as calling pHttpServer->RecycleProcess - // multiple times can lead to AVs. - if (m_fRecycleCalled) - { - return; - } - - { - SRWExclusiveLock lock(m_srwLock); - - if (m_fRecycleCalled) - { - return; - } - - m_fRecycleCalled = true; - } + AppOfflineTrackingApplication::Stop(fServerInitiated); // Stop was initiated by server no need to do anything, server would stop on it's own if (fServerInitiated) diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h index c189095ffa..5df37d34b6 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -22,7 +22,6 @@ public: protected: BOOL m_fRecycleCalled; - SRWLOCK m_srwLock; IHttpServer& m_pHttpServer; // Allows to override call to hostfxr_main with custome callback // used in testing diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 2cbcc4d6f3..575d9180e4 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -69,6 +69,16 @@ IN_PROCESS_APPLICATION::Stop(bool fServerInitiated) HRESULT hr = S_OK; CHandle hThread; DWORD dwThreadStatus = 0; + + SRWExclusiveLock stopLock(m_stateLock); + + if (m_fStopCalled) + { + return; + } + + AppOfflineTrackingApplication::Stop(fServerInitiated); + DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); if (IsDebuggerPresent()) @@ -149,8 +159,6 @@ IN_PROCESS_APPLICATION::ShutDownInternal() } { - SRWExclusiveLock lock(m_srwLock); - if (m_fShutdownCalledFromNative || m_status == APPLICATION_STATUS::STARTING || m_status == APPLICATION_STATUS::FAIL) @@ -241,7 +249,7 @@ IN_PROCESS_APPLICATION::LoadManagedApplication HRESULT hr = S_OK; DWORD dwTimeout; DWORD dwResult; - + ReferenceApplication(); if (m_status != APPLICATION_STATUS::STARTING) @@ -263,7 +271,7 @@ IN_PROCESS_APPLICATION::LoadManagedApplication { // Set up stdout redirect - SRWExclusiveLock lock(m_srwLock); + SRWExclusiveLock lock(m_stateLock); if (m_pLoggerProvider == NULL) { diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index e23cf6b83a..a18d85a617 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -134,11 +134,9 @@ private: // The exit code of the .NET Core process INT m_ProcessExitCode; - BOOL m_fIsWebSocketsConnection; volatile BOOL m_fBlockCallbacksIntoManaged; volatile BOOL m_fShutdownCalledFromNative; volatile BOOL m_fShutdownCalledFromManaged; - BOOL m_fRecycleCalled; BOOL m_fInitialized; std::unique_ptr m_pConfig; diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp index 5f5877edac..41835538a9 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp @@ -14,12 +14,11 @@ OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( { m_status = APPLICATION_STATUS::RUNNING; m_pProcessManager = NULL; - InitializeSRWLock(&m_srwLock); } OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() { - SRWExclusiveLock lock(m_srwLock); + SRWExclusiveLock lock(m_stateLock); if (m_pProcessManager != NULL) { m_pProcessManager->Shutdown(); @@ -65,9 +64,15 @@ __override VOID OUT_OF_PROCESS_APPLICATION::Stop(bool fServerInitiated) { - UNREFERENCED_PARAMETER(fServerInitiated); + SRWExclusiveLock lock(m_stateLock); + + if (m_fStopCalled) + { + return; + } + + AppOfflineTrackingApplication::Stop(fServerInitiated); - SRWExclusiveLock lock(m_srwLock); if (m_pProcessManager != NULL) { m_pProcessManager->Shutdown(); diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h index 1667069c09..6ac813f6a7 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h @@ -56,7 +56,6 @@ private: VOID SetWebsocketStatus(IHttpContext *pHttpContext); PROCESS_MANAGER * m_pProcessManager; - SRWLOCK m_srwLock; IHttpServer *m_pHttpServer; WEBSOCKET_STATUS m_fWebSocketSupported; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp index 17c004b693..ed465eb7c1 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -26,6 +26,19 @@ HRESULT AppOfflineTrackingApplication::StartMonitoringAppOffline() return hr; } +void AppOfflineTrackingApplication::Stop(bool fServerInitiated) +{ + APPLICATION::Stop(fServerInitiated); + + m_status = APPLICATION_STATUS::RECYCLED; + + if (m_fileWatcher) + { + m_fileWatcher->StopMonitor(); + m_fileWatcher = nullptr; + } +} + HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() { if (m_fileWatcher) @@ -34,25 +47,21 @@ HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() } m_fileWatcher = std::make_unique(); - RETURN_IF_FAILED(m_fileWatcher->Create()); - m_fileWatcherEntry.reset(new FILE_WATCHER_ENTRY(m_fileWatcher.get())); - // FileWatcherCompletionRoutine calls dereference so we need to get ourselves a reference count - m_fileWatcherEntry->ReferenceFileWatcherEntry(); - RETURN_IF_FAILED(m_fileWatcherEntry->Create( - m_applicationPath.c_str(), + RETURN_IF_FAILED(m_fileWatcher->Create(m_applicationPath.c_str(), L"app_offline.htm", - std::bind(&AppOfflineTrackingApplication::OnAppOffline, this), - NULL)); + this)); return S_OK; } void AppOfflineTrackingApplication::OnAppOffline() { + if (m_fAppOfflineProcessed.exchange(true)) + { + return; + } + LOG_INFOF("Received app_offline notification in application %S", m_applicationPath.c_str()); - m_fileWatcherEntry->StopMonitor(); - m_fileWatcherEntry.reset(nullptr); - m_status = APPLICATION_STATUS::RECYCLED; UTILITY::LogEventF(g_hEventLog, EVENTLOG_INFORMATION_TYPE, ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h index 2b6c36ccfa..f104370c72 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -6,6 +6,7 @@ #include #include "application.h" #include "filewatcher.h" +#include class AppOfflineTrackingApplication: public APPLICATION { @@ -13,20 +14,23 @@ public: AppOfflineTrackingApplication(const IHttpApplication& application) : m_applicationPath(application.GetApplicationPhysicalPath()), m_fileWatcher(nullptr), - m_fileWatcherEntry(nullptr) + m_fAppOfflineProcessed(false) { } ~AppOfflineTrackingApplication() override { - if (m_fileWatcherEntry) + if (m_fileWatcher) { - m_fileWatcherEntry->StopMonitor(); + m_fileWatcher->StopMonitor(); } }; HRESULT StartMonitoringAppOffline(); + + VOID + Stop(bool fServerInitiated) override; virtual VOID @@ -38,5 +42,5 @@ private: std::wstring m_applicationPath; std::unique_ptr m_fileWatcher; - std::unique_ptr m_fileWatcherEntry; + std::atomic_bool m_fAppOfflineProcessed; }; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 721e248220..a4d7a0c57e 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -4,6 +4,7 @@ #include "stdafx.h" #include "filewatcher.h" #include "debugutil.h" +#include "AppOfflineTrackingApplication.h" FILE_WATCHER::FILE_WATCHER() : m_hCompletionPort(NULL), @@ -14,13 +15,13 @@ FILE_WATCHER::FILE_WATCHER() : FILE_WATCHER::~FILE_WATCHER() { + StopMonitor(); + if (m_hChangeNotificationThread != NULL) { DWORD dwRetryCounter = 20; // totally wait for 1s DWORD dwExitCode = STILL_ACTIVE; - // signal the file watch thread to exit - PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); while (!m_fThreadExit && dwRetryCounter > 0) { if (GetExitCodeThread(m_hChangeNotificationThread, &dwExitCode)) @@ -45,55 +46,66 @@ FILE_WATCHER::~FILE_WATCHER() { TerminateThread(m_hChangeNotificationThread, 1); } - - CloseHandle(m_hChangeNotificationThread); - m_hChangeNotificationThread = NULL; - } - - if (NULL != m_hCompletionPort) - { - CloseHandle(m_hCompletionPort); - m_hCompletionPort = NULL; } } HRESULT FILE_WATCHER::Create( - VOID + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ AppOfflineTrackingApplication *pApplication ) { - HRESULT hr = S_OK; - m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, - NULL, - 0, - 0); + RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)); - if (m_hCompletionPort == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - m_hChangeNotificationThread = CreateThread(NULL, + RETURN_LAST_ERROR_IF_NULL(m_hChangeNotificationThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ChangeNotificationThread, this, 0, - NULL); + NULL)); - if (m_hChangeNotificationThread == NULL) + if (pszDirectoryToMonitor == NULL || + pszFileNameToMonitor == NULL || + pApplication == NULL) { - hr = HRESULT_FROM_WIN32(GetLastError()); - - CloseHandle(m_hCompletionPort); - m_hCompletionPort = NULL; - - goto Finished; + DBG_ASSERT(FALSE); + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); } -Finished: - return hr; + _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 @@ -148,107 +160,29 @@ Win32 error DBG_ASSERT(pOverlapped != NULL); if (pOverlapped != NULL) { - FileWatcherCompletionRoutine( - dwErrorStatus, - cbCompletion, - pOverlapped); + 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); } -VOID -WINAPI -FILE_WATCHER::FileWatcherCompletionRoutine( - DWORD dwCompletionStatus, - DWORD cbCompletion, - OVERLAPPED * pOverlapped -) -/*++ - -Routine Description: - -Called when ReadDirectoryChangesW() completes - -Arguments: - -dwCompletionStatus - Error of completion -cbCompletion - Bytes of completion -pOverlapped - State of completion - -Return Value: - -None - ---*/ -{ - FILE_WATCHER_ENTRY * pMonitorEntry; - pMonitorEntry = CONTAINING_RECORD(pOverlapped, FILE_WATCHER_ENTRY, _overlapped); - - DBG_ASSERT(pMonitorEntry != NULL); - - pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion); - - if (pMonitorEntry->QueryIsValid()) - { - // - // Continue monitoring - // - pMonitorEntry->Monitor(); - } - // - // Deference the counter not matter whether the monitor is valid - // Valid: Monitor increases the counter, need to reduce one - // InValid: Reduce the counter to free the entry - // - pMonitorEntry->DereferenceFileWatcherEntry(); - -} - - -FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : - _pFileMonitor(pFileMonitor), - _hDirectory(INVALID_HANDLE_VALUE), - _hImpersonationToken(NULL), - _pCallback(), - _lStopMonitorCalled(0), - _cRefs(1), - _fIsValid(TRUE) -{ - _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE; - InitializeSRWLock(&_srwLock); -} - -FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() -{ - DBG_ASSERT(_cRefs == 0); - - _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; - - if (_hDirectory != INVALID_HANDLE_VALUE) - { - CloseHandle(_hDirectory); - _hDirectory = INVALID_HANDLE_VALUE; - } - - if (_hImpersonationToken != NULL) - { - CloseHandle(_hImpersonationToken); - _hImpersonationToken = NULL; - } -} - -#pragma warning(disable:4100) HRESULT -FILE_WATCHER_ENTRY::HandleChangeCompletion( - _In_ DWORD dwCompletionStatus, +FILE_WATCHER::HandleChangeCompletion( _In_ DWORD cbCompletion ) /*++ @@ -269,16 +203,8 @@ HRESULT --*/ { - HRESULT hr = S_OK; - FILE_NOTIFY_INFORMATION * pNotificationInfo; BOOL fFileChanged = FALSE; - AcquireSRWLockExclusive(&_srwLock); - if (!_fIsValid) - { - goto Finished; - } - // When directory handle is closed then HandleChangeCompletion // happens with cbCompletion = 0 and dwCompletionStatus = 0 // From documentation it is not clear if that combination @@ -289,7 +215,7 @@ HRESULT // if (_lStopMonitorCalled) { - goto Finished; + return S_OK; } // @@ -303,7 +229,7 @@ HRESULT } else { - pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); + auto pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); DBG_ASSERT(pNotificationInfo != NULL); while (pNotificationInfo != NULL) @@ -334,55 +260,58 @@ HRESULT } } -Finished: - ReleaseSRWLockExclusive(&_srwLock); - - if (fFileChanged) + if (fFileChanged && !_lStopMonitorCalled) { - // - // so far we only monitoring app_offline - // - _pCallback(); + // Reference application before + _pApplication->ReferenceApplication(); + RETURN_LAST_ERROR_IF(!QueueUserWorkItem(RunNotificationCallback, _pApplication.get(), WT_EXECUTEDEFAULT)); } - return hr; + + return S_OK; } -#pragma warning( error : 4100 ) +DWORD +WINAPI +FILE_WATCHER::RunNotificationCallback( + LPVOID pvArg +) +{ + // Recapture application instance into unique_ptr + auto pApplication = std::unique_ptr(static_cast(pvArg)); + DBG_ASSERT(pFileMonitor != NULL); + pApplication->OnAppOffline(); + + return 0; +} HRESULT -FILE_WATCHER_ENTRY::Monitor(VOID) +FILE_WATCHER::Monitor(VOID) { HRESULT hr = S_OK; DWORD cbRead; - AcquireSRWLockExclusive(&_srwLock); - ReferenceFileWatcherEntry(); + ZeroMemory(&_overlapped, sizeof(_overlapped)); - if (!ReadDirectoryChangesW(_hDirectory, + 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)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceFileWatcherEntry(); - } + NULL)); // Check if file exist because ReadDirectoryChangesW would not fire events for existing files if (GetFileAttributes(_strFullName.QueryStr()) != INVALID_FILE_ATTRIBUTES) { - PostQueuedCompletionStatus(_pFileMonitor->QueryCompletionPort(), 0, 0, &_overlapped); + PostQueuedCompletionStatus(m_hCompletionPort, 0, 0, &_overlapped); } - - ReleaseSRWLockExclusive(&_srwLock); + return hr; } VOID -FILE_WATCHER_ENTRY::StopMonitor(VOID) +FILE_WATCHER::StopMonitor() { // // Flag that monitoring is being stopped so that @@ -390,123 +319,8 @@ FILE_WATCHER_ENTRY::StopMonitor(VOID) // can be ignored // InterlockedExchange(&_lStopMonitorCalled, 1); - MarkEntryInValid(); - if (_hDirectory != INVALID_HANDLE_VALUE) - { - AcquireSRWLockExclusive(&_srwLock); - if (_hDirectory != INVALID_HANDLE_VALUE) - { - CloseHandle(_hDirectory); - _hDirectory = INVALID_HANDLE_VALUE; - DereferenceFileWatcherEntry(); - } - ReleaseSRWLockExclusive(&_srwLock); - } -} - -HRESULT -FILE_WATCHER_ENTRY::Create( - _In_ PCWSTR pszDirectoryToMonitor, - _In_ PCWSTR pszFileNameToMonitor, - _In_ std::function pCallback, - _In_ HANDLE hImpersonationToken -) -{ - HRESULT hr = S_OK; - BOOL fRet = FALSE; - - if (pszDirectoryToMonitor == NULL || - pszFileNameToMonitor == NULL || - pCallback == NULL) - { - DBG_ASSERT(FALSE); - hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); - goto Finished; - } - - _pCallback = pCallback; - - if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) - { - goto Finished; - } - - if (FAILED(hr = _strDirectoryName.Copy(pszDirectoryToMonitor))) - { - goto Finished; - } - - if (FAILED(hr = _strFullName.Append(_strDirectoryName)) || - FAILED(hr = _strFullName.Append(_strFileName))) - { - goto Finished; - } - - // - // Resize change buffer to something "reasonable" - // - if (!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE)) - { - hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); - goto Finished; - } - - if (hImpersonationToken != NULL) - { - fRet = DuplicateHandle(GetCurrentProcess(), - hImpersonationToken, - GetCurrentProcess(), - &_hImpersonationToken, - 0, - FALSE, - DUPLICATE_SAME_ACCESS); - - if (!fRet) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - } - else - { - if (_hImpersonationToken != NULL) - { - CloseHandle(_hImpersonationToken); - _hImpersonationToken = NULL; - } - } - - _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); - - if (_hDirectory == INVALID_HANDLE_VALUE) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (CreateIoCompletionPort( - _hDirectory, - _pFileMonitor->QueryCompletionPort(), - NULL, - 0) == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - // - // Start monitoring - // - hr = Monitor(); - -Finished: - - return hr; + // signal the file watch thread to exit + PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); + // Release application reference + _pApplication.reset(nullptr); } diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index 4e37600c69..46ea744533 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -6,12 +6,14 @@ #include #include +#include "iapplication.h" +#include "HandleWrapper.h" #define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) #define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 #define FILE_NOTIFY_VALID_MASK 0x00000fff -#define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') -#define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') + +class AppOfflineTrackingApplication; class FILE_WATCHER{ public: @@ -20,110 +22,36 @@ public: ~FILE_WATCHER(); - HRESULT Create(); - - HANDLE - QueryCompletionPort( - VOID - ) const - { - return m_hCompletionPort; - } + HRESULT Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ AppOfflineTrackingApplication *pApplication + ); static DWORD WINAPI ChangeNotificationThread(LPVOID); static - void - WINAPI FileWatcherCompletionRoutine - ( - DWORD dwCompletionStatus, - DWORD cbCompletion, - OVERLAPPED * pOverlapped - ); + DWORD + WINAPI RunNotificationCallback(LPVOID); -private: - HANDLE m_hCompletionPort; - HANDLE m_hChangeNotificationThread; - volatile BOOL m_fThreadExit; -}; - -class FILE_WATCHER_ENTRY -{ -public: - FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); - - OVERLAPPED _overlapped; - - HRESULT - Create( - _In_ PCWSTR pszDirectoryToMonitor, - _In_ PCWSTR pszFileNameToMonitor, - _In_ std::function pCallback, - _In_ HANDLE hImpersonationToken - ); - - VOID - ReferenceFileWatcherEntry() const - { - InterlockedIncrement(&_cRefs); - } - - VOID - DereferenceFileWatcherEntry() const - { - if (InterlockedDecrement(&_cRefs) == 0) - { - delete this; - } - } - - BOOL - QueryIsValid() const - { - return _fIsValid; - } - - VOID - MarkEntryInValid() - { - _fIsValid = FALSE; - } + HRESULT HandleChangeCompletion(DWORD cbCompletion); HRESULT Monitor(); - - VOID StopMonitor(); - - HRESULT - HandleChangeCompletion( - _In_ DWORD dwCompletionStatus, - _In_ DWORD cbCompletion - ); + void StopMonitor(); private: - virtual ~FILE_WATCHER_ENTRY(); + HandleWrapper m_hCompletionPort; + HandleWrapper m_hChangeNotificationThread; + HandleWrapper _hDirectory; + volatile BOOL m_fThreadExit; - DWORD _dwSignature; BUFFER _buffDirectoryChanges; - HANDLE _hImpersonationToken; - HANDLE _hDirectory; - FILE_WATCHER* _pFileMonitor; STRU _strFileName; STRU _strDirectoryName; STRU _strFullName; - LONG _lStopMonitorCalled; - mutable LONG _cRefs; - BOOL _fIsValid; - SRWLOCK _srwLock; - std::function _pCallback; -}; - - -struct FILE_WATCHER_ENTRY_DELETER -{ - void operator()(FILE_WATCHER_ENTRY* entry) const - { - entry->DereferenceFileWatcherEntry(); - } + LONG _lStopMonitorCalled {}; + OVERLAPPED _overlapped; + std::unique_ptr _pApplication; }; diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs index ce1f3e426e..aa35ac92da 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting /// internal class IISApplication { + internal const int ERROR_OBJECT_NOT_FOUND = unchecked((int)0x800710D8); + private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(10); private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200); private readonly ServerManager _serverManager = new ServerManager(); @@ -106,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting _logger.LogInformation($"Tried to start site, state: {state.ToString()}"); } } - catch (Exception ex) when (ex is DllNotFoundException || (ex is COMException && (uint)ex.HResult == 0x800710D8) ) + catch (Exception ex) when (ex is DllNotFoundException || (ex is COMException && ex.HResult == ERROR_OBJECT_NOT_FOUND) ) { // Accessing the site.State property while the site // is starting up returns the COMException diff --git a/test/Common.FunctionalTests/AppOfflineTests.cs b/test/Common.FunctionalTests/AppOfflineTests.cs index b2c30663a9..169be93035 100644 --- a/test/Common.FunctionalTests/AppOfflineTests.cs +++ b/test/Common.FunctionalTests/AppOfflineTests.cs @@ -77,7 +77,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess for (int i = 0; i < 10; i++) { - // send first request and add app_offline while app is starting var runningTask = AssertAppOffline(deploymentResult); @@ -220,14 +219,10 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess { HttpResponseMessage response = null; - for (var i = 0; i < 5; i++) + for (var i = 0; i < 5 && response?.StatusCode != HttpStatusCode.ServiceUnavailable; i++) { // Keep retrying until app_offline is present. response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); - if (!response.IsSuccessStatusCode) - { - break; - } } Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);