From 61b4473abe95f2f53aad35202a32bff5ab32c053 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 26 Jul 2018 08:12:08 -0700 Subject: [PATCH 1/6] Fix another shutdown race and appofline race (#1071) --- .../AspNetCore/AspNetCore.vcxproj | 2 + .../AspNetCore/ServerErrorApplication.h | 31 ++ .../AspNetCore/ServerErrorHandler.h | 23 ++ .../AspNetCore/applicationinfo.cpp | 177 +++-------- .../AspNetCore/applicationinfo.h | 35 +-- .../AspNetCore/applicationmanager.cpp | 1 - .../AspNetCore/proxymodule.cpp | 22 +- .../CommonLib/AppOfflineApplication.cpp | 61 ++++ .../CommonLib/AppOfflineApplication.h | 27 ++ .../CommonLib/AppOfflineHandler.cpp | 30 ++ .../CommonLib/AppOfflineHandler.h | 23 ++ .../CommonLib/CommonLib.vcxproj | 4 + .../PollingAppOfflineApplication.cpp | 95 +----- .../CommonLib/PollingAppOfflineApplication.h | 48 ++- .../CommonLib/application.h | 4 +- .../CommonLib/iapplication.h | 7 +- .../InProcessApplicationBase.cpp | 18 +- .../InProcessApplicationBase.h | 2 +- .../InProcessRequestHandler.vcxproj | 1 + .../ShuttingDownApplication.h | 45 +++ .../StartupExceptionApplication.cpp | 4 - .../StartupExceptionApplication.h | 1 - .../InProcessRequestHandler/dllmain.cpp | 14 +- .../inprocessapplication.cpp | 22 +- .../inprocessapplication.h | 4 +- .../inprocesshandler.cpp | 5 +- .../managedexports.cpp | 5 + .../outprocessapplication.cpp | 11 +- .../outprocessapplication.h | 7 +- .../AppOfflineTrackingApplication.cpp | 3 +- .../AppOfflineTrackingApplication.h | 8 +- .../RequestHandlerLib/filewatcher.cpp | 17 +- .../RequestHandlerLib/filewatcher.h | 1 + .../Common.FunctionalTests/AppOfflineTests.cs | 278 ++++++++++++++++++ .../Inprocess/ServerVariablesTest.cs | 24 +- .../Utilities/Helpers.cs | 23 ++ .../Utilities/IISCapability.cs | 3 +- .../RequiresIISAttribute.cs | 6 + test/IIS.Tests/TestServerTest.cs | 22 +- .../InProcess/AppOfflineTests.cs | 159 ---------- 40 files changed, 733 insertions(+), 540 deletions(-) create mode 100644 src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h create mode 100644 src/AspNetCoreModuleV2/AspNetCore/ServerErrorHandler.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.h create mode 100644 src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h create mode 100644 test/Common.FunctionalTests/AppOfflineTests.cs delete mode 100644 test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs diff --git a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index c0d926bd35..4895850cbc 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -230,6 +230,8 @@ + + diff --git a/src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h b/src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h new file mode 100644 index 0000000000..1872675656 --- /dev/null +++ b/src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include "PollingAppOfflineApplication.h" +#include "requesthandler.h" +#include "ServerErrorHandler.h" + +class ServerErrorApplication : public PollingAppOfflineApplication +{ +public: + ServerErrorApplication(IHttpApplication& pApplication, HRESULT hr) + : m_HR(hr), + PollingAppOfflineApplication(pApplication, PollingAppOfflineApplicationMode::StopWhenAdded) + { + m_status = APPLICATION_STATUS::RUNNING; + } + + ~ServerErrorApplication() = default; + + HRESULT CreateHandler(IHttpContext * pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override + { + *pRequestHandler = new ServerErrorHandler(pHttpContext, m_HR); + return S_OK; + } + + HRESULT OnAppOfflineFound() override { return S_OK; } +private: + HRESULT m_HR; +}; + diff --git a/src/AspNetCoreModuleV2/AspNetCore/ServerErrorHandler.h b/src/AspNetCoreModuleV2/AspNetCore/ServerErrorHandler.h new file mode 100644 index 0000000000..b3d917039d --- /dev/null +++ b/src/AspNetCoreModuleV2/AspNetCore/ServerErrorHandler.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include "requesthandler.h" + +class ServerErrorHandler : public REQUEST_HANDLER +{ +public: + ServerErrorHandler(IHttpContext* pContext, HRESULT hr) : m_pContext(pContext), m_HR(hr) + { + } + + REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override + { + m_pContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, m_HR); + return RQ_NOTIFICATION_FINISH_REQUEST; + } + +private: + IHttpContext * m_pContext; + HRESULT m_HR; +}; diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index edfc085f22..a65af02467 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -12,8 +12,10 @@ #include "SRWExclusiveLock.h" #include "GlobalVersionUtility.h" #include "exceptions.h" -#include "PollingAppOfflineApplication.h" #include "EventLog.h" +#include "HandleWrapper.h" +#include "ServerErrorApplication.h" +#include "AppOfflineApplication.h" extern HINSTANCE g_hModule; @@ -29,21 +31,7 @@ PFN_ASPNETCORE_CREATE_APPLICATION APPLICATION_INFO::s_pfnAspNetCoreCreateApplica APPLICATION_INFO::~APPLICATION_INFO() { - if (m_pApplication != NULL) - { - // shutdown the application - m_pApplication->ShutDown(); - m_pApplication->DereferenceApplication(); - m_pApplication = NULL; - } - - // configuration should be dereferenced after application shutdown - // since the former will use it during shutdown - if (m_pConfiguration != NULL) - { - delete m_pConfiguration; - m_pConfiguration = NULL; - } + ShutDownApplication(); } HRESULT @@ -51,7 +39,7 @@ APPLICATION_INFO::Initialize( _In_ IHttpApplication &pApplication ) { - m_pConfiguration = new ASPNETCORE_SHIM_CONFIG(); + m_pConfiguration.reset(new ASPNETCORE_SHIM_CONFIG()); RETURN_IF_FAILED(m_pConfiguration->Populate(&m_pServer, &pApplication)); RETURN_IF_FAILED(m_struInfoKey.Copy(pApplication.GetApplicationId())); @@ -60,22 +48,16 @@ APPLICATION_INFO::Initialize( HRESULT -APPLICATION_INFO::EnsureApplicationCreated( - IHttpContext *pHttpContext +APPLICATION_INFO::GetOrCreateApplication( + IHttpContext *pHttpContext, + std::unique_ptr& pApplication ) { HRESULT hr = S_OK; - IAPPLICATION *pApplication = NULL; - STRU struExeLocation; - STRU struHostFxrDllLocation; - STACK_STRU(struFileName, 300); // >MAX_PATH + + SRWExclusiveLock lock(m_applicationLock); - if (m_pApplication != nullptr && m_pApplication->QueryStatus() != RECYCLED) - { - return S_OK; - } - - SRWExclusiveLock lock(m_srwLock); + auto& httpApplication = *pHttpContext->GetApplication(); if (m_pApplication != nullptr) { @@ -84,7 +66,6 @@ APPLICATION_INFO::EnsureApplicationCreated( LOG_INFO("Application went offline"); // Application that went offline // are supposed to recycle themselves - m_pApplication->DereferenceApplication(); m_pApplication = nullptr; } else @@ -93,26 +74,15 @@ APPLICATION_INFO::EnsureApplicationCreated( FINISHED(S_OK); } } - else if (m_fAppCreationAttempted) - { - // previous CreateApplication failed - FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); - } - auto& httpApplication = *pHttpContext->GetApplication(); - if (PollingAppOfflineApplication::ShouldBeStarted(httpApplication)) + if (AppOfflineApplication::ShouldBeStarted(httpApplication)) { - LOG_INFO("Detected app_ofline file, creating polling application"); - m_pApplication = new PollingAppOfflineApplication(httpApplication); + LOG_INFO("Detected app_offline file, creating polling application"); + m_pApplication.reset(new AppOfflineApplication(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; + STRU struExeLocation; FINISHED_IF_FAILED(FindRequestHandlerAssembly(struExeLocation)); if (m_pfnAspNetCoreCreateApplication == NULL) @@ -123,15 +93,17 @@ APPLICATION_INFO::EnsureApplicationCreated( std::array parameters { {"InProcessExeLocation", struExeLocation.QueryStr()} }; + LOG_INFO("Creating handler application"); + IAPPLICATION * newApplication; FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication( &m_pServer, - pHttpContext->GetApplication(), + &httpApplication, parameters.data(), static_cast(parameters.size()), - &pApplication)); + &newApplication)); - m_pApplication = pApplication; + m_pApplication.reset(newApplication); } Finished: @@ -143,8 +115,15 @@ Finished: EVENTLOG_ERROR_TYPE, ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, - pHttpContext->GetApplication()->GetApplicationId(), + httpApplication.GetApplicationId(), hr); + + m_pApplication.reset(new ServerErrorApplication(httpApplication, hr)); + } + + if (m_pApplication) + { + pApplication = ReferenceApplication(m_pApplication.get()); } return hr; @@ -413,107 +392,47 @@ Finished: VOID APPLICATION_INFO::RecycleApplication() { - IAPPLICATION* pApplication; - HANDLE hThread = INVALID_HANDLE_VALUE; + SRWExclusiveLock lock(m_applicationLock); - if (m_pApplication != NULL) + if (m_pApplication) { - SRWExclusiveLock lock(m_srwLock); + const auto pApplication = m_pApplication.release(); - if (m_pApplication != NULL) - { - pApplication = m_pApplication; - if (m_pConfiguration->QueryHostingModel() == HOSTING_OUT_PROCESS) - { - // - // For inprocess, need to set m_pApplication to NULL first to - // avoid mapping new request to the recycled application. - // Outofprocess application instance will be created for new request - // For inprocess, as recycle will lead to shutdown later, leave m_pApplication - // to not block incoming requests till worker process shutdown - // - m_pApplication = NULL; - } - else - { - // - // For inprocess, need hold the application till shutdown is called - // Bump the reference counter as DoRecycleApplication will do dereference - // - pApplication->ReferenceApplication(); - } - - hThread = CreateThread( - NULL, // default security attributes - 0, // default stack size - (LPTHREAD_START_ROUTINE)DoRecycleApplication, - pApplication, // thread function arguments - 0, // default creation flags - NULL); // receive thread identifier - } - else - { - if (m_pConfiguration->QueryHostingModel() == HOSTING_IN_PROCESS) - { - // In process application failed to start for whatever reason, need to recycle the work process - m_pServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); - } - } - - if (hThread == NULL) - { - if (!g_fRecycleProcessCalled) - { - g_fRecycleProcessCalled = TRUE; - m_pServer.RecycleProcess(L"On Demand by AspNetCore Module for recycle application failure"); - } - } - else - { - // Closing a thread handle does not terminate the associated thread or remove the thread object. - CloseHandle(hThread); - } + HandleWrapper hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)DoRecycleApplication, + pApplication, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier } } -VOID +DWORD WINAPI APPLICATION_INFO::DoRecycleApplication( LPVOID lpParam) { - IAPPLICATION* pApplication = static_cast(lpParam); + auto pApplication = std::unique_ptr(static_cast(lpParam)); - // No lock required - - if (pApplication != NULL) + if (pApplication) { // Recycle will call shutdown for out of process - pApplication->Recycle(); - - // Decrement the ref count as we reference it in RecycleApplication. - pApplication->DereferenceApplication(); + pApplication->Stop(/*fServerInitiated*/ false); } + + return 0; } VOID APPLICATION_INFO::ShutDownApplication() { - IAPPLICATION* pApplication = NULL; + SRWExclusiveLock lock(m_applicationLock); - // pApplication can be NULL due to app_offline - if (m_pApplication != NULL) + if (m_pApplication) { - SRWExclusiveLock lock(m_srwLock); - - if (m_pApplication != NULL) - { - pApplication = m_pApplication; - - // Set m_pApplication to NULL first to prevent anyone from using it - m_pApplication = NULL; - pApplication->ShutDown(); - pApplication->DereferenceApplication(); - } + m_pApplication ->Stop(/* fServerInitiated */ true); + m_pApplication = nullptr; } } diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index 49badd578a..989120a538 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -32,11 +32,10 @@ public: m_pServer(pServer), m_cRefs(1), m_fValid(FALSE), - m_fAppCreationAttempted(FALSE), - m_pConfiguration(NULL), + m_pConfiguration(nullptr), m_pfnAspNetCoreCreateApplication(NULL) { - InitializeSRWLock(&m_srwLock); + InitializeSRWLock(&m_applicationLock); } PCWSTR @@ -90,19 +89,7 @@ public: 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 - // - std::unique_ptr - ExtractApplication() const - { - SRWSharedLock lock(m_srwLock); - return ReferenceApplication(m_pApplication); + return m_pConfiguration.get(); } VOID @@ -112,8 +99,9 @@ public: ShutDownApplication(); HRESULT - EnsureApplicationCreated( - IHttpContext *pHttpContext + GetOrCreateApplication( + IHttpContext *pHttpContext, + std::unique_ptr& pApplication ); private: @@ -121,17 +109,18 @@ private: HRESULT FindNativeAssemblyFromGlobalLocation(PCWSTR libraryName, STRU* location); HRESULT FindNativeAssemblyFromHostfxr(HOSTFXR_OPTIONS* hostfxrOptions, PCWSTR libraryName, STRU* location); - static VOID DoRecycleApplication(LPVOID lpParam); + static DWORD WINAPI DoRecycleApplication(LPVOID lpParam); mutable LONG m_cRefs; STRU m_struInfoKey; BOOL m_fValid; - BOOL m_fAppCreationAttempted; - ASPNETCORE_SHIM_CONFIG *m_pConfiguration; - IAPPLICATION *m_pApplication; - SRWLOCK m_srwLock; + SRWLOCK m_applicationLock; IHttpServer &m_pServer; PFN_ASPNETCORE_CREATE_APPLICATION m_pfnAspNetCoreCreateApplication; + + std::unique_ptr m_pConfiguration; + std::unique_ptr m_pApplication; + static const PCWSTR s_pwzAspnetcoreInProcessRequestHandlerName; static const PCWSTR s_pwzAspnetcoreOutOfProcessRequestHandlerName; diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp index a7f08e5d3e..9de2901be5 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -97,7 +97,6 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( *ppApplicationInfo = pApplicationInfo; pApplicationInfo = NULL; } - Finished: // log the error diff --git a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp index 962d7202d1..0ed100fd47 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp @@ -95,20 +95,18 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( // the error should already been logged to window event log for the first request FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } - - // make sure assmebly is loaded and application is created - FINISHED_IF_FAILED(m_pApplicationInfo->EnsureApplicationCreated(pHttpContext)); - - auto pApplication = m_pApplicationInfo->ExtractApplication(); - - DBG_ASSERT(pHttpContext); - // We allow OFFLINE application to serve pages + DBG_ASSERT(pHttpContext); + + std::unique_ptr pApplication; + FINISHED_IF_FAILED(m_pApplicationInfo->GetOrCreateApplication(pHttpContext, pApplication)); + + // We allow RECYCLED application to serve pages if (pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING && - pApplication->QueryStatus() != APPLICATION_STATUS::STARTING) + pApplication->QueryStatus() != APPLICATION_STATUS::STARTING && + pApplication->QueryStatus() != APPLICATION_STATUS::RECYCLED) { - hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); - goto Finished; + FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED)); } IREQUEST_HANDLER* pHandler; @@ -124,7 +122,7 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( } Finished: - if (FAILED(hr)) + if (LOG_IF_FAILED(hr)) { retVal = RQ_NOTIFICATION_FINISH_REQUEST; if (hr == HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)) diff --git a/src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.cpp b/src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.cpp new file mode 100644 index 0000000000..e42c6ca8e6 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.cpp @@ -0,0 +1,61 @@ +// 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 "AppOfflineApplication.h" + +#include +#include "HandleWrapper.h" +#include "AppOfflineHandler.h" + +HRESULT AppOfflineApplication::CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler) +{ + try + { + *pRequestHandler = new AppOfflineHandler(pHttpContext, m_strAppOfflineContent); + } + CATCH_RETURN(); + + return S_OK; +} + +HRESULT AppOfflineApplication::OnAppOfflineFound() +{ + 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 E_INVALIDARG; + } + + 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 AppOfflineApplication::ShouldBeStarted(IHttpApplication& pApplication) +{ + return is_regular_file(GetAppOfflineLocation(pApplication)); +} diff --git a/src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.h b/src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.h new file mode 100644 index 0000000000..f6ec00cab0 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/AppOfflineApplication.h @@ -0,0 +1,27 @@ +// 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 "application.h" +#include "requesthandler.h" +#include "PollingAppOfflineApplication.h" + +class AppOfflineApplication: public PollingAppOfflineApplication +{ +public: + AppOfflineApplication(IHttpApplication& pApplication) + : PollingAppOfflineApplication(pApplication, PollingAppOfflineApplicationMode::StopWhenRemoved) + { + } + + HRESULT CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler) override; + + HRESULT OnAppOfflineFound() override; + + static bool ShouldBeStarted(IHttpApplication& pApplication); + +private: + std::string m_strAppOfflineContent; +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.cpp b/src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.cpp new file mode 100644 index 0000000000..3efeb74463 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.cpp @@ -0,0 +1,30 @@ +// 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 "AppOfflineHandler.h" + +#include "HandleWrapper.h" + +REQUEST_NOTIFICATION_STATUS AppOfflineHandler::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/AppOfflineHandler.h b/src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.h new file mode 100644 index 0000000000..e9fd4b99da --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/AppOfflineHandler.h @@ -0,0 +1,23 @@ +// 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 "application.h" +#include "requesthandler.h" + +class AppOfflineHandler: public REQUEST_HANDLER +{ +public: + AppOfflineHandler(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/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 5ff3e0103f..e905714bfa 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -193,6 +193,8 @@ + + @@ -216,6 +218,8 @@ + + diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp index dff673fee3..1468c7ee37 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp @@ -7,25 +7,9 @@ #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; + return (AppOfflineExists() == (m_mode == StopWhenRemoved)) ? APPLICATION_STATUS::RUNNING : APPLICATION_STATUS::RECYCLED; } bool @@ -35,7 +19,7 @@ PollingAppOfflineApplication::AppOfflineExists() // // 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 + // we cache the file exist check result for 200 ms // if (ulCurrentTime - m_ulLastCheckTime > c_appOfflineRefreshIntervalMS) { @@ -45,7 +29,7 @@ PollingAppOfflineApplication::AppOfflineExists() m_fAppOfflineFound = is_regular_file(m_appOfflineLocation); if(m_fAppOfflineFound) { - LOG_IF_FAILED(LoadAppOfflineContent()); + LOG_IF_FAILED(OnAppOfflineFound()); } m_ulLastCheckTime = ulCurrentTime; } @@ -53,81 +37,8 @@ PollingAppOfflineApplication::AppOfflineExists() 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 index 592cdb2dc8..48e71f6641 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h @@ -4,54 +4,40 @@ #pragma once #include #include "application.h" -#include "requesthandler.h" + +enum PollingAppOfflineApplicationMode +{ + StopWhenAdded, + StopWhenRemoved +}; class PollingAppOfflineApplication: public APPLICATION { public: - PollingAppOfflineApplication(IHttpApplication& pApplication) + PollingAppOfflineApplication(IHttpApplication& pApplication, PollingAppOfflineApplicationMode mode) : m_ulLastCheckTime(0), m_appOfflineLocation(GetAppOfflineLocation(pApplication)), - m_fAppOfflineFound(false) + m_fAppOfflineFound(false), + m_mode(mode) { 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; + virtual HRESULT OnAppOfflineFound() = 0; + void Stop(bool fServerInitiated) override { UNREFERENCED_PARAMETER(fServerInitiated); } + +protected: + std::experimental::filesystem::path m_appOfflineLocation; + static std::experimental::filesystem::path GetAppOfflineLocation(IHttpApplication& pApplication); 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; + ULONGLONG m_ulLastCheckTime; bool m_fAppOfflineFound; SRWLOCK m_statusLock {}; + PollingAppOfflineApplicationMode m_mode; }; - - -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 56cf5ce2ac..9761851002 100644 --- a/src/AspNetCoreModuleV2/CommonLib/application.h +++ b/src/AspNetCoreModuleV2/CommonLib/application.h @@ -31,13 +31,15 @@ public: VOID ReferenceApplication() override { + DBG_ASSERT(m_cRefs > 0); + InterlockedIncrement(&m_cRefs); } VOID DereferenceApplication() override { - DBG_ASSERT(m_cRefs != 0); + DBG_ASSERT(m_cRefs > 0); if (InterlockedDecrement(&m_cRefs) == 0) { diff --git a/src/AspNetCoreModuleV2/CommonLib/iapplication.h b/src/AspNetCoreModuleV2/CommonLib/iapplication.h index ef5d2bf8d7..cdfd17ec03 100644 --- a/src/AspNetCoreModuleV2/CommonLib/iapplication.h +++ b/src/AspNetCoreModuleV2/CommonLib/iapplication.h @@ -25,14 +25,9 @@ struct APPLICATION_PARAMETER class IAPPLICATION { public: - virtual VOID - ShutDown() = 0; - - virtual - VOID - Recycle() = 0; + Stop(bool fServerInitiated) = 0; virtual ~IAPPLICATION() = 0 { }; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp index 5848f37a2f..d271a3a30a 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -18,9 +18,7 @@ InProcessApplicationBase::InProcessApplicationBase( } VOID -InProcessApplicationBase::Recycle( - VOID -) +InProcessApplicationBase::Stop(bool fServerInitiated) { // We need to guarantee that recycle is only called once, as calling pHttpServer->RecycleProcess // multiple times can lead to AVs. @@ -40,6 +38,12 @@ InProcessApplicationBase::Recycle( m_fRecycleCalled = true; } + // Stop was initiated by server no need to do anything, server would stop on it's own + if (fServerInitiated) + { + return; + } + if (!m_pHttpServer.IsCommandLineLaunch()) { // IIS scenario. @@ -50,13 +54,7 @@ InProcessApplicationBase::Recycle( } else { - // 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() - && s_fMainCallback == NULL) - { - exit(0); - } + exit(0); } } diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h index ca907d67c6..c189095ffa 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -18,7 +18,7 @@ public: ~InProcessApplicationBase() = default; - VOID Recycle(VOID) override; + VOID Stop(bool fServerInitiated) override; protected: BOOL m_fRecycleCalled; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj index 6531befa35..addfb56214 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj @@ -226,6 +226,7 @@ + diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h new file mode 100644 index 0000000000..4528a3f2b4 --- /dev/null +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "InProcessApplicationBase.h" + +class ShuttingDownHandler : public REQUEST_HANDLER +{ +public: + ShuttingDownHandler(IHttpContext* pContext) : m_pContext(pContext) + { + } + + REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override + { + return ServerShutdownMessage(m_pContext); + } + + static REQUEST_NOTIFICATION_STATUS ServerShutdownMessage(IHttpContext * pContext) + { + pContext->GetResponse()->SetStatus(503, "Server has been shutdown", 0, HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); + return RQ_NOTIFICATION_FINISH_REQUEST; + } +private: + IHttpContext * m_pContext; +}; + +class ShuttingDownApplication : public InProcessApplicationBase +{ +public: + ShuttingDownApplication(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication) + : InProcessApplicationBase(pHttpServer, pHttpApplication) + { + m_status = APPLICATION_STATUS::RUNNING; + } + + ~ShuttingDownApplication() = default; + + HRESULT CreateHandler(IHttpContext * pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override + { + *pRequestHandler = new ShuttingDownHandler(pHttpContext); + return S_OK; + } +}; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp index 4aa5b9fb31..fbb5350d62 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.cpp @@ -3,10 +3,6 @@ #include "StartupExceptionApplication.h" -VOID StartupExceptionApplication::ShutDown() -{ -} - HRESULT StartupExceptionApplication::CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler) { *pRequestHandler = new StartupExceptionHandler(pHttpContext, m_disableLogs, this); diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h index 0a2dc4fe58..14d26bf3a4 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h @@ -44,7 +44,6 @@ public: ~StartupExceptionApplication() = default; - VOID ShutDown() override; HRESULT CreateHandler(IHttpContext * pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override; std::string& diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp index f7b985aa0b..e0ed004a67 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp @@ -12,6 +12,7 @@ #include "debugutil.h" #include "resources.h" #include "exceptions.h" +#include "ShuttingDownApplication.h" DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2_inprocess.dll"); @@ -22,6 +23,7 @@ IHttpServer * g_pHttpServer = NULL; HINSTANCE g_hWinHttpModule; HINSTANCE g_hAspNetCoreModule; HANDLE g_hEventLog = NULL; +bool g_fInProcessApplicationCreated = false; HRESULT InitializeGlobalConfiguration( @@ -93,7 +95,15 @@ CreateApplication( try { RETURN_IF_FAILED(InitializeGlobalConfiguration(pServer, pHttpApplication)); - + + // In process application was already created so another call to CreateApplication + // means that server is shutting does and request arrived in the meantime + if (g_fInProcessApplicationCreated) + { + *ppApplication = new ShuttingDownApplication(*pServer, *pHttpApplication); + return S_OK; + } + REQUESTHANDLER_CONFIG *pConfig = nullptr; RETURN_IF_FAILED(REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig)); std::unique_ptr pRequestHandlerConfig(pConfig); @@ -102,6 +112,8 @@ CreateApplication( auto pApplication = std::make_unique(*pServer, *pHttpApplication, std::move(pRequestHandlerConfig), pParameters, nParameters); + // never create two inprocess applications in one process + g_fInProcessApplicationCreated = true; if (FAILED_LOG(pApplication->LoadManagedApplication())) { // Set the currently running application to a fake application that returns startup exceptions. diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index b52b08f833..756d78954c 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -50,7 +50,7 @@ IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() } //static -DWORD +DWORD WINAPI IN_PROCESS_APPLICATION::DoShutDown( LPVOID lpParam ) @@ -63,10 +63,9 @@ IN_PROCESS_APPLICATION::DoShutDown( __override VOID -IN_PROCESS_APPLICATION::ShutDown( - VOID -) +IN_PROCESS_APPLICATION::Stop(bool fServerInitiated) { + UNREFERENCED_PARAMETER(fServerInitiated); HRESULT hr = S_OK; CHandle hThread; DWORD dwThreadStatus = 0; @@ -118,15 +117,8 @@ Finished: ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG, m_pConfig->QueryConfigPath()->QueryStr()); - - // - // Managed layer may block the shutdown and lead to shutdown timeout - // Assumption: only one inprocess application is hosted. - // Call process exit to force shutdown - // - exit(hr); } - else + else { UTILITY::LogEventF(g_hEventLog, EVENTLOG_INFORMATION_TYPE, @@ -134,6 +126,8 @@ Finished: ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG, m_pConfig->QueryConfigPath()->QueryStr()); } + + InProcessApplicationBase::Stop(fServerInitiated); } VOID @@ -169,7 +163,7 @@ IN_PROCESS_APPLICATION::ShutDownInternal() // managed. We still need to wait on main exiting no matter what. m_fShutdownCalledFromNative // is used for detecting redundant calls and blocking more requests to OnExecuteRequestHandler. m_fShutdownCalledFromNative = TRUE; - m_status = APPLICATION_STATUS::SHUTDOWN; + m_status = APPLICATION_STATUS::RECYCLED; if (!m_fShutdownCalledFromManaged) { @@ -515,7 +509,7 @@ Finished: // // If the inprocess server was initialized, we need to cause recycle to be called on the worker process. // - Recycle(); + Stop(/*fServerInitiated*/ false); } } diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 1f6e3da570..e23cf6b83a 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -26,7 +26,7 @@ public: __override VOID - ShutDown(); + Stop(bool fServerInitiated) override; VOID SetCallbackHandles( @@ -167,7 +167,7 @@ private: ); static - DWORD + DWORD WINAPI DoShutDown( LPVOID lpParam ); diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp index de806e7f71..90b3bd9d53 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp @@ -5,6 +5,7 @@ #include "inprocessapplication.h" #include "aspnetcore_event.h" #include "IOutputManager.h" +#include "ShuttingDownApplication.h" ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL; @@ -93,9 +94,7 @@ IN_PROCESS_HANDLER::OnAsyncCompletion( 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; + return ShuttingDownHandler::ServerShutdownMessage(m_pW3Context); } VOID diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index 1db194bdbd..6e03ebd859 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -4,6 +4,9 @@ #include "inprocessapplication.h" #include "inprocesshandler.h" #include "requesthandler_config.h" + +extern bool g_fInProcessApplicationCreated; + // // Initialization export // @@ -469,6 +472,8 @@ EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID set_main_handler(_In_ hostfxr_main_fn main) { + // Allow inprocess application to be recreated as we reuse the same CLR + g_fInProcessApplicationCreated = false; IN_PROCESS_APPLICATION::SetMainCallback(main); } diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp index 90d75b2991..5f5877edac 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp @@ -63,8 +63,10 @@ OUT_OF_PROCESS_APPLICATION::GetProcess( __override VOID -OUT_OF_PROCESS_APPLICATION::ShutDown() +OUT_OF_PROCESS_APPLICATION::Stop(bool fServerInitiated) { + UNREFERENCED_PARAMETER(fServerInitiated); + SRWExclusiveLock lock(m_srwLock); if (m_pProcessManager != NULL) { @@ -72,13 +74,6 @@ OUT_OF_PROCESS_APPLICATION::ShutDown() } } -__override -VOID -OUT_OF_PROCESS_APPLICATION::Recycle() -{ - ShutDown(); -} - HRESULT OUT_OF_PROCESS_APPLICATION::CreateHandler( _In_ IHttpContext *pHttpContext, diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h index 8f51bf4216..1667069c09 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h @@ -32,12 +32,7 @@ public: __override VOID - ShutDown() - override; - - __override - VOID - Recycle() + Stop(bool fServerInitiated) override; __override diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp index 7c335ceff1..17c004b693 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -51,6 +51,7 @@ void AppOfflineTrackingApplication::OnAppOffline() { LOG_INFOF("Received app_offline notification in application %S", m_applicationPath.c_str()); m_fileWatcherEntry->StopMonitor(); + m_fileWatcherEntry.reset(nullptr); m_status = APPLICATION_STATUS::RECYCLED; UTILITY::LogEventF(g_hEventLog, EVENTLOG_INFORMATION_TYPE, @@ -58,5 +59,5 @@ void AppOfflineTrackingApplication::OnAppOffline() ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, m_applicationPath.c_str()); - Recycle(); + Stop(/*fServerInitiated*/ false); } diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h index 776ae9913c..2b6c36ccfa 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -17,7 +17,13 @@ public: { } - ~AppOfflineTrackingApplication() override = default; + ~AppOfflineTrackingApplication() override + { + if (m_fileWatcherEntry) + { + m_fileWatcherEntry->StopMonitor(); + } + }; HRESULT StartMonitoringAppOffline(); diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 37828b2623..721e248220 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -227,7 +227,7 @@ FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() { - StopMonitor(); + DBG_ASSERT(_cRefs == 0); _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; @@ -354,7 +354,6 @@ FILE_WATCHER_ENTRY::Monitor(VOID) { HRESULT hr = S_OK; DWORD cbRead; - AcquireSRWLockExclusive(&_srwLock); ReferenceFileWatcherEntry(); ZeroMemory(&_overlapped, sizeof(_overlapped)); @@ -372,6 +371,12 @@ FILE_WATCHER_ENTRY::Monitor(VOID) DereferenceFileWatcherEntry(); } + // Check if file exist because ReadDirectoryChangesW would not fire events for existing files + if (GetFileAttributes(_strFullName.QueryStr()) != INVALID_FILE_ATTRIBUTES) + { + PostQueuedCompletionStatus(_pFileMonitor->QueryCompletionPort(), 0, 0, &_overlapped); + } + ReleaseSRWLockExclusive(&_srwLock); return hr; } @@ -385,7 +390,7 @@ FILE_WATCHER_ENTRY::StopMonitor(VOID) // can be ignored // InterlockedExchange(&_lStopMonitorCalled, 1); - + MarkEntryInValid(); if (_hDirectory != INVALID_HANDLE_VALUE) { AcquireSRWLockExclusive(&_srwLock); @@ -431,6 +436,12 @@ FILE_WATCHER_ENTRY::Create( goto Finished; } + if (FAILED(hr = _strFullName.Append(_strDirectoryName)) || + FAILED(hr = _strFullName.Append(_strFileName))) + { + goto Finished; + } + // // Resize change buffer to something "reasonable" // diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h index c010caefc0..4e37600c69 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -111,6 +111,7 @@ private: FILE_WATCHER* _pFileMonitor; STRU _strFileName; STRU _strDirectoryName; + STRU _strFullName; LONG _lStopMonitorCalled; mutable LONG _cRefs; BOOL _fIsValid; diff --git a/test/Common.FunctionalTests/AppOfflineTests.cs b/test/Common.FunctionalTests/AppOfflineTests.cs new file mode 100644 index 0000000000..b2c30663a9 --- /dev/null +++ b/test/Common.FunctionalTests/AppOfflineTests.cs @@ -0,0 +1,278 @@ +// 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.IO; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess +{ + public class AppOfflineTests : IISFunctionalTestBase + { + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDroppedWhileSiteIsDown_SiteReturns503(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + AddAppOffline(deploymentResult.ContentRoot); + + await AssertAppOffline(deploymentResult); + DeletePublishOutput(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess, 500, "500.0")] + [InlineData(HostingModel.OutOfProcess, 502, "502.5")] + public async Task AppOfflineDroppedWhileSiteFailedToStartInShim_AppOfflineServed(HostingModel hostingModel, int statusCode, string content) + { + + var deploymentParameters = Helpers.GetBaseDeploymentParameters(hostingModel: hostingModel, publish: true); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "nonexistent")); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var result = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(statusCode, (int)result.StatusCode); + Assert.Contains(content, await result.Content.ReadAsStringAsync()); + + AddAppOffline(deploymentResult.ContentRoot); + + await AssertAppOffline(deploymentResult); + DeletePublishOutput(deploymentResult); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task AppOfflineDroppedWhileSiteFailedToStartInRequestHandler_SiteStops_InProcess() + { + var deploymentResult = await DeployApp(HostingModel.InProcess); + + // Set file content to empty so it fails at runtime + File.WriteAllText(Path.Combine(deploymentResult.ContentRoot, "Microsoft.AspNetCore.Server.IIS.dll"), ""); + + var result = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(500, (int)result.StatusCode); + Assert.Contains("500.30", await result.Content.ReadAsStringAsync()); + + AddAppOffline(deploymentResult.ContentRoot); + AssertStopsProcess(deploymentResult); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task AppOfflineDroppedWhileSiteStarting_SiteShutsDown_InProcess() + { + var deploymentResult = await DeployApp(HostingModel.InProcess); + + for (int i = 0; i < 10; i++) + { + + // send first request and add app_offline while app is starting + var runningTask = AssertAppOffline(deploymentResult); + + // This test tries to hit a race where we drop app_offline file while + // in process application is starting, application start takes at least 400ms + // so we back off for 100ms to allow request to reach request handler + // Test itself is racy and can result in two scenarios + // 1. ANCM detects app_offline before it starts the request - if AssertAppOffline succeeds we've hit it + // 2. Intended scenario where app starts and then shuts down + // In first case we remove app_offline and try again + await Task.Delay(100); + + AddAppOffline(deploymentResult.ContentRoot); + + try + { + await runningTask.TimeoutAfterDefault(); + + // if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app + // try again + RemoveAppOffline(deploymentResult.ContentRoot); + } + catch + { + AssertStopsProcess(deploymentResult); + return; + } + } + + Assert.True(false); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_InProcess() + { + var deploymentResult = await AssertStarts(HostingModel.InProcess); + + AddAppOffline(deploymentResult.ContentRoot); + + AssertStopsProcess(deploymentResult); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_OutOfProcess() + { + var deploymentResult = await AssertStarts(HostingModel.OutOfProcess); + + // Repeat dropping file and restarting multiple times + for (int i = 0; i < 5; i++) + { + AddAppOffline(deploymentResult.ContentRoot); + await AssertAppOffline(deploymentResult); + RemoveAppOffline(deploymentResult.ContentRoot); + await AssertRunning(deploymentResult); + } + + AddAppOffline(deploymentResult.ContentRoot); + await AssertAppOffline(deploymentResult); + DeletePublishOutput(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDropped_CanRemoveAppOfflineAfterAddingAndSiteWorks(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + AddAppOffline(deploymentResult.ContentRoot); + + await AssertAppOffline(deploymentResult); + + RemoveAppOffline(deploymentResult.ContentRoot); + + await AssertRunning(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineAddedAndRemovedStress(HostingModel hostingModel) + { + var deploymentResult = await AssertStarts(hostingModel); + + var load = Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => { + var statusCode = (int)response.StatusCode; + Assert.True(statusCode == 200 || statusCode == 503, "Status code was " + statusCode); + }); + + for (int i = 0; i < 100; i++) + { + // AddAppOffline might fail if app_offline is being read by ANCM and deleted at the same time + RetryHelper.RetryOperation( + () => AddAppOffline(deploymentResult.ContentRoot), + e => Logger.LogError($"Failed to create app_offline : {e.Message}"), + retryCount: 3, + retryDelayMilliseconds: 100); + RemoveAppOffline(deploymentResult.ContentRoot); + } + + try + { + await load; + } + catch (HttpRequestException ex) when (ex.InnerException is IOException | ex.InnerException is SocketException) + { + // IOException in InProcess is fine, just means process stopped + if (hostingModel != HostingModel.InProcess) + { + throw; + } + } + } + + + private async Task DeployApp(HostingModel hostingModel = HostingModel.InProcess) + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters(hostingModel: hostingModel, publish: true); + + return await DeployAsync(deploymentParameters); + } + + private void AddAppOffline(string appPath, string content = "The app is offline.") + { + File.WriteAllText(Path.Combine(appPath, "app_offline.htm"), content); + } + + private void RemoveAppOffline(string appPath) + { + RetryHelper.RetryOperation( + () => File.Delete(Path.Combine(appPath, "app_offline.htm")), + e => Logger.LogError($"Failed to remove app_offline : {e.Message}"), + retryCount: 3, + retryDelayMilliseconds: 100); + } + + private async Task AssertAppOffline(IISDeploymentResult deploymentResult, string expectedResponse = "The app is offline.") + { + HttpResponseMessage response = null; + + for (var i = 0; i < 5; i++) + { + // Keep retrying until app_offline is present. + response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); + if (!response.IsSuccessStatusCode) + { + break; + } + } + + Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode); + + Assert.Equal(expectedResponse, await response.Content.ReadAsStringAsync()); + } + + private void AssertStopsProcess(IISDeploymentResult deploymentResult) + { + var hostShutdownToken = deploymentResult.HostShutdownToken; + + Assert.True(hostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); + Assert.True(hostShutdownToken.IsCancellationRequested); + } + + private async Task AssertStarts(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + 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); + } + + private void DeletePublishOutput(IISDeploymentResult deploymentResult) + { + foreach (var file in Directory.GetFiles(deploymentResult.ContentRoot, "*", SearchOption.AllDirectories)) + { + // Out of process module dll is allowed to be locked + var name = Path.GetFileName(file); + if (name == "aspnetcore.dll" || name == "aspnetcorev2.dll" || name == "aspnetcorev2_outofprocess.dll") + { + continue; + } + File.Delete(file); + } + } + + } +} diff --git a/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs b/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs index 74af2e00cb..aa79727e19 100644 --- a/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs +++ b/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs @@ -43,25 +43,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalFact] public async Task GetServerVariableDoesNotCrash() { - async Task RunRequests() - { - var client = new HttpClient() { BaseAddress = _fixture.Client.BaseAddress }; - - for (int j = 0; j < 10; j++) - { - var response = await client.GetStringAsync("/GetServerVariableStress"); - Assert.StartsWith("Response Begin", response); - Assert.EndsWith("Response End", response); - } - } - - List tasks = new List(); - for (int i = 0; i < 10; i++) - { - tasks.Add(Task.Run(RunRequests)); - } - - await Task.WhenAll(tasks); + await Helpers.StressLoad(_fixture.Client, "/GetServerVariableStress", response => { + var text = response.Content.ReadAsStringAsync().Result; + Assert.StartsWith("Response Begin", text); + Assert.EndsWith("Response End", text); + }); } } } diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index f4348514d1..c067ff46e6 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.AspNetCore.Server.IntegrationTesting; @@ -51,5 +52,27 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal("Hello World", responseText); } + + public static async Task StressLoad(HttpClient httpClient, string path, Action action) + { + async Task RunRequests() + { + var connection = new HttpClient() { BaseAddress = httpClient.BaseAddress }; + + for (int j = 0; j < 10; j++) + { + var response = await connection.GetAsync(path); + action(response); + } + } + + List tasks = new List(); + for (int i = 0; i < 10; i++) + { + tasks.Add(Task.Run(RunRequests)); + } + + await Task.WhenAll(tasks); + } } } diff --git a/test/Common.FunctionalTests/Utilities/IISCapability.cs b/test/Common.FunctionalTests/Utilities/IISCapability.cs index e3a5bf6c4e..d62716db09 100644 --- a/test/Common.FunctionalTests/Utilities/IISCapability.cs +++ b/test/Common.FunctionalTests/Utilities/IISCapability.cs @@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests None = 0, Websockets = 1, WindowsAuthentication = 2, - PoolEnvironmentVariables = 4 + PoolEnvironmentVariables = 4, + ShutdownToken = 8 } } diff --git a/test/IIS.FunctionalTests/RequiresIISAttribute.cs b/test/IIS.FunctionalTests/RequiresIISAttribute.cs index 9befaeba96..f94459fd8f 100644 --- a/test/IIS.FunctionalTests/RequiresIISAttribute.cs +++ b/test/IIS.FunctionalTests/RequiresIISAttribute.cs @@ -130,6 +130,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests _skipReason += "The machine does allow for setting environment variables on application pools."; } } + + if (capabilities.HasFlag(IISCapability.ShutdownToken)) + { + _isMet = false; + _skipReason += "https://github.com/aspnet/IISIntegration/issues/1074"; + } } public bool IsMet => _isMet; diff --git a/test/IIS.Tests/TestServerTest.cs b/test/IIS.Tests/TestServerTest.cs index c168af99d8..79b1c42113 100644 --- a/test/IIS.Tests/TestServerTest.cs +++ b/test/IIS.Tests/TestServerTest.cs @@ -19,17 +19,17 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var helloWorld = "Hello World"; var expectedPath = "/Path"; - string path = null; - using (var testServer = await TestServer.Create(ctx => - { - path = ctx.Request.Path.ToString(); - return ctx.Response.WriteAsync(helloWorld); - }, LoggerFactory)) - { - var result = await testServer.HttpClient.GetAsync(expectedPath); - Assert.Equal(helloWorld, await result.Content.ReadAsStringAsync()); - Assert.Equal(expectedPath, path); + string path = null; + using (var testServer = await TestServer.Create(ctx => + { + path = ctx.Request.Path.ToString(); + return ctx.Response.WriteAsync(helloWorld); + }, LoggerFactory)) + { + var result = await testServer.HttpClient.GetAsync(expectedPath); + Assert.Equal(helloWorld, await result.Content.ReadAsStringAsync()); + Assert.Equal(expectedPath, path); + } } } - } } diff --git a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs deleted file mode 100644 index 265907542f..0000000000 --- a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -// 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; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.AspNetCore.Testing.xunit; -using Microsoft.Extensions.Logging; -using Xunit; -using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; - -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess -{ - public class AppOfflineTests : IISFunctionalTestBase - { - // TODO these will differ between IIS and IISExpress - [ConditionalTheory] - [InlineData(HostingModel.InProcess)] - [InlineData(HostingModel.OutOfProcess)] - public async Task AppOfflineDroppedWhileSiteIsDown_SiteReturns503(HostingModel hostingModel) - { - var deploymentResult = await DeployApp(hostingModel); - - AddAppOffline(deploymentResult.ContentRoot); - - await AssertAppOffline(deploymentResult); - } - - [ConditionalTheory] - [InlineData(HostingModel.InProcess)] - [InlineData(HostingModel.OutOfProcess)] - public async Task AppOfflineDroppedWhileSiteIsDown_CustomResponse(HostingModel hostingModel) - { - var expectedResponse = "The app is offline."; - var deploymentResult = await DeployApp(hostingModel); - - AddAppOffline(deploymentResult.ContentRoot, expectedResponse); - - await AssertAppOffline(deploymentResult, expectedResponse); - } - - [ConditionalFact] - public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_InProcess() - { - var deploymentResult = await AssertStarts(HostingModel.InProcess); - - AddAppOffline(deploymentResult.ContentRoot); - - await AssertStopsProcess(deploymentResult); - } - - [ConditionalFact] - public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_OutOfProcess() - { - var deploymentResult = await AssertStarts(HostingModel.OutOfProcess); - - // Repeat dropping file and restarting multiple times - for (int i = 0; i < 5; i++) - { - AddAppOffline(deploymentResult.ContentRoot); - await AssertAppOffline(deploymentResult); - RemoveAppOffline(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.ContentRoot); - - await AssertAppOffline(deploymentResult); - - RemoveAppOffline(deploymentResult.ContentRoot); - - await AssertRunning(deploymentResult); - } - - private async Task DeployApp(HostingModel hostingModel = HostingModel.InProcess) - { - var deploymentParameters = Helpers.GetBaseDeploymentParameters(hostingModel: hostingModel, publish: true); - - return await DeployAsync(deploymentParameters); - } - - private void AddAppOffline(string appPath, string content = "") - { - File.WriteAllText(Path.Combine(appPath, "app_offline.htm"), content); - } - - private void RemoveAppOffline(string appPath) - { - RetryHelper.RetryOperation( - () => File.Delete(Path.Combine(appPath, "app_offline.htm")), - e => Logger.LogError($"Failed to remove app_offline : {e.Message}"), - retryCount: 3, - retryDelayMilliseconds: 100); - } - - private async Task AssertAppOffline(IISDeploymentResult deploymentResult, string expectedResponse = "") - { - var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); - - for (var i = 0; response.IsSuccessStatusCode && i < 5; i++) - { - // Keep retrying until app_offline is present. - response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); - } - - Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode); - - Assert.Equal(expectedResponse, await response.Content.ReadAsStringAsync()); - } - - private async Task AssertStopsProcess(IISDeploymentResult deploymentResult) - { - try - { - var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); - } - catch (HttpRequestException) - { - // dropping app_offline will kill the process - } - - var hostShutdownToken = deploymentResult.HostShutdownToken; - - Assert.True(hostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); - Assert.True(hostShutdownToken.IsCancellationRequested); - } - - private async Task AssertStarts(HostingModel hostingModel) - { - var deploymentResult = await DeployApp(hostingModel); - - 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); - } - } -} From afa5d60821eef1731482bafd1134a4cfbc05eb48 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 26 Jul 2018 12:12:12 -0700 Subject: [PATCH 2/6] Add content/webroot and currect directory tests (#1103) --- .../WebHostBuilderIISExtensions.cs | 5 ++- .../IISExpressDeployer.cs | 4 ++- .../LoggingHandler.cs | 1 + .../Inprocess/HostingEnvironmentTests.cs | 33 +++++++++++++++++++ .../OutOfProcess/HelloWorldTest.cs | 7 ++++ .../Utilities/IISTestSiteFixture.cs | 4 +-- .../InProcessWebSite/InProcessWebSite.csproj | 6 +++- test/WebSites/InProcessWebSite/Startup.cs | 9 +++++ .../InProcessWebSite/wwwroot/static.txt | 0 .../OutOfProcessWebSite.csproj | 4 +++ test/WebSites/OutOfProcessWebSite/Program.cs | 2 ++ test/WebSites/OutOfProcessWebSite/Startup.cs | 10 ++++++ .../OutOfProcessWebSite/wwwroot/static.txt | 0 13 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs create mode 100644 test/WebSites/InProcessWebSite/wwwroot/static.txt create mode 100644 test/WebSites/OutOfProcessWebSite/wwwroot/static.txt diff --git a/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs b/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs index 6ce99f7832..146f9a50f5 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; @@ -32,7 +33,9 @@ namespace Microsoft.AspNetCore.Hosting hostBuilder.CaptureStartupErrors(true); var iisConfigData = NativeMethods.HttpGetApplicationProperties(); - hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath); + // Trim trailing slash to be consistent with other servers + var contentRoot = iisConfigData.pwzFullApplicationPath.TrimEnd(Path.DirectorySeparatorChar); + hostBuilder.UseContentRoot(contentRoot); return hostBuilder.ConfigureServices( services => { services.AddSingleton(new IISNativeApplication(iisConfigData.pNativeApplication)); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs index 0fea5fa391..6d99edf0fd 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs @@ -175,7 +175,9 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS UseShellExecute = false, CreateNoWindow = true, RedirectStandardError = true, - RedirectStandardOutput = true + RedirectStandardOutput = true, + // VS sets current directory to C:\Program Files\IIS Express + WorkingDirectory = Path.GetDirectoryName(iisExpressPath) }; AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs index 241991f62d..d9067c038e 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs @@ -44,6 +44,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS offset += count; } while (count != 0 && offset != buffer.Length); + readAsStreamAsync.Position = 0; _logger.Log(logLevel, Encoding.ASCII.GetString(buffer, 0, offset)); } } diff --git a/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs b/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs new file mode 100644 index 0000000000..061b828a6c --- /dev/null +++ b/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs @@ -0,0 +1,33 @@ +// 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; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class HostingEnvironmentTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public HostingEnvironmentTests(IISTestSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task HostingEnvironmentIsCorrect() + { + Assert.Equal( + $"ContentRootPath {_fixture.DeploymentResult.ContentRoot}" + Environment.NewLine + + $"WebRootPath {_fixture.DeploymentResult.ContentRoot}\\wwwroot" + Environment.NewLine + + $"CurrentDirectory {Path.GetDirectoryName(_fixture.DeploymentResult.HostProcess.MainModule.FileName)}", + await _fixture.Client.GetStringAsync("/HostingEnvironment")); + } + } +} diff --git a/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs index 8ae0c7dc89..e868207bb8 100644 --- a/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ b/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -1,6 +1,7 @@ // 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; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; @@ -58,6 +59,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests responseText = await response.Content.ReadAsStringAsync(); Assert.True("backcompat;Windows".Equals(responseText) || "latest;null".Equals(responseText), "Auth"); + + Assert.Equal( + $"ContentRootPath {deploymentResult.ContentRoot}" + Environment.NewLine + + $"WebRootPath {deploymentResult.ContentRoot}\\wwwroot" + Environment.NewLine + + $"CurrentDirectory {deploymentResult.ContentRoot}", + await deploymentResult.HttpClient.GetStringAsync("/HostingEnvironment")); } } } diff --git a/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs b/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs index 787276a8fa..fc5a3b6df1 100644 --- a/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests _deployer = IISApplicationDeployerFactory.Create(deploymentParameters, loggerFactory); - DeploymentResult = _deployer.DeployAsync().Result; + DeploymentResult = (IISDeploymentResult)_deployer.DeployAsync().Result; Client = DeploymentResult.HttpClient; BaseUri = DeploymentResult.ApplicationBaseUri; ShutdownToken = DeploymentResult.HostShutdownToken; @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public string BaseUri { get; } public HttpClient Client { get; } public CancellationToken ShutdownToken { get; } - public DeploymentResult DeploymentResult { get; } + public IISDeploymentResult DeploymentResult { get; } public TestConnection CreateTestConnection() { diff --git a/test/WebSites/InProcessWebSite/InProcessWebSite.csproj b/test/WebSites/InProcessWebSite/InProcessWebSite.csproj index e5158c8cad..b6d09409c1 100644 --- a/test/WebSites/InProcessWebSite/InProcessWebSite.csproj +++ b/test/WebSites/InProcessWebSite/InProcessWebSite.csproj @@ -1,4 +1,4 @@ - + @@ -13,6 +13,10 @@ + + + + diff --git a/test/WebSites/InProcessWebSite/Startup.cs b/test/WebSites/InProcessWebSite/Startup.cs index f42e220751..7142f627d3 100644 --- a/test/WebSites/InProcessWebSite/Startup.cs +++ b/test/WebSites/InProcessWebSite/Startup.cs @@ -43,6 +43,15 @@ namespace IISTestSite }); } + private async Task HostingEnvironment(HttpContext context) + { + var hostingEnv = context.RequestServices.GetService(); + + await context.Response.WriteAsync("ContentRootPath "+hostingEnv.ContentRootPath + Environment.NewLine); + await context.Response.WriteAsync("WebRootPath "+hostingEnv.WebRootPath + Environment.NewLine); + await context.Response.WriteAsync("CurrentDirectory "+Environment.CurrentDirectory); + } + private void AuthenticationRestricted(IApplicationBuilder app) { app.Run(async ctx => diff --git a/test/WebSites/InProcessWebSite/wwwroot/static.txt b/test/WebSites/InProcessWebSite/wwwroot/static.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj b/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj index 399a22e279..36ca6e2b54 100644 --- a/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj +++ b/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj @@ -14,6 +14,10 @@ + + + + diff --git a/test/WebSites/OutOfProcessWebSite/Program.cs b/test/WebSites/OutOfProcessWebSite/Program.cs index 52be26b80c..a5943f5f00 100644 --- a/test/WebSites/OutOfProcessWebSite/Program.cs +++ b/test/WebSites/OutOfProcessWebSite/Program.cs @@ -1,6 +1,7 @@ // 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.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; @@ -16,6 +17,7 @@ namespace TestSites factory.AddConsole(); factory.AddFilter("Console", level => level >= LogLevel.Information); }) + .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .UseKestrel() diff --git a/test/WebSites/OutOfProcessWebSite/Startup.cs b/test/WebSites/OutOfProcessWebSite/Startup.cs index 185aecb7f4..be44480891 100644 --- a/test/WebSites/OutOfProcessWebSite/Startup.cs +++ b/test/WebSites/OutOfProcessWebSite/Startup.cs @@ -9,6 +9,7 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.IISIntegration.FunctionalTests; @@ -104,5 +105,14 @@ namespace TestSites return context.Response.WriteAsync(context.Request.Headers["ANCMRHPath"]); } + + private async Task HostingEnvironment(HttpContext context) + { + var hostingEnv = context.RequestServices.GetService(); + + await context.Response.WriteAsync("ContentRootPath "+hostingEnv.ContentRootPath + Environment.NewLine); + await context.Response.WriteAsync("WebRootPath "+hostingEnv.WebRootPath + Environment.NewLine); + await context.Response.WriteAsync("CurrentDirectory "+Environment.CurrentDirectory); + } } } diff --git a/test/WebSites/OutOfProcessWebSite/wwwroot/static.txt b/test/WebSites/OutOfProcessWebSite/wwwroot/static.txt new file mode 100644 index 0000000000..e69de29bb2 From 7067d92c75feaaec4abe8e98162b715588b65c3e Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 26 Jul 2018 14:15:50 -0700 Subject: [PATCH 3/6] Limit RH native assets to netcoreapp2.2 (#1105) --- .../Microsoft.AspNetCore.Server.IIS.csproj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj b/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj index 76c3ecfe5c..c2fa16aa42 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj +++ b/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj @@ -9,6 +9,7 @@ aspnetcore;iis true true + netcoreapp2.2 @@ -23,11 +24,11 @@ - - + + - - + + From f901bca8a7183d7c0d93262fb597bddafc535557 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 27 Jul 2018 15:00:04 -0700 Subject: [PATCH 4/6] Fixing jenkins agents. (#1106) --- build/buildpipeline/windows.groovy | 2 +- build/dependencies.props | 4 ++-- .../IISDeployer.cs | 4 +++- test/Common.FunctionalTests/AppHostConfig/IIS.config | 2 -- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/buildpipeline/windows.groovy b/build/buildpipeline/windows.groovy index dcd6894cb7..3aa3d99283 100644 --- a/build/buildpipeline/windows.groovy +++ b/build/buildpipeline/windows.groovy @@ -9,7 +9,7 @@ simpleNode('Windows.10.Amd64.EnterpriseRS3.ASPNET.Open') { } stage ('Build') { def logFolder = getLogFolder() - def environment = "\$env:ASPNETCORE_TEST_LOG_DIR='${WORKSPACE}\\${logFolder}';\$env:ASPNETCORE_TEST_SKIP_IIS='true';" + def environment = "\$env:ASPNETCORE_TEST_LOG_DIR='${WORKSPACE}\\${logFolder}'" bat "powershell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command \"&.\\tools\\update_schema.ps1;${environment};&.\\run.cmd -CI default-build /p:Configuration=${params.Configuration}\"" } } diff --git a/build/dependencies.props b/build/dependencies.props index 665c81f665..9dc782a008 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -14,7 +14,7 @@ 2.2.0-preview1-34755 2.2.0-preview1-34755 2.2.0-preview1-34755 - 0.6.0-preview1-34755 + 0.6.0-a-preview1-IISVariants-17101 2.2.0-preview1-34755 2.2.0-preview1-34755 2.2.0-preview1-34755 diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs index 3f7425fce6..18b8e5d2ea 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs @@ -61,7 +61,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS // For now, only support using published output DeploymentParameters.PublishApplicationBeforeDeployment = true; - if (DeploymentParameters.ApplicationType == ApplicationType.Portable) { DefaultWebConfigActions.Add( @@ -75,6 +74,9 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS DotnetPublish(); contentRoot = DeploymentParameters.PublishedApplicationRootPath; IISDeploymentParameters.AddDebugLogToWebConfig(Path.Combine(contentRoot, $"{_application.WebSiteName}.txt")); + DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection( + key: "modules", + value: DeploymentParameters.AncmVersion.ToString())); RunWebConfigActions(); } diff --git a/test/Common.FunctionalTests/AppHostConfig/IIS.config b/test/Common.FunctionalTests/AppHostConfig/IIS.config index 4d30d2ad0f..c8c4848e25 100644 --- a/test/Common.FunctionalTests/AppHostConfig/IIS.config +++ b/test/Common.FunctionalTests/AppHostConfig/IIS.config @@ -225,7 +225,6 @@ - @@ -282,7 +281,6 @@ - From 12011b3809ad078eff4c76398b13a44ff0a8e179 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 27 Jul 2018 16:11:17 -0700 Subject: [PATCH 5/6] Cleanup testsite module locations (#1110) --- build/applicationhost.config | 2 +- build/applicationhost.iis.config | 2 +- build/launchSettings.json | 6 ++++-- samples/NativeIISSample/Properties/launchSettings.json | 6 ++++-- .../InProcessWebSite/Properties/launchSettings.json | 6 ++++-- .../OutOfProcessWebSite/Properties/launchSettings.json | 6 ++++-- .../OverriddenServerWebSite/Properties/launchSettings.json | 6 ++++-- .../StartupExceptionWebSite/Properties/launchSettings.json | 6 ++++-- .../StressTestWebSite/Properties/launchSettings.json | 6 ++++-- 9 files changed, 30 insertions(+), 16 deletions(-) diff --git a/build/applicationhost.config b/build/applicationhost.config index 8c7429db8d..ce00a887b4 100644 --- a/build/applicationhost.config +++ b/build/applicationhost.config @@ -233,7 +233,7 @@ - + diff --git a/build/applicationhost.iis.config b/build/applicationhost.iis.config index e7771484cb..34a4da59e6 100644 --- a/build/applicationhost.iis.config +++ b/build/applicationhost.iis.config @@ -226,7 +226,7 @@ - + diff --git a/build/launchSettings.json b/build/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/build/launchSettings.json +++ b/build/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", diff --git a/samples/NativeIISSample/Properties/launchSettings.json b/samples/NativeIISSample/Properties/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/samples/NativeIISSample/Properties/launchSettings.json +++ b/samples/NativeIISSample/Properties/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", diff --git a/test/WebSites/InProcessWebSite/Properties/launchSettings.json b/test/WebSites/InProcessWebSite/Properties/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/test/WebSites/InProcessWebSite/Properties/launchSettings.json +++ b/test/WebSites/InProcessWebSite/Properties/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", diff --git a/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json b/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json +++ b/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", diff --git a/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json b/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json +++ b/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", diff --git a/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json b/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json +++ b/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", diff --git a/test/WebSites/StressTestWebSite/Properties/launchSettings.json b/test/WebSites/StressTestWebSite/Properties/launchSettings.json index 0ef9e81546..943d2ad712 100644 --- a/test/WebSites/StressTestWebSite/Properties/launchSettings.json +++ b/test/WebSites/StressTestWebSite/Properties/launchSettings.json @@ -15,7 +15,8 @@ "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", @@ -28,7 +29,8 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCMV2_PATH": "$(TargetDir)$(AncmV2Path)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", "LAUNCHER_PATH": "$(DotNetPath)", From c6ba21efc14a145532fef8887ef6c81e9f071830 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Fri, 27 Jul 2018 16:34:21 -0700 Subject: [PATCH 6/6] Add VSTS ci pipeline (#893) --- .vsts-pipelines/builds/ci-internal.yml | 13 +++++++++++++ .vsts-pipelines/builds/ci-public.yml | 15 +++++++++++++++ .vsts-pipelines/templates/build-steps.yml | 17 +++++++++++++++++ test/Common.Tests/Common.Tests.csproj | 3 +-- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 .vsts-pipelines/builds/ci-internal.yml create mode 100644 .vsts-pipelines/builds/ci-public.yml create mode 100644 .vsts-pipelines/templates/build-steps.yml diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml new file mode 100644 index 0000000000..260baf4536 --- /dev/null +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -0,0 +1,13 @@ +trigger: +- master +- release/* + +resources: + repositories: + - repository: buildtools + type: git + name: aspnet-BuildTools + ref: refs/heads/release/2.2 + +phases: +- template: ../templates/build-steps.yml diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml new file mode 100644 index 0000000000..0ed1a241ab --- /dev/null +++ b/.vsts-pipelines/builds/ci-public.yml @@ -0,0 +1,15 @@ +trigger: +- master +- release/* + +# See https://github.com/aspnet/BuildTools +resources: + repositories: + - repository: buildtools + type: github + endpoint: DotNet-Bot GitHub Connection + name: aspnet/BuildTools + ref: refs/heads/release/2.2 + +phases: +- template: ../templates/build-steps.yml diff --git a/.vsts-pipelines/templates/build-steps.yml b/.vsts-pipelines/templates/build-steps.yml new file mode 100644 index 0000000000..40843183ea --- /dev/null +++ b/.vsts-pipelines/templates/build-steps.yml @@ -0,0 +1,17 @@ +phases: +- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools + parameters: + agentOs: Windows + beforeBuild: + - powershell: "& ./tools/update_schema.ps1" + displayName: Update ANCM schema + - powershell: Restart-Service w3svc + displayName: Restart IIS + +- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools + parameters: + agentOs: macOS + +- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools + parameters: + agentOs: Linux diff --git a/test/Common.Tests/Common.Tests.csproj b/test/Common.Tests/Common.Tests.csproj index 50b978f517..ede80732ee 100644 --- a/test/Common.Tests/Common.Tests.csproj +++ b/test/Common.Tests/Common.Tests.csproj @@ -2,6 +2,7 @@ netcoreapp2.2 + false @@ -13,11 +14,9 @@ - -