diff --git a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index db4a1edf2d..1a80711d26 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -226,11 +226,9 @@ - - @@ -239,7 +237,6 @@ - Create diff --git a/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h index f6f5890b8e..92b5aaff81 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h +++ b/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h @@ -3,17 +3,12 @@ #pragma once -#include - #include "hostfxroptions.h" -#include "appoffline.h" -#include "filewatcher.h" #include "hashtable.h" #include "hashfn.h" #include "aspnetcore_shim_config.h" #include "iapplication.h" #include "SRWSharedLock.h" -#include "ntassert.h" #define API_BUFFER_TOO_SMALL 0x80008098 @@ -37,11 +32,8 @@ public: APPLICATION_INFO() : m_pServer(NULL), m_cRefs(1), - m_fAppOfflineFound(FALSE), m_fValid(FALSE), - m_fDoneAppCreation(FALSE), - m_pAppOfflineHtm(NULL), - m_pFileWatcherEntry(NULL), + m_fAppCreationAttempted(FALSE), m_pConfiguration(NULL), m_pfnAspNetCoreCreateApplication(NULL) { @@ -60,8 +52,7 @@ public: HRESULT Initialize( _In_ IHttpServer *pServer, - _In_ IHttpApplication *pApplication, - _In_ FILE_WATCHER *pFileWatcher + _In_ IHttpApplication *pApplication ); VOID @@ -79,18 +70,6 @@ public: } } - APP_OFFLINE_HTM* - QueryAppOfflineHtm() - { - return m_pAppOfflineHtm; - } - - BOOL - AppOfflineFound() - { - return m_fAppOfflineFound; - } - BOOL IsValid() { @@ -103,19 +82,12 @@ public: m_fValid = TRUE; } - VOID - UpdateAppOfflineFileHandle(); - - HRESULT - StartMonitoringAppOffline(); - ASPNETCORE_SHIM_CONFIG* QueryConfig() { return m_pConfiguration; } - // // ExtractApplication will increase the reference counter of the application // Caller is responsible for dereference the application. @@ -152,11 +124,8 @@ private: mutable LONG m_cRefs; STRU m_struInfoKey; - BOOL m_fAppOfflineFound; BOOL m_fValid; - BOOL m_fDoneAppCreation; - APP_OFFLINE_HTM *m_pAppOfflineHtm; - FILE_WATCHER_ENTRY *m_pFileWatcherEntry; + BOOL m_fAppCreationAttempted; ASPNETCORE_SHIM_CONFIG *m_pConfiguration; IAPPLICATION *m_pApplication; SRWLOCK m_srwLock; diff --git a/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h index dac98163f5..5245af06d5 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h @@ -5,7 +5,7 @@ #include "applicationinfo.h" #include "multisz.h" -#include "filewatcher.h" +#include "exceptions.h" #define DEFAULT_HASH_BUCKETS 17 @@ -83,11 +83,6 @@ public: ~APPLICATION_MANAGER() { - if (m_pFileWatcher != NULL) - { - delete m_pFileWatcher; - m_pFileWatcher = NULL; - } if(m_pApplicationInfoHash != NULL) { @@ -97,41 +92,18 @@ public: } } - FILE_WATCHER* - GetFileWatcher() - { - return m_pFileWatcher; - } - HRESULT Initialize() { - HRESULT hr = S_OK; - if(m_pApplicationInfoHash == NULL) { - m_pApplicationInfoHash = new APPLICATION_INFO_HASH(); - - hr = m_pApplicationInfoHash->Initialize(DEFAULT_HASH_BUCKETS); - if(FAILED(hr)) + try { - goto Finished; + m_pApplicationInfoHash = new APPLICATION_INFO_HASH(); } + CATCH_RETURN(); + RETURN_IF_FAILED(m_pApplicationInfoHash->Initialize(DEFAULT_HASH_BUCKETS)); } - - if( m_pFileWatcher == NULL ) - { - m_pFileWatcher = new FILE_WATCHER; - if(m_pFileWatcher == NULL) - { - hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); - goto Finished; - } - - m_pFileWatcher->Create(); - } - - Finished: - return hr; + return S_OK; } private: @@ -139,13 +111,11 @@ private: // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload // APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL), - m_pFileWatcher(NULL), - m_hostingModel(HOSTING_UNKNOWN) + m_hostingModel(HOSTING_UNKNOWN) { InitializeSRWLock(&m_srwLock); } - FILE_WATCHER *m_pFileWatcher; APPLICATION_INFO_HASH *m_pApplicationInfoHash; static APPLICATION_MANAGER *sm_pApplicationManager; SRWLOCK m_srwLock; diff --git a/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h deleted file mode 100644 index 0f985ca64d..0000000000 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -#include "stringa.h" -#include "stringu.h" - -class APP_OFFLINE_HTM -{ -public: - APP_OFFLINE_HTM(LPCWSTR pszPath) : m_cRefs(1) - { - m_Path.Copy(pszPath); - } - - VOID - ReferenceAppOfflineHtm() const - { - InterlockedIncrement(&m_cRefs); - } - - VOID - DereferenceAppOfflineHtm() const - { - if (InterlockedDecrement(&m_cRefs) == 0) - { - delete this; - } - } - - BOOL - Load( - VOID - ) - { - BOOL fResult = TRUE; - LARGE_INTEGER li = { 0 }; - CHAR *pszBuff = NULL; - HANDLE handle = INVALID_HANDLE_VALUE; - - handle = CreateFile(m_Path.QueryStr(), - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (handle == INVALID_HANDLE_VALUE) - { - if (HRESULT_FROM_WIN32(GetLastError()) == ERROR_FILE_NOT_FOUND) - { - fResult = FALSE; - } - - // This Load() member function is supposed be called only when the change notification event of file creation or file modification happens. - // If file is currenlty locked exclusively by other processes, we might get INVALID_HANDLE_VALUE even though the file exists. In that case, we should return TRUE here. - goto Finished; - } - - if (!GetFileSizeEx(handle, &li)) - { - goto Finished; - } - - if (li.HighPart != 0) - { - // > 4gb file size not supported - // todo: log a warning at event log - goto Finished; - } - - DWORD bytesRead = 0; - - if (li.LowPart > 0) - { - pszBuff = new CHAR[li.LowPart + 1]; - - if (ReadFile(handle, pszBuff, li.LowPart, &bytesRead, NULL)) - { - m_Contents.Copy(pszBuff, bytesRead); - } - } - - Finished: - if (handle != INVALID_HANDLE_VALUE) - { - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - } - - if (pszBuff != NULL) - { - delete[] pszBuff; - pszBuff = NULL; - } - - return fResult; - } - - mutable LONG m_cRefs; - STRA m_Contents; - STRU m_Path; -}; diff --git a/src/AspNetCoreModuleV2/AspNetCore/Inc/precomp.hxx b/src/AspNetCoreModuleV2/AspNetCore/Inc/precomp.hxx index b0ee0b7e32..1191022c01 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/precomp.hxx +++ b/src/AspNetCoreModuleV2/AspNetCore/Inc/precomp.hxx @@ -16,8 +16,9 @@ #include #include #include +#include #include "stringu.h" -#include "stringu.h" +#include "stringa.h" extern PVOID g_pModuleId; extern BOOL g_fAspnetcoreRHAssemblyLoaded; diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp index 99251ddc4a..112915ba08 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp @@ -12,29 +12,14 @@ #include "SRWExclusiveLock.h" #include "GlobalVersionUtility.h" #include "exceptions.h" +#include "HandleWrapper.h" +#include "PollingAppOfflineApplication.h" const PCWSTR APPLICATION_INFO::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll"; const PCWSTR APPLICATION_INFO::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll"; APPLICATION_INFO::~APPLICATION_INFO() { - if (m_pAppOfflineHtm != NULL) - { - m_pAppOfflineHtm->DereferenceAppOfflineHtm(); - m_pAppOfflineHtm = NULL; - } - - if (m_pFileWatcherEntry != NULL) - { - // Mark the entry as invalid, - // StopMonitor will close the file handle and trigger a FCN - // the entry will delete itself when processing this FCN - m_pFileWatcherEntry->MarkEntryInValid(); - m_pFileWatcherEntry->StopMonitor(); - m_pFileWatcherEntry->DereferenceFileWatcherEntry(); - m_pFileWatcherEntry = NULL; - } - if (m_pApplication != NULL) { // shutdown the application @@ -55,118 +40,24 @@ APPLICATION_INFO::~APPLICATION_INFO() HRESULT APPLICATION_INFO::Initialize( _In_ IHttpServer *pServer, - _In_ IHttpApplication *pApplication, - _In_ FILE_WATCHER *pFileWatcher + _In_ IHttpApplication *pApplication ) { HRESULT hr = S_OK; DBG_ASSERT(pServer); DBG_ASSERT(pApplication); - DBG_ASSERT(pFileWatcher); // todo: make sure Initialize should be called only once m_pServer = pServer; FINISHED_IF_NULL_ALLOC(m_pConfiguration = new ASPNETCORE_SHIM_CONFIG()); FINISHED_IF_FAILED(m_pConfiguration->Populate(m_pServer, pApplication)); FINISHED_IF_FAILED(m_struInfoKey.Copy(pApplication->GetApplicationId())); - FINISHED_IF_NULL_ALLOC(m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pFileWatcher)); - - UpdateAppOfflineFileHandle(); Finished: return hr; } -HRESULT -APPLICATION_INFO::StartMonitoringAppOffline() -{ - if (m_pFileWatcherEntry != NULL) - { - RETURN_IF_FAILED(m_pFileWatcherEntry->Create(m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), L"app_offline.htm", this, NULL)); - } - return S_OK; -} - -// -// Called by the file watcher when the app_offline.htm's file status has been changed. -// If it finds it, we will call recycle on the application. -// -VOID -APPLICATION_INFO::UpdateAppOfflineFileHandle() -{ - STRU strFilePath; - UTILITY::ConvertPathToFullPath(L".\\app_offline.htm", - m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), - &strFilePath); - APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; - APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL; - - ReferenceApplicationInfo(); - - if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr())) - { - // Check if app offline was originally present. - // if it was, log that app_offline has been dropped. - if (m_fAppOfflineFound) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG); - } - - m_fAppOfflineFound = FALSE; - } - else - { - pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); - - if (pNewAppOfflineHtm != NULL) - { - if (pNewAppOfflineHtm->Load()) - { - // - // loaded the new app_offline.htm - // - pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); - - if (pOldAppOfflineHtm != NULL) - { - pOldAppOfflineHtm->DereferenceAppOfflineHtm(); - pOldAppOfflineHtm = NULL; - } - } - else - { - // ignored the new app_offline file because the file does not exist. - pNewAppOfflineHtm->DereferenceAppOfflineHtm(); - pNewAppOfflineHtm = NULL; - } - } - - m_fAppOfflineFound = TRUE; - - // recycle the application - if (m_pApplication != NULL) - { - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, - m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr()))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, - strEventMsg.QueryStr()); - } - - RecycleApplication(); - } - } - - DereferenceApplicationInfo(); -} HRESULT APPLICATION_INFO::EnsureApplicationCreated( @@ -179,57 +70,68 @@ APPLICATION_INFO::EnsureApplicationCreated( STRU struHostFxrDllLocation; STACK_STRU(struFileName, 300); // >MAX_PATH - if (m_pApplication != NULL) + if (m_pApplication != nullptr && m_pApplication->QueryStatus() != RECYCLED) { return S_OK; } - // one optimization for failure scenario is to reduce the lock scope SRWExclusiveLock lock(m_srwLock); + if (m_pApplication != nullptr) { - if (m_pApplication != NULL) + if (m_pApplication->QueryStatus() == RECYCLED) { - // another thread created the applicaiton - FINISHED(S_OK); + LOG_INFO("Application went offline"); + // Application that went offline + // are supposed to recycle themselves + m_pApplication->DereferenceApplication(); + m_pApplication = nullptr; } - else if (m_fDoneAppCreation) + else { - // previous CreateApplication failed - FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); + // another thread created the application + FINISHED(S_OK); + } + } + else if (m_fAppCreationAttempted) + { + // previous CreateApplication failed + FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); + } + + auto& httpApplication = *pHttpContext->GetApplication(); + if (PollingAppOfflineApplication::ShouldBeStarted(httpApplication)) + { + LOG_INFO("Detected app_ofline file, creating polling application"); + m_pApplication = new PollingAppOfflineApplication(httpApplication); + } + else + { + // Move the request handler check inside of the lock + // such that only one request finds and loads it. + // FindRequestHandlerAssembly obtains a global lock, but after releasing the lock, + // there is a period where we could call + + m_fAppCreationAttempted = TRUE; + FINISHED_IF_FAILED(FindRequestHandlerAssembly(struExeLocation)); + + if (m_pfnAspNetCoreCreateApplication == NULL) + { + FINISHED(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION)); } - // - // in case of app offline, we don't want to create a new application now - // - if (!m_fAppOfflineFound) - { - // Move the request handler check inside of the lock - // such that only one request finds and loads it. - // FindRequestHandlerAssembly obtains a global lock, but after releasing the lock, - // there is a period where we could call + std::array parameters { + {"InProcessExeLocation", struExeLocation.QueryStr()} + }; + LOG_INFO("Creating handler application"); + FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication( + m_pServer, + pHttpContext->GetApplication(), + parameters.data(), + static_cast(parameters.size()), + &pApplication)); - m_fDoneAppCreation = TRUE; - FINISHED_IF_FAILED(FindRequestHandlerAssembly(struExeLocation)); - - if (m_pfnAspNetCoreCreateApplication == NULL) - { - FINISHED(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION)); - } - - std::array parameters { - {"InProcessExeLocation", struExeLocation.QueryStr()} - }; - - FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication( - m_pServer, - pHttpContext->GetApplication(), - parameters.data(), - static_cast(parameters.size()), - &pApplication)); - - m_pApplication = pApplication; - } + m_pApplication = pApplication; } Finished: diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx b/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx index e492b53f04..71fb2e464f 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx +++ b/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx @@ -57,7 +57,7 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( { pApplicationInfo = new APPLICATION_INFO(); - FINISHED_IF_FAILED(pApplicationInfo->Initialize(pServer, pHttpContext->GetApplication(), m_pFileWatcher)); + FINISHED_IF_FAILED(pApplicationInfo->Initialize(pServer, pHttpContext->GetApplication())); SRWExclusiveLock lock(m_srwLock); @@ -102,10 +102,7 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( FINISHED_IF_FAILED(m_pApplicationInfoHash->InsertRecord(pApplicationInfo)); - *ppApplicationInfo = pApplicationInfo; - pApplicationInfo->StartMonitoringAppOffline(); - pApplicationInfo = NULL; } @@ -323,12 +320,6 @@ APPLICATION_MANAGER::ShutDown() g_fInShutdown = TRUE; if (m_pApplicationInfoHash != NULL) { - if (m_pFileWatcher != NULL) - { - delete m_pFileWatcher; - m_pFileWatcher = NULL; - } - DBG_ASSERT(m_pApplicationInfoHash); // During shutdown we lock until we delete the application diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx b/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx index 6987863616..2289143202 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx +++ b/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx @@ -107,37 +107,6 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( goto Finished; } - // app_offline check to avoid loading aspnetcorerh.dll unnecessarily - if (m_pApplicationInfo->AppOfflineFound()) - { - // servicing app_offline - HTTP_DATA_CHUNK DataChunk; - IHttpResponse *pResponse = NULL; - APP_OFFLINE_HTM *pAppOfflineHtm = NULL; - - pResponse = pHttpContext->GetResponse(); - pAppOfflineHtm = m_pApplicationInfo->QueryAppOfflineHtm(); - DBG_ASSERT(pAppOfflineHtm); - DBG_ASSERT(pResponse); - - // Ignore failure hresults as nothing we can do - // Set fTrySkipCustomErrors to true as we want client see the offline content - pResponse->SetStatus(503, "Service Unavailable", 0, hr, NULL, TRUE); - pResponse->SetHeader("Content-Type", - "text/html", - (USHORT)strlen("text/html"), - FALSE - ); - - DataChunk.DataChunkType = HttpDataChunkFromMemory; - DataChunk.FromMemory.pBuffer = (PVOID)pAppOfflineHtm->m_Contents.QueryStr(); - DataChunk.FromMemory.BufferLength = pAppOfflineHtm->m_Contents.QueryCB(); - pResponse->WriteEntityChunkByReference(&DataChunk); - - retVal = RQ_NOTIFICATION_FINISH_REQUEST; - goto Finished; - } - // make sure assmebly is loaded and application is created hr = m_pApplicationInfo->EnsureApplicationCreated(pHttpContext); if (FAILED(hr)) @@ -147,18 +116,18 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( m_pApplicationInfo->ExtractApplication(&pApplication); - // make sure application is in running state - // cannot recreate the application as we cannot reload clr for inprocess - if (pApplication != NULL && - pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING && + DBG_ASSERT(pHttpContext); + + // We allow OFFLINE application to serve pages + if (pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING && pApplication->QueryStatus() != APPLICATION_STATUS::STARTING) { hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); goto Finished; } + // Create RequestHandler and process the request - hr = pApplication->CreateHandler(pHttpContext, - &m_pHandler); + hr = pApplication->CreateHandler(pHttpContext, &m_pHandler); if (FAILED(hr)) { diff --git a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index bb34327e27..efeb7f1ddc 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -205,6 +205,7 @@ + @@ -221,6 +222,7 @@ + diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp new file mode 100644 index 0000000000..3b07cf8645 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp @@ -0,0 +1,133 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "PollingAppOfflineApplication.h" + +#include +#include "SRWExclusiveLock.h" +#include "HandleWrapper.h" + +HRESULT PollingAppOfflineApplication::CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler) +{ + try + { + *pRequestHandler = new PollingAppOfflineHandler(pHttpContext, m_strAppOfflineContent); + } + CATCH_RETURN(); + + return S_OK; +} + +APPLICATION_STATUS PollingAppOfflineApplication::QueryStatus() +{ + if (AppOfflineExists()) + { + return APPLICATION_STATUS::RUNNING; + } + + return APPLICATION_STATUS::RECYCLED; +} + +bool +PollingAppOfflineApplication::AppOfflineExists() +{ + const auto ulCurrentTime = GetTickCount64(); + // + // we only care about app offline presented. If not, it means the application has started + // and is monitoring the app offline file + // we cache the file exist check result for 1 second + // + if (ulCurrentTime - m_ulLastCheckTime > c_appOfflineRefreshIntervalMS) + { + SRWExclusiveLock lock(m_statusLock); + if (ulCurrentTime - m_ulLastCheckTime > c_appOfflineRefreshIntervalMS) + { + m_fAppOfflineFound = is_regular_file(m_appOfflineLocation); + if(m_fAppOfflineFound) + { + LOG_IF_FAILED(LoadAppOfflineContent()); + } + m_ulLastCheckTime = ulCurrentTime; + } + } + return m_fAppOfflineFound; +} + +HRESULT PollingAppOfflineApplication::LoadAppOfflineContent() +{ + LARGE_INTEGER li = {}; + + HandleWrapper handle = CreateFile(m_appOfflineLocation.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + RETURN_LAST_ERROR_IF(handle == INVALID_HANDLE_VALUE); + + RETURN_LAST_ERROR_IF (!GetFileSizeEx(handle, &li)); + + if (li.HighPart != 0) + { + // > 4gb file size not supported + // todo: log a warning at event log + return TRUE; + } + + if (li.LowPart > 0) + { + DWORD bytesRead = 0; + std::string pszBuff(li.LowPart + 1, '\0'); + + RETURN_LAST_ERROR_IF(!ReadFile(handle, pszBuff.data(), li.LowPart, &bytesRead, NULL)); + pszBuff.resize(bytesRead); + + m_strAppOfflineContent = pszBuff; + } + + return S_OK; +} + +bool PollingAppOfflineApplication::ShouldBeStarted(IHttpApplication& pApplication) +{ + return is_regular_file(GetAppOfflineLocation(pApplication)); +} + +std::experimental::filesystem::path PollingAppOfflineApplication::GetAppOfflineLocation(IHttpApplication& pApplication) +{ + return std::experimental::filesystem::path(pApplication.GetApplicationPhysicalPath()) / "app_offline.htm"; +} + +void PollingAppOfflineApplication::ShutDown() +{ +} + +void PollingAppOfflineApplication::Recycle() +{ +} + +REQUEST_NOTIFICATION_STATUS PollingAppOfflineHandler::OnExecuteRequestHandler() +{ + HTTP_DATA_CHUNK DataChunk; + IHttpResponse* pResponse = m_pContext->GetResponse(); + + DBG_ASSERT(pResponse); + + // Ignore failure hresults as nothing we can do + // Set fTrySkipCustomErrors to true as we want client see the offline content + pResponse->SetStatus(503, "Service Unavailable", 0, S_OK, nullptr, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + static_cast(strlen("text/html")), + FALSE + ); + + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = m_strAppOfflineContent.data(); + DataChunk.FromMemory.BufferLength = static_cast(m_strAppOfflineContent.size()); + pResponse->WriteEntityChunkByReference(&DataChunk); + + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; +} diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h new file mode 100644 index 0000000000..e66f5f46c8 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once +#include +#include "application.h" +#include "requesthandler.h" + +class PollingAppOfflineApplication: public APPLICATION +{ +public: + PollingAppOfflineApplication(IHttpApplication& pApplication) + : + m_ulLastCheckTime(0), + m_appOfflineLocation(GetAppOfflineLocation(pApplication)), + m_fAppOfflineFound(false) + { + InitializeSRWLock(&m_statusLock); + } + + HRESULT CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler) override; + + APPLICATION_STATUS QueryStatus() override; + bool AppOfflineExists(); + HRESULT LoadAppOfflineContent(); + static bool ShouldBeStarted(IHttpApplication& pApplication); + void ShutDown() override; + void Recycle() override; + +private: + static const int c_appOfflineRefreshIntervalMS = 200; + static std::experimental::filesystem::path GetAppOfflineLocation(IHttpApplication& pApplication); + std::string m_strAppOfflineContent; + ULONGLONG m_ulLastCheckTime; + std::experimental::filesystem::path m_appOfflineLocation; + bool m_fAppOfflineFound; + SRWLOCK m_statusLock {}; +}; + + +class PollingAppOfflineHandler: public REQUEST_HANDLER +{ +public: + PollingAppOfflineHandler(IHttpContext* pContext, const std::string appOfflineContent) + : m_pContext(pContext), + m_strAppOfflineContent(appOfflineContent) + { + } + + REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override; + +private: + IHttpContext* m_pContext; + std::string m_strAppOfflineContent; +}; + + diff --git a/src/AspNetCoreModuleV2/CommonLib/application.h b/src/AspNetCoreModuleV2/CommonLib/application.h index 0c0752f0c4..56cf5ce2ac 100644 --- a/src/AspNetCoreModuleV2/CommonLib/application.h +++ b/src/AspNetCoreModuleV2/CommonLib/application.h @@ -4,12 +4,18 @@ #pragma once #include "iapplication.h" +#include "exceptions.h" +#include "utility.h" #include "ntassert.h" + class APPLICATION : public IAPPLICATION { public: + // Non-copyable + APPLICATION(const APPLICATION&) = delete; + const APPLICATION& operator=(const APPLICATION&) = delete; APPLICATION_STATUS QueryStatus() override @@ -33,8 +39,7 @@ public: { DBG_ASSERT(m_cRefs != 0); - LONG cRefs = 0; - if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) + if (InterlockedDecrement(&m_cRefs) == 0) { delete this; } @@ -42,7 +47,7 @@ public: protected: volatile APPLICATION_STATUS m_status = APPLICATION_STATUS::UNKNOWN; - private: + mutable LONG m_cRefs; }; diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index 5ea3c030d2..13781f7c82 100644 --- a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -171,7 +171,7 @@ Language=English . Messageid=1024 -SymbolicName=ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED +SymbolicName=ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR Language=English %1 . diff --git a/src/AspNetCoreModuleV2/CommonLib/iapplication.h b/src/AspNetCoreModuleV2/CommonLib/iapplication.h index dcec7a5e3e..590fd7e51a 100644 --- a/src/AspNetCoreModuleV2/CommonLib/iapplication.h +++ b/src/AspNetCoreModuleV2/CommonLib/iapplication.h @@ -11,6 +11,7 @@ enum APPLICATION_STATUS STARTING, RUNNING, SHUTDOWN, + RECYCLED, FAIL }; diff --git a/src/AspNetCoreModuleV2/CommonLib/requesthandler.h b/src/AspNetCoreModuleV2/CommonLib/requesthandler.h index 4553ee5c87..ea4692f506 100644 --- a/src/AspNetCoreModuleV2/CommonLib/requesthandler.h +++ b/src/AspNetCoreModuleV2/CommonLib/requesthandler.h @@ -24,13 +24,26 @@ public: { DBG_ASSERT(m_cRefs != 0); - LONG cRefs = 0; - if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) + if (InterlockedDecrement(&m_cRefs) == 0) { delete this; } } + REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(DWORD cbCompletion, HRESULT hrCompletionStatus) override + { + UNREFERENCED_PARAMETER(cbCompletion); + UNREFERENCED_PARAMETER(hrCompletionStatus); + // We shouldn't get here in default implementation + DBG_ASSERT(FALSE); + return RQ_NOTIFICATION_FINISH_REQUEST; + } + + VOID TerminateRequest(bool fClientInitiated) override + { + UNREFERENCED_PARAMETER(fClientInitiated); + } + private: mutable LONG m_cRefs = 1; }; diff --git a/src/AspNetCoreModuleV2/CommonLib/resources.h b/src/AspNetCoreModuleV2/CommonLib/resources.h index 153af6d3d0..4620fa7e17 100644 --- a/src/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/AspNetCoreModuleV2/CommonLib/resources.h @@ -33,7 +33,7 @@ #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Please check the stderr logs for more information." #define ASPNETCORE_EVENT_APP_IN_SHUTDOWN_MSG L"Application shutting down." #define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' was recycled after detecting the app_offline file." -#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG L"App_offline.htm has been removed from the application. Application will be recycled." +#define ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR_MSG L"Monitoring app_offline.htm failed for application '%s', ErrorCode '0x%x'. " #define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change" #define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process." #define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled" diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp index 2f3986a0f5..5848f37a2f 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -7,11 +7,12 @@ hostfxr_main_fn InProcessApplicationBase::s_fMainCallback = NULL; InProcessApplicationBase::InProcessApplicationBase( - IHttpServer *pHttpServer) - : - m_srwLock(), - m_fRecycleCalled(FALSE), - m_pHttpServer(pHttpServer) + IHttpServer& pHttpServer, + IHttpApplication& pHttpApplication) + : AppOfflineTrackingApplication(pHttpApplication), + m_fRecycleCalled(FALSE), + m_srwLock(), + m_pHttpServer(pHttpServer) { InitializeSRWLock(&m_srwLock); } @@ -39,23 +40,19 @@ InProcessApplicationBase::Recycle( m_fRecycleCalled = true; } - if (!m_pHttpServer->IsCommandLineLaunch()) + if (!m_pHttpServer.IsCommandLineLaunch()) { // IIS scenario. // We don't actually handle any shutdown logic here. // Instead, we notify IIS that the process needs to be recycled, which will call // ApplicationManager->Shutdown(). This will call shutdown on the application. - m_pHttpServer->RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); + m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); } else { - // IISExpress scenario - // Try to graceful shutdown the managed application - // and call exit to terminate current process - ShutDown(); // If we set a static callback, we don't want to kill the current process as // that will kill the test process and means we are running in hostable webcore mode. - if (m_pHttpServer->IsCommandLineLaunch() + if (m_pHttpServer.IsCommandLineLaunch() && s_fMainCallback == NULL) { exit(0); diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h index defcf3bdf8..bb312d4a09 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -5,15 +5,17 @@ #include "precomp.hxx" #include "application.h" -#include "requesthandler_config.h" +#include "AppOfflineTrackingApplication.h" typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs -class InProcessApplicationBase : public APPLICATION +class InProcessApplicationBase : public AppOfflineTrackingApplication { public: - InProcessApplicationBase(IHttpServer* pHttpServer); + InProcessApplicationBase( + IHttpServer& pHttpServer, + IHttpApplication& pHttpApplication); ~InProcessApplicationBase() = default; @@ -22,7 +24,7 @@ public: protected: BOOL m_fRecycleCalled; SRWLOCK m_srwLock; - IHttpServer* const m_pHttpServer; + IHttpServer& m_pHttpServer; // Allows to override call to hostfxr_main with custome callback // used in testing static hostfxr_main_fn s_fMainCallback; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp index 10d6ddb4af..675c639959 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +#include "precomp.hxx" + #include "StartupExceptionApplication.h" VOID StartupExceptionApplication::ShutDown() { - exit(0); } -HRESULT StartupExceptionApplication::CreateHandler(IHttpContext *pContext, IREQUEST_HANDLER ** pRequestHandler) +HRESULT StartupExceptionApplication::CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler) { - *pRequestHandler = new StartupExceptionHandler(pContext, m_disableLogs, this); + *pRequestHandler = new StartupExceptionHandler(pHttpContext, m_disableLogs, this); return S_OK; } diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h index 52658c8cda..0a2dc4fe58 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h @@ -3,68 +3,58 @@ #pragma once -#include "precomp.hxx" #include "InProcessApplicationBase.h" #include "StartupExceptionHandler.h" -#include "SRWExclusiveLock.h" class StartupExceptionApplication : public InProcessApplicationBase { public: - StartupExceptionApplication(IHttpServer* pServer, BOOL disableLogs) + StartupExceptionApplication( + IHttpServer& pServer, + IHttpApplication& pApplication, + BOOL disableLogs) : m_disableLogs(disableLogs), - m_pHttpServer(pServer), - InProcessApplicationBase(pServer) + InProcessApplicationBase(pServer, pApplication) { - InitializeSRWLock(&m_srwLock); m_status = APPLICATION_STATUS::RUNNING; + html500Page = std::string(" \ + \ + \ + \ + IIS 500.30 Error \ +
\ +

