diff --git a/build/testsite.props b/build/testsite.props index 42e3cb1d8e..ab74d58d72 100644 --- a/build/testsite.props +++ b/build/testsite.props @@ -40,7 +40,7 @@ - /config:"$(IISExpressAppHostConfig)" + /config:"$(IISExpressAppHostConfig)" /systray:false -h "$(IISAppHostConfig)" $(NativePlatform)\aspnetcore.dll 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..3d4b901755 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,33 +82,22 @@ 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. // Otherwise memory leak // - VOID - ExtractApplication(IAPPLICATION** ppApplication) + std::unique_ptr + ExtractApplication() const { SRWSharedLock lock(m_srwLock); - if (m_pApplication != NULL) - { - m_pApplication->ReferenceApplication(); - } - *ppApplication = m_pApplication; + return ReferenceApplication(m_pApplication); } VOID @@ -152,11 +120,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/Inc/proxymodule.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h index a0580b39b1..5864dd0c6f 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h +++ b/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h @@ -3,6 +3,7 @@ #pragma once +#include #include "applicationinfo.h" #include "irequesthandler.h" @@ -50,7 +51,7 @@ class ASPNET_CORE_PROXY_MODULE : public CHttpModule APPLICATION_INFO *m_pApplicationInfo; IAPPLICATION *m_pApplication; - IREQUEST_HANDLER *m_pHandler; + std::unique_ptr m_pHandler; }; class ASPNET_CORE_PROXY_MODULE_FACTORY : public IHttpModuleFactory diff --git a/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc b/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc index 39edfaffc4..f9acf89035 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc +++ b/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc @@ -10,7 +10,7 @@ #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#define FileDescription "IIS AspNetCore Module. Commit: " CommitHash +#define FileDescription "IIS AspNetCore Module V2. Commit: " CommitHash ///////////////////////////////////////////////////////////////////////////// // diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp index 99251ddc4a..2072e33af6 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: @@ -426,7 +328,7 @@ APPLICATION_INFO::FindNativeAssemblyFromHostfxr( DWORD dwBufferSize = 1024 * 10; DWORD dwRequiredBufferSize = 0; - DBG_ASSERT(struFileName != NULL); + DBG_ASSERT(struFilename != NULL); FINISHED_LAST_ERROR_IF_NULL(hmHostFxrDll = LoadLibraryW(hostfxrOptions->GetHostFxrLocation())); 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/dllmain.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp index 34d96e45d3..03196a316d 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp @@ -11,7 +11,7 @@ #include "resources.h" #include "exceptions.h" -DECLARE_DEBUG_PRINT_OBJECT("aspnetcore.dll"); +DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2.dll"); HTTP_MODULE_ID g_pModuleId = NULL; IHttpServer * g_pHttpServer = NULL; @@ -120,7 +120,7 @@ HRESULT // check whether the feature is disabled due to security reason if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", 0, KEY_READ, &hKey) == NO_ERROR) diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx b/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx index 6987863616..f27a1571c3 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx +++ b/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx @@ -48,7 +48,7 @@ Return value: } ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE( -) : m_pApplicationInfo(NULL), m_pHandler(NULL) +) : m_pApplicationInfo(nullptr), m_pApplication(nullptr), m_pHandler(nullptr) { } @@ -59,12 +59,6 @@ ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE() m_pApplicationInfo->DereferenceApplicationInfo(); m_pApplicationInfo = NULL; } - - if (m_pHandler != NULL) - { - m_pHandler->DereferenceRequestHandler(); - m_pHandler = NULL; - } } __override @@ -77,93 +71,48 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( HRESULT hr = S_OK; APPLICATION_MANAGER *pApplicationManager = NULL; REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; - IAPPLICATION* pApplication = NULL; STRU struExeLocation; try { if (g_fInShutdown) { - hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); - goto Finished; + FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); } pApplicationManager = APPLICATION_MANAGER::GetInstance(); - hr = pApplicationManager->GetOrCreateApplicationInfo( + FINISHED_IF_FAILED(pApplicationManager->GetOrCreateApplicationInfo( g_pHttpServer, pHttpContext, - &m_pApplicationInfo); - if (FAILED(hr)) - { - goto Finished; - } + &m_pApplicationInfo)); if (!m_pApplicationInfo->IsValid()) { // Application cannot be started due to wrong hosting mode // the error should already been logged to window event log for the first request - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - 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; + FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } // make sure assmebly is loaded and application is created - hr = m_pApplicationInfo->EnsureApplicationCreated(pHttpContext); - if (FAILED(hr)) - { - goto Finished; - } + FINISHED_IF_FAILED(m_pApplicationInfo->EnsureApplicationCreated(pHttpContext)); - m_pApplicationInfo->ExtractApplication(&pApplication); + auto pApplication = m_pApplicationInfo->ExtractApplication(); - // 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); - if (FAILED(hr)) - { - goto Finished; - } + IREQUEST_HANDLER* pHandler; + // Create RequestHandler and process the request + FINISHED_IF_FAILED(pApplication->CreateHandler(pHttpContext, &pHandler)); + m_pHandler.reset(pHandler); retVal = m_pHandler->OnExecuteRequestHandler(); } @@ -186,10 +135,6 @@ Finished: } } - if (pApplication != NULL) - { - pApplication->DereferenceApplication(); - } return retVal; } 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/debugutil.cpp b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp index 189a5097f9..cd8ca3ea05 100644 --- a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp @@ -23,7 +23,7 @@ DebugInitialize() HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", 0, KEY_READ, &hKey) == NO_ERROR) diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index cf703d34a0..fd3a65abf8 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -225,8 +225,8 @@ HOSTFXR_UTILITY::ParseHostfxrArguments( { UNREFERENCED_PARAMETER(hEventLog); // TODO use event log to set errors. - DBG_ASSERT(dwArgCount != NULL); - DBG_ASSERT(pwzArgv != NULL); + DBG_ASSERT(pdwArgCount != NULL); + DBG_ASSERT(pbstrArgv != NULL); DBG_ASSERT(pwzExePath != NULL); HRESULT hr = S_OK; @@ -324,7 +324,6 @@ Finished: if (pwzArgs != NULL) { LocalFree(pwzArgs); - DBG_ASSERT(pwzArgs == NULL); } return hr; } diff --git a/src/AspNetCoreModuleV2/CommonLib/iapplication.h b/src/AspNetCoreModuleV2/CommonLib/iapplication.h index dcec7a5e3e..ef5d2bf8d7 100644 --- a/src/AspNetCoreModuleV2/CommonLib/iapplication.h +++ b/src/AspNetCoreModuleV2/CommonLib/iapplication.h @@ -3,6 +3,7 @@ #pragma once +#include #include "irequesthandler.h" enum APPLICATION_STATUS @@ -11,6 +12,7 @@ enum APPLICATION_STATUS STARTING, RUNNING, SHUTDOWN, + RECYCLED, FAIL }; @@ -53,3 +55,18 @@ public: _In_ IHttpContext *pHttpContext, _Out_ IREQUEST_HANDLER **pRequestHandler) = 0; }; + +struct IAPPLICATION_DELETER +{ + void operator ()(IAPPLICATION* application) const + { + application->DereferenceApplication(); + } +}; + +template< class APPLICATION > +std::unique_ptr ReferenceApplication(APPLICATION* application) +{ + application->ReferenceApplication(); + return std::unique_ptr(application); +}; diff --git a/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h b/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h index b2be7ee401..5f9b415794 100644 --- a/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h +++ b/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h @@ -4,6 +4,7 @@ #pragma once #include +#include // // Pure abstract class @@ -46,3 +47,12 @@ public: VOID ) = 0; }; + + +struct IREQUEST_HANDLER_DELETER +{ + void operator ()(IREQUEST_HANDLER* application) const + { + application->DereferenceRequestHandler(); + } +}; 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/IISLib/ntassert.h b/src/AspNetCoreModuleV2/IISLib/ntassert.h index 6d2f3b9a30..8e19e03239 100644 --- a/src/AspNetCoreModuleV2/IISLib/ntassert.h +++ b/src/AspNetCoreModuleV2/IISLib/ntassert.h @@ -11,6 +11,10 @@ #undef ASSERT #endif +#ifdef DEBUG + #define DBG 1 +#endif + #if defined( DBG ) && DBG #define SX_ASSERT( _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L#_x ), DbgRaiseAssertionFailure(), FALSE ) ) ) #define SX_ASSERTMSG( _m, _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L##_m ), DbgRaiseAssertionFailure(), FALSE ) ) ) 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..ca907d67c6 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -3,17 +3,18 @@ #pragma once -#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 +23,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/InProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj index 907726e2b2..c96aaa7000 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj @@ -83,7 +83,7 @@ - NotUsing + Use Level4 Disabled WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) @@ -104,17 +104,20 @@ CompileAsCpp true stdcpp17 + precomp.hxx + true Windows true kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) Source.def + UseLinkTimeCodeGeneration - NotUsing + Use Level4 Disabled WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) @@ -135,18 +138,21 @@ CompileAsCpp true stdcpp17 + precomp.hxx + true Windows true kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) Source.def + UseLinkTimeCodeGeneration Level4 - NotUsing + Use MaxSpeed true true @@ -166,6 +172,8 @@ CompileAsCpp true stdcpp17 + precomp.hxx + true Windows @@ -174,12 +182,13 @@ true Source.def kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration Level4 - NotUsing + Use MaxSpeed true true @@ -199,6 +208,8 @@ CompileAsCpp true stdcpp17 + precomp.hxx + true Windows @@ -207,6 +218,7 @@ true Source.def kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration @@ -224,6 +236,12 @@ + + Create + Create + Create + Create + diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp index 10d6ddb4af..4aa5b9fb31 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp @@ -5,11 +5,10 @@ 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..f8041a0f13 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +#include "StartupExceptionApplication.h" #include "StartupExceptionHandler.h" REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler() @@ -28,15 +29,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..e1bc9ac143 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cxx +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cxx @@ -3,8 +3,6 @@ // dllmain.cpp : Defines the entry point for the DLL application. -#include "precomp.hxx" -#include #include #include "inprocessapplication.h" @@ -39,7 +37,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 +87,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..ad0f6a35a6 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -6,7 +6,6 @@ #include "hostfxroptions.h" #include "requesthandler_config.h" #include "environmentvariablehelpers.h" -#include "aspnetcore_event.h" #include "utility.h" #include "EventLog.h" #include "SRWExclusiveLock.h" @@ -18,23 +17,20 @@ 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? - // If so, I don't think there is much to do here. - DBG_ASSERT(pHttpServer != NULL); - DBG_ASSERT(pConfig != NULL); + DBG_ASSERT(m_pConfig); for (DWORD i = 0; i < nParameters; i++) { @@ -207,98 +203,11 @@ IN_PROCESS_APPLICATION::ShutDownInternal() s_Application = NULL; } -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_APPLICATION::OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus, - IN_PROCESS_HANDLER* pInProcessHandler -) -{ - REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; - - ReferenceApplication(); - - if (pInProcessHandler->QueryIsManagedRequestComplete()) - { - // means PostCompletion has been called and this is the associated callback. - dwRequestNotificationStatus = pInProcessHandler->QueryAsyncCompletionStatus(); - } - else if (m_fBlockCallbacksIntoManaged) - { - // this can potentially happen in ungraceful shutdown. - // Or something really wrong happening with async completions - // At this point, managed is in a shutting down state and we cannot send a request to it. - pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(503, - "Server has been shutdown", - 0, - (ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); - dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST; - } - else - { - // Call the managed handler for async completion. - dwRequestNotificationStatus = m_AsyncCompletionHandler(pInProcessHandler->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); - } - - DereferenceApplication(); - - return dwRequestNotificationStatus; -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_APPLICATION::OnExecuteRequest( - _In_ IHttpContext* pHttpContext, - _In_ IN_PROCESS_HANDLER* pInProcessHandler -) -{ - REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; - PFN_REQUEST_HANDLER pRequestHandler = NULL; - - ReferenceApplication(); - pRequestHandler = m_RequestHandler; - - if (pRequestHandler == NULL) - { - // - // return error as the application did not register callback - // - if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(pHttpContext->GetTraceContext())) - { - ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(pHttpContext->GetTraceContext(), - NULL, - (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); - } - - pHttpContext->GetResponse()->SetStatus(500, - "Internal Server Error", - 0, - (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); - - dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST; - } - else if (m_status != APPLICATION_STATUS::RUNNING || m_fBlockCallbacksIntoManaged) - { - pHttpContext->GetResponse()->SetStatus(503, - "Server is currently shutting down.", - 0, - (ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); - dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST; - } - else - { - dwRequestNotificationStatus = pRequestHandler(pInProcessHandler, m_RequestHandlerContext); - } - - DereferenceApplication(); - - return dwRequestNotificationStatus; -} - VOID IN_PROCESS_APPLICATION::SetCallbackHandles( _In_ PFN_REQUEST_HANDLER request_handler, _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, - _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, + _In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler, _In_ VOID* pvRequstHandlerContext, _In_ VOID* pvShutdownHandlerContext ) @@ -664,14 +573,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, @@ -680,7 +581,7 @@ IN_PROCESS_APPLICATION::CreateHandler( HRESULT hr = S_OK; IREQUEST_HANDLER* pHandler = NULL; - pHandler = new IN_PROCESS_HANDLER(pHttpContext, this); + pHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler); if (pHandler == NULL) { diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 034c36fc93..1f6e3da570 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -3,21 +3,21 @@ #pragma once -#include "precomp.hxx" #include "InProcessApplicationBase.h" -#include "inprocesshandler.h" #include "requesthandler_config.h" #include "IOutputManager.h" +class IN_PROCESS_HANDLER; typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext); typedef BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); -typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_MANAGED_CONTEXT_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion); +typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion); 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); @@ -32,7 +32,7 @@ public: SetCallbackHandles( _In_ PFN_REQUEST_HANDLER request_callback, _In_ PFN_SHUTDOWN_HANDLER shutdown_callback, - _In_ PFN_MANAGED_CONTEXT_HANDLER managed_context_callback, + _In_ PFN_ASYNC_COMPLETION_HANDLER managed_context_callback, _In_ VOID* pvRequstHandlerContext, _In_ VOID* pvShutdownHandlerContext ); @@ -60,20 +60,6 @@ public: HRESULT hr ); - REQUEST_NOTIFICATION_STATUS - OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus, - IN_PROCESS_HANDLER* pInProcessHandler - ); - - REQUEST_NOTIFICATION_STATUS - OnExecuteRequest - ( - IHttpContext* pHttpContext, - IN_PROCESS_HANDLER* pInProcessHandler - ); - VOID StopCallsIntoManaged( VOID @@ -105,17 +91,27 @@ public: return s_Application; } - REQUESTHANDLER_CONFIG* - QueryConfig() const; - PCWSTR QueryExeLocation() { return m_struExeLocation.QueryStr(); } + REQUESTHANDLER_CONFIG* + QueryConfig() + { + return m_pConfig.get(); + } + + bool + QueryBlockCallbacksIntoManaged() const + { + return m_fBlockCallbacksIntoManaged; + } + private: - IHttpServer* const m_pHttpServer; + + IHttpServer & m_pHttpServer; // Thread executing the .NET Core process HANDLE m_hThread; @@ -128,7 +124,7 @@ private: PFN_SHUTDOWN_HANDLER m_ShutdownHandler; VOID* m_ShutdownHandlerContext; - PFN_MANAGED_CONTEXT_HANDLER m_AsyncCompletionHandler; + PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler; // The event that gets triggered when managed initialization is complete HANDLE m_pInitalizeEvent; @@ -144,12 +140,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..de806e7f71 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp @@ -9,17 +9,20 @@ ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL; IN_PROCESS_HANDLER::IN_PROCESS_HANDLER( + _In_ std::unique_ptr pApplication, _In_ IHttpContext *pW3Context, - _In_ IN_PROCESS_APPLICATION *pApplication -): m_pW3Context(pW3Context), - m_pApplication(pApplication) + _In_ PFN_REQUEST_HANDLER pRequestHandler, + _In_ void * pRequestHandlerContext, + _In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion +): m_pManagedHttpContext(nullptr), + m_requestNotificationStatus(RQ_NOTIFICATION_PENDING), + m_fManagedRequestComplete(FALSE), + m_pW3Context(pW3Context), + m_pApplication(std::move(pApplication)), + m_pRequestHandler(pRequestHandler), + m_pRequestHandlerContext(pRequestHandlerContext), + m_pAsyncCompletionHandler(pAsyncCompletion) { - m_fManagedRequestComplete = FALSE; -} - -IN_PROCESS_HANDLER::~IN_PROCESS_HANDLER() -{ - //todo } __override @@ -36,8 +39,32 @@ IN_PROCESS_HANDLER::OnExecuteRequestHandler() L"InProcess Application"); } - //SetHttpSysDisconnectCallback(); - return m_pApplication->OnExecuteRequest(m_pW3Context, this); + if (m_pRequestHandler == NULL) + { + // + // return error as the application did not register callback + // + if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) + { + ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(m_pW3Context->GetTraceContext(), + NULL, + (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); + } + + m_pW3Context->GetResponse()->SetStatus(500, + "Internal Server Error", + 0, + (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); + + return RQ_NOTIFICATION_FINISH_REQUEST; + } + else if (m_pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING || m_pApplication-> + QueryBlockCallbacksIntoManaged()) + { + return ServerShutdownMessage(); + } + + return m_pRequestHandler(this, m_pRequestHandlerContext); } __override @@ -47,9 +74,28 @@ IN_PROCESS_HANDLER::OnAsyncCompletion( HRESULT hrCompletionStatus ) { - // OnAsyncCompletion must call into the application if there was a error. We will redo calls - // to Read/Write if we called cancelIo on the IHttpContext. - return m_pApplication->OnAsyncCompletion(cbCompletion, hrCompletionStatus, this); + if (m_fManagedRequestComplete) + { + // means PostCompletion has been called and this is the associated callback. + return m_requestNotificationStatus; + } + if (m_pApplication->QueryBlockCallbacksIntoManaged()) + { + // this can potentially happen in ungraceful shutdown. + // Or something really wrong happening with async completions + // At this point, managed is in a shutting down state and we cannot send a request to it. + return ServerShutdownMessage(); + } + + // Call the managed handler for async completion. + return m_pAsyncCompletionHandler(m_pManagedHttpContext, hrCompletionStatus, cbCompletion); +} + +REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage() const +{ + m_pW3Context->GetResponse()->SetStatus(503, "Server has been shutdown", 0, + (ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); + return RQ_NOTIFICATION_FINISH_REQUEST; } VOID @@ -58,31 +104,6 @@ IN_PROCESS_HANDLER::TerminateRequest( ) { UNREFERENCED_PARAMETER(fClientInitiated); - //todo -} - -PVOID -IN_PROCESS_HANDLER::QueryManagedHttpContext( - VOID -) -{ - return m_pManagedHttpContext; -} - -BOOL -IN_PROCESS_HANDLER::QueryIsManagedRequestComplete( - VOID -) -{ - return m_fManagedRequestComplete; -} - -IHttpContext* -IN_PROCESS_HANDLER::QueryHttpContext( - VOID -) -{ - return m_pW3Context; } VOID @@ -93,14 +114,6 @@ IN_PROCESS_HANDLER::IndicateManagedRequestComplete( m_fManagedRequestComplete = TRUE; } -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_HANDLER::QueryAsyncCompletionStatus( - VOID -) -{ - return m_requestNotificationStatus; -} - VOID IN_PROCESS_HANDLER::SetAsyncCompletionStatus( REQUEST_NOTIFICATION_STATUS requestNotificationStatus @@ -163,7 +176,7 @@ HRESULT } hr = sm_pAlloc->Initialize(sizeof(IN_PROCESS_HANDLER), - 64); // nThreshold + 64); // nThreshold Finished: if (FAILED(hr)) diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h index 24a29b737f..fe0331da9b 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h @@ -3,8 +3,10 @@ #pragma once -#include "precomp.hxx" #include "requesthandler.h" +#include +#include "iapplication.h" +#include "inprocessapplication.h" class IN_PROCESS_APPLICATION; @@ -12,10 +14,13 @@ class IN_PROCESS_HANDLER : public REQUEST_HANDLER { public: IN_PROCESS_HANDLER( + _In_ std::unique_ptr pApplication, _In_ IHttpContext *pW3Context, - _In_ IN_PROCESS_APPLICATION *pApplication); + _In_ PFN_REQUEST_HANDLER pRequestHandler, + _In_ void * pRequestHandlerContext, + _In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion); - ~IN_PROCESS_HANDLER() override; + ~IN_PROCESS_HANDLER() override = default; __override REQUEST_NOTIFICATION_STATUS @@ -33,37 +38,25 @@ public: TerminateRequest( bool fClientInitiated ) override; - - PVOID - QueryManagedHttpContext( + + IHttpContext* + QueryHttpContext( VOID - ); + ) const + { + return m_pW3Context; + } VOID SetManagedHttpContext( PVOID pManagedHttpContext ); - IHttpContext* - QueryHttpContext( - VOID - ); - - BOOL - QueryIsManagedRequestComplete( - VOID - ); - VOID IndicateManagedRequestComplete( VOID ); - REQUEST_NOTIFICATION_STATUS - QueryAsyncCompletionStatus( - VOID - ); - VOID SetAsyncCompletionStatus( REQUEST_NOTIFICATION_STATUS requestNotificationStatus @@ -77,12 +70,18 @@ public: HRESULT StaticInitialize(VOID); - private: + REQUEST_NOTIFICATION_STATUS + ServerShutdownMessage() const; + PVOID m_pManagedHttpContext; BOOL m_fManagedRequestComplete; REQUEST_NOTIFICATION_STATUS m_requestNotificationStatus; IHttpContext* m_pW3Context; - IN_PROCESS_APPLICATION* m_pApplication; + std::unique_ptr m_pApplication; + PFN_REQUEST_HANDLER m_pRequestHandler; + void* m_pRequestHandlerContext; + PFN_ASYNC_COMPLETION_HANDLER m_pAsyncCompletionHandler; + static ALLOC_CACHE_HANDLER * sm_pAlloc; }; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cxx b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cxx index c4860c1acb..38c4cb6503 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cxx +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cxx @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#include "precomp.hxx" #include "inprocessapplication.h" #include "inprocesshandler.h" #include "requesthandler_config.h" @@ -14,7 +13,7 @@ register_callbacks( _In_ IN_PROCESS_APPLICATION* pInProcessApplication, _In_ PFN_REQUEST_HANDLER request_handler, _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, - _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, + _In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler, _In_ VOID* pvRequstHandlerContext, _In_ VOID* pvShutdownHandlerContext ) @@ -64,8 +63,6 @@ http_get_server_variable( PCWSTR pszVariableValue; DWORD cbLength; - DBG_ASSERT(pszVariableValue != NULL); - HRESULT hr = pInProcessHandler ->QueryHttpContext() ->GetServerVariable(pszVariableName, &pszVariableValue, &cbLength); diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/precomp.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/precomp.cpp new file mode 100644 index 0000000000..12fcb1d436 --- /dev/null +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/precomp.cpp @@ -0,0 +1,4 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +// Do not remove this file. It is used for precompiled header generation diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj index 3778b906c6..41f2612683 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj @@ -111,6 +111,7 @@ true kernel32.lib;user32.lib;nothrownew.obj;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) Source.def + UseLinkTimeCodeGeneration @@ -143,6 +144,7 @@ true kernel32.lib;user32.lib;nothrownew.obj;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) Source.def + UseLinkTimeCodeGeneration @@ -177,6 +179,7 @@ true Source.def kernel32.lib;user32.lib;nothrownew.obj;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration @@ -211,6 +214,7 @@ true Source.def kernel32.lib;user32.lib;nothrownew.obj;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx index c74f3cacaa..e2d5c1ec3c 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cxx @@ -54,7 +54,7 @@ InitializeGlobalConfiguration( } if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", 0, KEY_READ, &hKey) == NO_ERROR) @@ -119,7 +119,7 @@ HRESULT EnsureOutOfProcessInitializtion() { - DBG_ASSERT(pServer); + DBG_ASSERT(g_pHttpServer); HRESULT hr = S_OK; BOOL fLocked = FALSE; @@ -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/Inprocess/FixtureLoggedTest.cs b/test/Common.FunctionalTests/Inprocess/FixtureLoggedTest.cs new file mode 100644 index 0000000000..4604687991 --- /dev/null +++ b/test/Common.FunctionalTests/Inprocess/FixtureLoggedTest.cs @@ -0,0 +1,31 @@ +// 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. + +using System.Reflection; +using Microsoft.Extensions.Logging.Testing; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class FixtureLoggedTest: LoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public FixtureLoggedTest(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(methodInfo, testMethodArguments, testOutputHelper); + _fixture.Attach(this); + } + + public override void Dispose() + { + _fixture.Detach(this); + base.Dispose(); + } + } +} diff --git a/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs b/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs index 9b57d4996b..03aa0e16e6 100644 --- a/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs +++ b/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs @@ -6,23 +6,22 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Testing.xunit; using Xunit; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] - public class SynchronousReadAndWriteTests + public class SynchronousReadAndWriteTests: FixtureLoggedTest { private readonly IISTestSiteFixture _fixture; - public SynchronousReadAndWriteTests(IISTestSiteFixture fixture) + public SynchronousReadAndWriteTests(IISTestSiteFixture fixture): base(fixture) { _fixture = fixture; } - [ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/766")] + [ConditionalFact] public async Task ReadAndWriteSynchronously() { for (int i = 0; i < 100; i++) 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/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs b/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs index d19dad36d0..1a60e85246 100644 --- a/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; @@ -14,6 +16,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public class IISTestSiteFixture : IDisposable { private readonly ApplicationDeployer _deployer; + private readonly ForwardingProvider _forwardingProvider; public IISTestSiteFixture() { @@ -30,14 +33,17 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests PublishApplicationBeforeDeployment = true, }; + _forwardingProvider = new ForwardingProvider(); + var loggerFactory = logging.CreateLoggerFactory(null, nameof(IISTestSiteFixture)); + loggerFactory.AddProvider(_forwardingProvider); if (deploymentParameters.ServerType == ServerType.IIS) { // Currently hosting throws if the Servertype = IIS. - _deployer = new IISDeployer(deploymentParameters, logging.CreateLoggerFactory(null, nameof(IISTestSiteFixture))); + _deployer = new IISDeployer(deploymentParameters, loggerFactory); } else if (deploymentParameters.ServerType == ServerType.IISExpress) { - _deployer = new IISExpressDeployer(deploymentParameters, logging.CreateLoggerFactory(null, nameof(IISTestSiteFixture))); + _deployer = new IISExpressDeployer(deploymentParameters, loggerFactory); } DeploymentResult = _deployer.DeployAsync().Result; @@ -60,5 +66,88 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { _deployer.Dispose(); } + + public void Attach(LoggedTest test) + { + if (_forwardingProvider.LoggerFactory != null) + { + throw new InvalidOperationException("Test instance is already attached to this fixture"); + } + + _forwardingProvider.LoggerFactory = test.LoggerFactory; + } + + public void Detach(LoggedTest test) + { + if (_forwardingProvider.LoggerFactory != test.LoggerFactory) + { + throw new InvalidOperationException("Different test is attached to this fixture"); + } + + _forwardingProvider.LoggerFactory = null; + } + + private class ForwardingProvider : ILoggerProvider + { + private readonly List _loggers = new List(); + + private ILoggerFactory _loggerFactory; + + public ILoggerFactory LoggerFactory + { + get => _loggerFactory; + set + { + + lock (_loggers) + { + _loggerFactory = value; + foreach (var logger in _loggers) + { + logger.Logger = _loggerFactory?.CreateLogger("FIXTURE:" + logger.Name); + } + } + } + } + + public void Dispose() + { + lock (_loggers) + { + _loggers.Clear(); + } + } + + public ILogger CreateLogger(string categoryName) + { + lock (_loggers) + { + var logger = new ForwardingLogger(categoryName); + _loggers.Add(logger); + return logger; + } + } + } + + internal class ForwardingLogger : ILogger + { + public ForwardingLogger(string name) + { + Name = name; + } + + public ILogger Logger { get; set; } + public string Name { get; set; } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + Logger?.Log(logLevel, eventId, state, exception, formatter); + } + + public bool IsEnabled(LogLevel logLevel) => Logger?.IsEnabled(logLevel) == true; + + public IDisposable BeginScope(TState state) => Logger?.BeginScope(state); + } } + } diff --git a/test/CommonLibTests/CommonLibTests.vcxproj b/test/CommonLibTests/CommonLibTests.vcxproj index 906518c621..85cc1d39be 100644 --- a/test/CommonLibTests/CommonLibTests.vcxproj +++ b/test/CommonLibTests/CommonLibTests.vcxproj @@ -101,7 +101,7 @@ true Console ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\; - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;precomp.obj;%(AdditionalDependencies) @@ -127,7 +127,7 @@ true Console ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\; - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;precomp.obj;%(AdditionalDependencies) @@ -153,7 +153,7 @@ true true ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\; - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;precomp.obj;%(AdditionalDependencies) @@ -179,7 +179,7 @@ true true ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\; - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;precomp.obj;%(AdditionalDependencies) 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..6d8e73a15c 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); @@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests StopServer(); var logContents = File.ReadAllText(tempFile); - Assert.Contains("[aspnetcore.dll]", logContents); + Assert.Contains("[aspnetcorev2.dll]", logContents); Assert.Contains("[aspnetcorev2_inprocess.dll]", logContents); } finally 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");