HTTP Error 500.30 - ANCM In-Process Start Failure

\ +
\ +

Common causes of this issue:

\ +
  • The application failed to start
  • \ +
  • The application started but then stopped
  • \ +
  • The application started but threw an exception during startup
\ +
\ +
\ +

Troubleshooting steps:

\ +
  • Check the system event log for error messages
  • \ +
  • Enable logging the application process' stdout messages
  • \ +
  • Attach a debugger to the application process and inspect
\ +

For more information visit: \ + https://go.microsoft.com/fwlink/?LinkID=808681

\ +
\ +
\ +
"); } ~StartupExceptionApplication() = default; - virtual VOID ShutDown() override; - virtual HRESULT CreateHandler(IHttpContext * pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override; + VOID ShutDown() override; + HRESULT CreateHandler(IHttpContext * pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override; std::string& GetStaticHtml500Content() { - { - SRWExclusiveLock lock(m_srwLock); - if (html500Page.empty()) - { - html500Page = std::string(" \ - \ - \ - \ - IIS 500.30 Error \ -
\ -

HTTP Error 500.30 - ANCM In-Process Start Failure

\ -
\ -

Common causes of this issue:

\ -
  • The application failed to start
  • \ -
  • The application started but then stopped
  • \ -
  • The application started but threw an exception during startup
\ -
\ -
\ -

Troubleshooting steps:

\ -
  • Check the system event log for error messages
  • \ -
  • Enable logging the application process' stdout messages
  • \ -
  • Attach a debugger to the application process and inspect
\ -

For more information visit: \ - https://go.microsoft.com/fwlink/?LinkID=808681

\ -
\ -
\ -
"); - } - } - return html500Page; } private: std::string html500Page; - SRWLOCK m_srwLock; BOOL m_disableLogs; - IHttpServer* m_pHttpServer; }; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.cpp index b0817a4659..af38e7025e 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.cpp @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +#include "precomp.hxx" + +#include "StartupExceptionApplication.h" #include "StartupExceptionHandler.h" REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler() @@ -28,15 +31,5 @@ REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler() } return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; - } -REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnAsyncCompletion(DWORD , HRESULT ) -{ - return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; -} - -VOID StartupExceptionHandler::TerminateRequest(bool ) -{ - return VOID(); -} diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h index 859f3a73f5..9b5a9ba45d 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h @@ -3,21 +3,13 @@ #pragma once -#include "precomp.hxx" #include "requesthandler.h" -#include "StartupExceptionApplication.h" class StartupExceptionApplication; class StartupExceptionHandler : public REQUEST_HANDLER { public: - virtual REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override; - - virtual REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(DWORD cbCompletion, HRESULT hrCompletionStatus) override; - - virtual VOID TerminateRequest(bool fClientInitiated) override; - StartupExceptionHandler(IHttpContext* pContext, BOOL disableLogs, StartupExceptionApplication* pApplication) : m_pContext(pContext), @@ -26,9 +18,7 @@ public: { } - ~StartupExceptionHandler() - { - } + REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override; private: IHttpContext * m_pContext; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cxx b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cxx index 20314d34a4..ac1e1a0ae4 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cxx +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cxx @@ -4,7 +4,6 @@ // dllmain.cpp : Defines the entry point for the DLL application. #include "precomp.hxx" -#include #include #include "inprocessapplication.h" @@ -39,7 +38,7 @@ InitializeGlobalConfiguration( g_pHttpServer = pServer; RETURN_IF_FAILED(ALLOC_CACHE_HANDLER::StaticInitialize()); RETURN_IF_FAILED(IN_PROCESS_HANDLER::StaticInitialize()); - + if (pServer->IsCommandLineLaunch()) { g_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); @@ -89,27 +88,30 @@ CreateApplication( _Out_ IAPPLICATION **ppApplication ) { - REQUESTHANDLER_CONFIG *pConfig = NULL; try { - // Initialze some global variables here RETURN_IF_FAILED(InitializeGlobalConfiguration(pServer)); + + REQUESTHANDLER_CONFIG *pConfig = nullptr; RETURN_IF_FAILED(REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig)); + std::unique_ptr pRequestHandlerConfig(pConfig); + + BOOL disableStartupPage = pConfig->QueryDisableStartUpErrorPage(); - auto config = std::unique_ptr(pConfig); - - const bool disableStartupPage = pConfig->QueryDisableStartUpErrorPage(); - - auto pApplication = std::make_unique(pServer, std::move(config), pParameters, nParameters); - + auto pApplication = std::make_unique(*pServer, *pHttpApplication, std::move(pRequestHandlerConfig), pParameters, nParameters); + if (FAILED_LOG(pApplication->LoadManagedApplication())) { // Set the currently running application to a fake application that returns startup exceptions. - *ppApplication = new StartupExceptionApplication(pServer, disableStartupPage); + auto pErrorApplication = std::make_unique(*pServer, *pHttpApplication, disableStartupPage); + + RETURN_IF_FAILED(pErrorApplication->StartMonitoringAppOffline()); + *ppApplication = pErrorApplication.release(); } else { + RETURN_IF_FAILED(pApplication->StartMonitoringAppOffline()); *ppApplication = pApplication.release(); } } diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index a1cf301054..7714d9a9e4 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +#include "precomp.hxx" #include "inprocessapplication.h" #include "inprocesshandler.h" #include "hostfxroptions.h" @@ -18,17 +19,17 @@ const LPCSTR IN_PROCESS_APPLICATION::s_exeLocationParameterName = "InProcessExeL IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( - IHttpServer *pHttpServer, + IHttpServer& pHttpServer, + IHttpApplication& pApplication, std::unique_ptr pConfig, APPLICATION_PARAMETER *pParameters, DWORD nParameters) : - InProcessApplicationBase(pHttpServer), + InProcessApplicationBase(pHttpServer, pApplication), m_pHttpServer(pHttpServer), m_ProcessExitCode(0), m_fBlockCallbacksIntoManaged(FALSE), m_fShutdownCalledFromNative(FALSE), m_fShutdownCalledFromManaged(FALSE), - m_fInitialized(FALSE), m_pConfig(std::move(pConfig)) { // is it guaranteed that we have already checked app offline at this point? @@ -664,14 +665,6 @@ IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hos return hr; } -// static - -REQUESTHANDLER_CONFIG* -IN_PROCESS_APPLICATION::QueryConfig() const -{ - return m_pConfig.get(); -} - HRESULT IN_PROCESS_APPLICATION::CreateHandler( _In_ IHttpContext *pHttpContext, diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 034c36fc93..0bcd1cbd48 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -3,7 +3,6 @@ #pragma once -#include "precomp.hxx" #include "InProcessApplicationBase.h" #include "inprocesshandler.h" #include "requesthandler_config.h" @@ -17,7 +16,8 @@ class IN_PROCESS_APPLICATION : public InProcessApplicationBase { public: IN_PROCESS_APPLICATION( - IHttpServer* pHttpServer, + IHttpServer& pHttpServer, + IHttpApplication& pApplication, std::unique_ptr pConfig, APPLICATION_PARAMETER *pParameters, DWORD nParameters); @@ -105,17 +105,21 @@ public: return s_Application; } - REQUESTHANDLER_CONFIG* - QueryConfig() const; - PCWSTR QueryExeLocation() { return m_struExeLocation.QueryStr(); } + REQUESTHANDLER_CONFIG* + QueryConfig() + { + return m_pConfig.get(); + } + private: - IHttpServer* const m_pHttpServer; + + IHttpServer & m_pHttpServer; // Thread executing the .NET Core process HANDLE m_hThread; @@ -144,12 +148,11 @@ private: volatile BOOL m_fShutdownCalledFromManaged; BOOL m_fRecycleCalled; BOOL m_fInitialized; - + std::unique_ptr m_pConfig; static IN_PROCESS_APPLICATION* s_Application; IOutputManager* m_pLoggerProvider; - std::unique_ptr m_pConfig; static const LPCSTR s_exeLocationParameterName; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp index 5daf8d3604..1494eb158e 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +#include "precomp.hxx" #include "inprocesshandler.h" #include "inprocessapplication.h" #include "aspnetcore_event.h" @@ -12,9 +13,10 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER( _In_ IHttpContext *pW3Context, _In_ IN_PROCESS_APPLICATION *pApplication ): m_pW3Context(pW3Context), - m_pApplication(pApplication) + m_requestNotificationStatus(RQ_NOTIFICATION_PENDING), + m_pApplication(pApplication), + m_fManagedRequestComplete(FALSE) { - m_fManagedRequestComplete = FALSE; } IN_PROCESS_HANDLER::~IN_PROCESS_HANDLER() diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h index 24a29b737f..a9c068a751 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h @@ -3,7 +3,6 @@ #pragma once -#include "precomp.hxx" #include "requesthandler.h" class IN_PROCESS_APPLICATION; diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx index c74f3cacaa..0855ae6566 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx @@ -148,7 +148,7 @@ EnsureOutOfProcessInitializtion() g_hWinHttpModule = GetModuleHandle(TEXT("winhttp.dll")); - g_hAspNetCoreModule = GetModuleHandle(TEXT("aspnetcore.dll")); + g_hAspNetCoreModule = GetModuleHandle(TEXT("aspnetcorev2.dll")); hr = WINHTTP_HELPER::StaticInitialize(); if (FAILED(hr)) @@ -279,42 +279,20 @@ CreateApplication( { UNREFERENCED_PARAMETER(pParameters); UNREFERENCED_PARAMETER(nParameters); - HRESULT hr = S_OK; - IAPPLICATION *pApplication = NULL; - REQUESTHANDLER_CONFIG *pConfig = NULL; - - // Initialze some global variables here + InitializeGlobalConfiguration(pServer); - hr = REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig); - if (FAILED(hr)) - { - return hr; - } + REQUESTHANDLER_CONFIG *pConfig = nullptr; + RETURN_IF_FAILED(REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig)); + std::unique_ptr pRequestHandlerConfig(pConfig); - hr = EnsureOutOfProcessInitializtion(); - if (FAILED(hr)) - { - goto Finished; - } + RETURN_IF_FAILED(EnsureOutOfProcessInitializtion()); - pApplication = new OUT_OF_PROCESS_APPLICATION(pConfig); - if (pApplication == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); - goto Finished; - } + std::unique_ptr pApplication = std::make_unique(*pHttpApplication, std::move(pRequestHandlerConfig)); - hr = ((OUT_OF_PROCESS_APPLICATION*)pApplication)->Initialize(); - if (FAILED(hr)) - { - delete pApplication; - pApplication = NULL; - goto Finished; - } + RETURN_IF_FAILED(pApplication->Initialize()); + RETURN_IF_FAILED(pApplication->StartMonitoringAppOffline()); - *ppApplication = pApplication; - -Finished: - return hr; + *ppApplication = pApplication.release(); + return S_OK; } diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.cpp b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.cpp index 0457b4d552..ce29fd4745 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.cpp @@ -1,9 +1,12 @@ #include "..\precomp.hxx" +#include "SRWExclusiveLock.h" OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( - REQUESTHANDLER_CONFIG *pConfig) : + IHttpApplication& pApplication, + std::unique_ptr pConfig) : + AppOfflineTrackingApplication(pApplication), m_fWebSocketSupported(WEBSOCKET_STATUS::WEBSOCKET_UNKNOWN), - m_pConfig(pConfig) + m_pConfig(std::move(pConfig)) { m_status = APPLICATION_STATUS::RUNNING; m_pProcessManager = NULL; @@ -12,18 +15,13 @@ OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() { + SRWExclusiveLock lock(m_srwLock); if (m_pProcessManager != NULL) { - m_pProcessManager->ShutdownAllProcesses(); + m_pProcessManager->Shutdown(); m_pProcessManager->DereferenceProcessManager(); m_pProcessManager = NULL; } - - if (m_pConfig != NULL) - { - delete m_pConfig; - m_pConfig = NULL; - } } HRESULT @@ -56,29 +54,18 @@ OUT_OF_PROCESS_APPLICATION::GetProcess( _Out_ SERVER_PROCESS **ppServerProcess ) { - return m_pProcessManager->GetProcess(m_pConfig, QueryWebsocketStatus(), ppServerProcess); -} - -REQUESTHANDLER_CONFIG* -OUT_OF_PROCESS_APPLICATION::QueryConfig() const -{ - return m_pConfig; + return m_pProcessManager->GetProcess(m_pConfig.get(), QueryWebsocketStatus(), ppServerProcess); } __override VOID OUT_OF_PROCESS_APPLICATION::ShutDown() -{ - AcquireSRWLockExclusive(&m_srwLock); +{ + SRWExclusiveLock lock(m_srwLock); + if (m_pProcessManager != NULL) { - if (m_pProcessManager != NULL) - { - m_pProcessManager->ShutdownAllProcesses(); - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } + m_pProcessManager->Shutdown(); } - ReleaseSRWLockExclusive(&m_srwLock); } __override diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.h index 9601178e63..8f51bf4216 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.h +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/outprocessapplication.h @@ -3,7 +3,9 @@ #pragma once -class OUT_OF_PROCESS_APPLICATION : public APPLICATION +#include "AppOfflineTrackingApplication.h" + +class OUT_OF_PROCESS_APPLICATION : public AppOfflineTrackingApplication { enum WEBSOCKET_STATUS { @@ -14,7 +16,8 @@ class OUT_OF_PROCESS_APPLICATION : public APPLICATION public: OUT_OF_PROCESS_APPLICATION( - REQUESTHANDLER_CONFIG *pConfig); + IHttpApplication& pApplication, + std::unique_ptr pConfig); __override ~OUT_OF_PROCESS_APPLICATION() override; @@ -44,14 +47,15 @@ public: _Out_ IREQUEST_HANDLER **pRequestHandler) override; - REQUESTHANDLER_CONFIG* - QueryConfig() - const; - BOOL QueryWebsocketStatus() const; + REQUESTHANDLER_CONFIG* QueryConfig() + { + return m_pConfig.get(); + } + private: VOID SetWebsocketStatus(IHttpContext *pHttpContext); @@ -60,6 +64,6 @@ private: SRWLOCK m_srwLock; IHttpServer *m_pHttpServer; - REQUESTHANDLER_CONFIG* m_pConfig; WEBSOCKET_STATUS m_fWebSocketSupported; + std::unique_ptr m_pConfig; }; diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.cxx b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.cxx index 7c75d65a0d..85160dc55b 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.cxx +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.cxx @@ -114,6 +114,11 @@ PROCESS_MANAGER::GetProcess( DWORD dwProcessIndex = 0; SERVER_PROCESS *pSelectedServerProcess = NULL; + if (InterlockedCompareExchange(&m_lStopping, 1L, 1L) == 1L) + { + return hr = E_APPLICATION_EXITING; + } + if (!m_fServerProcessListReady) { AcquireSRWLockExclusive(&m_srwLock); diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.h index b15da72486..764e77d595 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.h +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocess/processmanager.h @@ -88,6 +88,16 @@ public: ReleaseSRWLockExclusive( &m_srwLock ); } + VOID + Shutdown( + ) + { + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + ShutdownAllProcesses(); + } + } + VOID IncrementRapidFailCount( VOID @@ -103,6 +113,7 @@ public: m_dwProcessesPerApplication( 1 ), m_dwRouteToProcessIndex( 0 ), m_fServerProcessListReady(FALSE), + m_lStopping(0), m_cRefs( 1 ) { m_ppServerProcessList = NULL; @@ -193,4 +204,5 @@ private: volatile static BOOL sm_fWSAStartupDone; volatile BOOL m_fServerProcessListReady; + volatile LONG m_lStopping; }; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp new file mode 100644 index 0000000000..7c335ceff1 --- /dev/null +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "AppOfflineTrackingApplication.h" +#include "EventLog.h" +#include "exceptions.h" + +extern HANDLE g_hEventLog; + +HRESULT AppOfflineTrackingApplication::StartMonitoringAppOffline() +{ + LOG_INFOF("Starting app_offline monitoring in application %S", m_applicationPath.c_str()); + HRESULT hr = StartMonitoringAppOflineImpl(); + + if (FAILED_LOG(hr)) + { + UTILITY::LogEventF(g_hEventLog, + EVENTLOG_WARNING_TYPE, + ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR, + ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR_MSG, + m_applicationPath.c_str(), + hr); + } + + return hr; +} + +HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() +{ + if (m_fileWatcher) + { + RETURN_IF_FAILED(E_UNEXPECTED); + } + + 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(), + L"app_offline.htm", + std::bind(&AppOfflineTrackingApplication::OnAppOffline, this), + NULL)); + + return S_OK; +} + +void AppOfflineTrackingApplication::OnAppOffline() +{ + LOG_INFOF("Received app_offline notification in application %S", m_applicationPath.c_str()); + m_fileWatcherEntry->StopMonitor(); + m_status = APPLICATION_STATUS::RECYCLED; + UTILITY::LogEventF(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, + m_applicationPath.c_str()); + + Recycle(); +} diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h new file mode 100644 index 0000000000..776ae9913c --- /dev/null +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include +#include "application.h" +#include "filewatcher.h" + +class AppOfflineTrackingApplication: public APPLICATION +{ +public: + AppOfflineTrackingApplication(const IHttpApplication& application) + : m_applicationPath(application.GetApplicationPhysicalPath()), + m_fileWatcher(nullptr), + m_fileWatcherEntry(nullptr) + { + } + + ~AppOfflineTrackingApplication() override = default; + + HRESULT + StartMonitoringAppOffline(); + + virtual + VOID + OnAppOffline(); + +private: + HRESULT + StartMonitoringAppOflineImpl(); + + std::wstring m_applicationPath; + std::unique_ptr m_fileWatcher; + std::unique_ptr m_fileWatcherEntry; +}; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj b/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj index 844328640e..8b1b24631e 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj @@ -1,4 +1,4 @@ - + @@ -188,7 +188,9 @@ + + @@ -199,7 +201,9 @@ + + diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp similarity index 97% rename from src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx rename to src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 4bb215b4ed..a35d8cb63c 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -1,11 +1,9 @@ // 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 "ntassert.h" -#include "applicationinfo.h" - FILE_WATCHER::FILE_WATCHER() : m_hCompletionPort(NULL), m_hChangeNotificationThread(NULL), @@ -214,7 +212,7 @@ FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : _pFileMonitor(pFileMonitor), _hDirectory(INVALID_HANDLE_VALUE), _hImpersonationToken(NULL), - _pApplicationInfo(NULL), + _pCallback(), _lStopMonitorCalled(0), _cRefs(1), _fIsValid(TRUE) @@ -225,6 +223,8 @@ FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() { + StopMonitor(); + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; if (_hDirectory != INVALID_HANDLE_VALUE) @@ -330,16 +330,16 @@ HRESULT } } +Finished: + ReleaseSRWLockExclusive(&_srwLock); + if (fFileChanged) { // // so far we only monitoring app_offline // - _pApplicationInfo->UpdateAppOfflineFileHandle(); + _pCallback(); } - -Finished: - ReleaseSRWLockExclusive(&_srwLock); return hr; } @@ -399,7 +399,7 @@ HRESULT FILE_WATCHER_ENTRY::Create( _In_ PCWSTR pszDirectoryToMonitor, _In_ PCWSTR pszFileNameToMonitor, - _In_ APPLICATION_INFO* pApplicationInfo, + _In_ std::function pCallback, _In_ HANDLE hImpersonationToken ) { @@ -408,17 +408,14 @@ FILE_WATCHER_ENTRY::Create( if (pszDirectoryToMonitor == NULL || pszFileNameToMonitor == NULL || - pApplicationInfo == NULL) + pCallback == NULL) { DBG_ASSERT(FALSE); hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto Finished; } - // - //remember the application - // - _pApplicationInfo = pApplicationInfo; + _pCallback = pCallback; if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) { diff --git a/src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h similarity index 88% rename from src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h rename to src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index 8bcd746326..c010caefc0 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -1,20 +1,18 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. + #pragma once -#include - -#include "stringu.h" +#include +#include #define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) #define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 -#define FILE_NOTIFY_VALID_MASK 0x00000fff +#define FILE_NOTIFY_VALID_MASK 0x00000fff #define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') #define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') -class APPLICATION_INFO; - class FILE_WATCHER{ public: @@ -62,7 +60,7 @@ public: Create( _In_ PCWSTR pszDirectoryToMonitor, _In_ PCWSTR pszFileNameToMonitor, - _In_ APPLICATION_INFO* pApplicationInfo, + _In_ std::function pCallback, _In_ HANDLE hImpersonationToken ); @@ -111,11 +109,20 @@ private: HANDLE _hImpersonationToken; HANDLE _hDirectory; FILE_WATCHER* _pFileMonitor; - APPLICATION_INFO* _pApplicationInfo; STRU _strFileName; STRU _strDirectoryName; 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(); + } }; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h b/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h index a7369ca0e7..5ba8959317 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h @@ -54,7 +54,8 @@ enum APP_HOSTING_MODEL class REQUESTHANDLER_CONFIG { public: - virtual + + ~REQUESTHANDLER_CONFIG(); static @@ -217,7 +218,6 @@ protected: REQUESTHANDLER_CONFIG() : m_fStdoutLogEnabled(FALSE), m_pEnvironmentVariables(NULL), - m_cRefs(1), m_hostingModel(HOSTING_UNKNOWN), m_ppStrArguments(NULL) { @@ -229,8 +229,6 @@ protected: IHttpApplication *pHttpApplication ); - mutable LONG m_cRefs; - DWORD m_dwRequestTimeoutInMS; DWORD m_dwStartupTimeLimitInMS; DWORD m_dwShutdownTimeLimitInMS; @@ -254,4 +252,5 @@ protected: STRU m_struHostFxrLocation; PWSTR* m_ppStrArguments; DWORD m_dwArgc; + }; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h b/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h index 96c3ffe6de..fd6382710b 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h @@ -24,5 +24,3 @@ #include "dbgutil.h" #include "ahutil.h" #include "hashfn.h" - - diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index 6915d955ec..f4114730b3 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -53,15 +53,20 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } // Defaults to inprocess specific deployment parameters - public static DeploymentParameters GetBaseDeploymentParameters(string site = "InProcessWebSite") + public static DeploymentParameters GetBaseDeploymentParameters(string site = null, HostingModel hostingModel = HostingModel.InProcess, bool publish = false) { + if (site == null) + { + site = hostingModel == HostingModel.InProcess ? "InProcessWebSite" : "OutOfProcessWebSite"; + } + return new DeploymentParameters(Helpers.GetTestWebSitePath(site), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { TargetFramework = Tfm.NetCoreApp22, ApplicationType = ApplicationType.Portable, AncmVersion = AncmVersion.AspNetCoreModuleV2, - HostingModel = HostingModel.InProcess, - PublishApplicationBeforeDeployment = site == "InProcessWebSite", + HostingModel = hostingModel, + PublishApplicationBeforeDeployment = publish, }; } diff --git a/test/CommonLibTests/fakeclasses.h b/test/CommonLibTests/fakeclasses.h index 14c6f8dfee..ba792017c4 100644 --- a/test/CommonLibTests/fakeclasses.h +++ b/test/CommonLibTests/fakeclasses.h @@ -41,7 +41,6 @@ public: class MockElement : public IAppHostElement { public: - MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); @@ -167,6 +166,16 @@ class MockHttpServer : public IHttpServer } }; + +class MockHttpApplication: public IHttpApplication +{ +public: + MOCK_CONST_METHOD0(GetApplicationPhysicalPath, PCWSTR ()); + MOCK_CONST_METHOD0(GetApplicationId, PCWSTR ()); + MOCK_CONST_METHOD0(GetAppConfigPath, PCWSTR ()); + MOCK_METHOD0(GetModuleContextContainer, IHttpModuleContextContainer* ()); +}; + class MockRequestHandlerConfig : public REQUESTHANDLER_CONFIG { public: diff --git a/test/CommonLibTests/inprocess_application_tests.cpp b/test/CommonLibTests/inprocess_application_tests.cpp index 3f1f67e457..00c7b01611 100644 --- a/test/CommonLibTests/inprocess_application_tests.cpp +++ b/test/CommonLibTests/inprocess_application_tests.cpp @@ -7,6 +7,9 @@ #include "inprocessapplication.h" #include "fakeclasses.h" +using ::testing::_; +using ::testing::NiceMock; + // Externals defined in inprocess BOOL g_fProcessDetach; HANDLE g_hEventLog; @@ -15,16 +18,22 @@ namespace InprocessTests { TEST(InProcessTest, NoNullRefForExePath) { - auto server = new MockHttpServer(); - auto requestHandlerConfig = MockRequestHandlerConfig::CreateConfig(); - auto config = std::unique_ptr(requestHandlerConfig); + MockHttpServer server; + NiceMock application; + + ON_CALL(application, GetApplicationPhysicalPath()) + .WillByDefault(testing::Return(L"Some path")); + + auto requestHandlerConfig = std::unique_ptr(MockRequestHandlerConfig::CreateConfig()); std::wstring exePath(L"hello"); - std::array parameters { + + std::array parameters{ {"InProcessExeLocation", exePath.data()} }; - IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, std::move(config), parameters.data(), 1); + IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), parameters.data(), 1); + ASSERT_STREQ(app->QueryExeLocation(), L"hello"); } } diff --git a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs index 5cfd7fe7c0..87741af201 100644 --- a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs @@ -19,21 +19,25 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess public class AppOfflineTests : IISFunctionalTestBase { // TODO these will differ between IIS and IISExpress - [ConditionalFact] - public async Task AppOfflineDroppedWhileSiteIsDown_SiteReturns503() + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDroppedWhileSiteIsDown_SiteReturns503(HostingModel hostingModel) { - var deploymentResult = await DeployApp(); + var deploymentResult = await DeployApp(hostingModel); AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); await AssertAppOffline(deploymentResult); } - [ConditionalFact] - public async Task AppOfflineDroppedWhileSiteIsDown_CustomResponse() + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDroppedWhileSiteIsDown_CustomResponse(HostingModel hostingModel) { var expectedResponse = "The app is offline."; - var deploymentResult = await DeployApp(); + var deploymentResult = await DeployApp(hostingModel); AddAppOffline(deploymentResult.DeploymentResult.ContentRoot, expectedResponse); @@ -41,9 +45,9 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess } [ConditionalFact] - public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown() + public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_InProcess() { - var deploymentResult = await AssertStarts(); + var deploymentResult = await AssertStarts(HostingModel.InProcess); AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); @@ -51,9 +55,26 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess } [ConditionalFact] - public async Task AppOfflineDropped_CanRemoveAppOfflineAfterAddingAndSiteWorks() + public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_OutOfProcess() { - var deploymentResult = await DeployApp(); + var deploymentResult = await AssertStarts(HostingModel.OutOfProcess); + + // Repeat dropping file and restarting multiple times + for (int i = 0; i < 5; i++) + { + AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); + await AssertAppOffline(deploymentResult); + RemoveAppOffline(deploymentResult.DeploymentResult.ContentRoot); + await AssertRunning(deploymentResult); + } + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDropped_CanRemoveAppOfflineAfterAddingAndSiteWorks(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); @@ -61,13 +82,12 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess RemoveAppOffline(deploymentResult.DeploymentResult.ContentRoot); - var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); - + await AssertRunning(deploymentResult); } - private async Task DeployApp() + private async Task DeployApp(HostingModel hostingModel = HostingModel.InProcess) { - var deploymentParameters = Helpers.GetBaseDeploymentParameters(); + var deploymentParameters = Helpers.GetBaseDeploymentParameters(hostingModel: hostingModel, publish: true); return await DeployAsync(deploymentParameters); } @@ -118,18 +138,21 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess Assert.True(hostShutdownToken.IsCancellationRequested); } - private async Task AssertStarts() + private async Task AssertStarts(HostingModel hostingModel) { - var deploymentParameters = Helpers.GetBaseDeploymentParameters(); + var deploymentResult = await DeployApp(hostingModel); - var deploymentResult = await DeployAsync(deploymentParameters); + await AssertRunning(deploymentResult); + return deploymentResult; + } + + private static async Task AssertRunning(IISDeploymentResult deploymentResult) + { var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); var responseText = await response.Content.ReadAsStringAsync(); Assert.Equal("Hello World", responseText); - - return deploymentResult; } } } diff --git a/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs b/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs index c3540d51ee..31179b1ecf 100644 --- a/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalFact] public async Task Authentication_InProcess() { - var deploymentParameters = Helpers.GetBaseDeploymentParameters(); + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); deploymentParameters.ServerConfigTemplateContent = GetWindowsAuthConfig(); var deploymentResult = await DeployAsync(deploymentParameters); diff --git a/test/IISExpress.FunctionalTests/InProcess/LoggingTests.cs b/test/IISExpress.FunctionalTests/InProcess/LoggingTests.cs index 856bf1669f..82ea09de64 100644 --- a/test/IISExpress.FunctionalTests/InProcess/LoggingTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/LoggingTests.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var tempFile = Path.GetTempFileName(); try { - var deploymentParameters = Helpers.GetBaseDeploymentParameters(); + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"] = tempFile; var deploymentResult = await DeployAsync(deploymentParameters); diff --git a/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs b/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs index 69eacfbc43..d43894a2c3 100644 --- a/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalFact] public async Task ServerShutsDownWhenMainExits() { - var parameters = Helpers.GetBaseDeploymentParameters(); + var parameters = Helpers.GetBaseDeploymentParameters(publish: true); var result = await DeployAsync(parameters); var response = await result.RetryingHttpClient.GetAsync("/Shutdown");