Fix dotnet.exe abort exception and startup/shutdown timeouts (#1352)

This commit is contained in:
Pavel Krymets 2018-08-31 09:05:46 -07:00 committed by GitHub
parent db01ae3717
commit 0459b6d0d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 962 additions and 756 deletions

View File

@ -56,10 +56,10 @@ HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, Shim
std::unique_ptr<IOutputManager> outputManager;
RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create(
NULL,
pConfiguration.QueryProcessPath().c_str(),
L"",
pConfiguration.QueryProcessPath(),
pApplication.GetApplicationPhysicalPath(),
pConfiguration.QueryArguments().c_str(),
pConfiguration.QueryArguments(),
options));
location = options->GetDotnetExeLocation();

View File

@ -8,8 +8,6 @@
#include "exceptions.h"
#include <unordered_map>
#define DEFAULT_HASH_BUCKETS 17
//
// This class will manage the lifecycle of all Asp.Net Core applciation
// It should be global singleton.

View File

@ -195,7 +195,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="application.h" />
<ClInclude Include="ConfigurationLoadException.h" />
<ClInclude Include="ConfigurationSection.h" />
<ClInclude Include="ConfigurationSource.h" />
<ClInclude Include="config_utility.h" />
@ -211,9 +210,11 @@
<ClInclude Include="hostfxr_utility.h" />
<ClInclude Include="iapplication.h" />
<ClInclude Include="debugutil.h" />
<ClInclude Include="InvalidOperationException.h" />
<ClInclude Include="IOutputManager.h" />
<ClInclude Include="irequesthandler.h" />
<ClInclude Include="LoggingHelpers.h" />
<ClInclude Include="ModuleHelpers.h" />
<ClInclude Include="NonCopyable.h" />
<ClInclude Include="NullOutputManager.h" />
<ClInclude Include="PipeOutputManager.h" />

View File

@ -26,6 +26,16 @@ bool ConfigurationSection::GetRequiredBool(const std::wstring& name) const
return result.value();
}
DWORD ConfigurationSection::GetRequiredLong(const std::wstring& name) const
{
auto result = GetLong(name);
if (!result.has_value())
{
ThrowRequiredException(name);
}
return result.value();
}
DWORD ConfigurationSection::GetRequiredTimespan(const std::wstring& name) const
{
auto result = GetTimespan(name);

View File

@ -17,6 +17,8 @@
#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath"
#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments"
#define CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT L""
#define CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT L"startupTimeLimit"
#define CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT L"shutdownTimeLimit"
#define CS_ASPNETCORE_HOSTING_MODEL_OUTOFPROCESS L"outofprocess"
#define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess"
#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel"
@ -31,10 +33,12 @@ public:
virtual ~ConfigurationSection() = default;
virtual std::optional<std::wstring> GetString(const std::wstring& name) const = 0;
virtual std::optional<bool> GetBool(const std::wstring& name) const = 0;
virtual std::optional<DWORD> GetLong(const std::wstring& name) const = 0;
virtual std::optional<DWORD> GetTimespan(const std::wstring& name) const = 0;
std::wstring GetRequiredString(const std::wstring& name) const;
bool GetRequiredBool(const std::wstring& name) const;
DWORD GetRequiredLong(const std::wstring& name) const;
DWORD GetRequiredTimespan(const std::wstring& name) const;
virtual std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(const std::wstring& name) const = 0;

View File

@ -0,0 +1,22 @@
// 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 <string>
class InvalidOperationException: public std::runtime_error
{
public:
InvalidOperationException(std::wstring msg)
: runtime_error("InvalidOperationException"), message(std::move(msg))
{
}
std::wstring as_wstring() const
{
return message;
}
private:
std::wstring message;
};

View File

@ -0,0 +1,20 @@
// 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 "HandleWrapper.h"
#include "exceptions.h"
extern HMODULE g_hModule;
class ModuleHelpers
{
public:
static
void IncrementCurrentModuleRefCount(HandleWrapper<ModuleHandleTraits> &handle)
{
WCHAR path[MAX_PATH];
THROW_LAST_ERROR_IF(!GetModuleFileName(g_hModule, path, sizeof(path)));
THROW_LAST_ERROR_IF(!GetModuleHandleEx(0, path, &handle));
}
};

View File

@ -28,6 +28,17 @@ std::optional<bool> WebConfigConfigurationSection::GetBool(const std::wstring& n
return std::make_optional(result);
}
std::optional<DWORD> WebConfigConfigurationSection::GetLong(const std::wstring& name) const
{
DWORD result;
if (FAILED_LOG(GetElementDWORDProperty(m_element, name.c_str(), &result)))
{
return std::nullopt;
}
return std::make_optional(result);
}
std::optional<DWORD> WebConfigConfigurationSection::GetTimespan(const std::wstring& name) const
{
ULONGLONG result;

View File

@ -17,6 +17,7 @@ public:
std::optional<std::wstring> GetString(const std::wstring& name) const override;
std::optional<bool> GetBool(const std::wstring& name) const override;
std::optional<DWORD> GetLong(const std::wstring& name) const override;
std::optional<DWORD> GetTimespan(const std::wstring& name) const override;
std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(const std::wstring& name) const override;

View File

@ -7,6 +7,7 @@
#include "iapplication.h"
#include "ntassert.h"
#include "SRWExclusiveLock.h"
#include "SRWSharedLock.h"
class APPLICATION : public IAPPLICATION
{
@ -15,11 +16,6 @@ public:
APPLICATION(const APPLICATION&) = delete;
const APPLICATION& operator=(const APPLICATION&) = delete;
APPLICATION_STATUS
QueryStatus() override
{
return m_fStopCalled ? APPLICATION_STATUS::RECYCLED : APPLICATION_STATUS::RUNNING;
}
APPLICATION(const IHttpApplication& pHttpApplication)
: m_fStopCalled(false),
@ -32,6 +28,12 @@ public:
m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath);
}
APPLICATION_STATUS
QueryStatus() override
{
SRWSharedLock stateLock(m_stateLock);
return m_fStopCalled ? APPLICATION_STATUS::RECYCLED : APPLICATION_STATUS::RUNNING;
}
VOID
Stop(bool fServerInitiated) override

View File

@ -3,10 +3,12 @@
#pragma once
#include <exception>
#include <system_error>
#include "debugutil.h"
#include "StringHelpers.h"
#include "InvalidOperationException.h"
#define LOCATION_INFO_ENABLED TRUE
@ -41,10 +43,10 @@
#define FINISHED_LAST_ERROR_IF(condition) do { if (condition) { hr = LogLastError(LOCATION_INFO); goto Finished; }} while (0, 0)
#define FINISHED_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { hr = LogLastError(LOCATION_INFO); goto Finished; }} while (0, 0)
#define THROW_LAST_ERROR() do { ThrowResultException(LogLastError(LOCATION_INFO)); } while (0, 0)
#define THROW_LAST_ERROR() do { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); } while (0, 0)
#define THROW_IF_FAILED(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { ThrowResultException(LOCATION_INFO, __hrRet); }} while (0, 0)
#define THROW_LAST_ERROR_IF(condition) do { if (condition) { ThrowResultException(LogLastError(LOCATION_INFO)); }} while (0, 0)
#define THROW_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { ThrowResultException(LogLastError(LOCATION_INFO)); }} while (0, 0)
#define THROW_LAST_ERROR_IF(condition) do { if (condition) { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); }} while (0, 0)
#define THROW_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); }} while (0, 0)
#define THROW_IF_NULL_ALLOC(ptr) Throw_IfNullAlloc(ptr)
@ -152,3 +154,7 @@ template <typename PointerT> auto Throw_IfNullAlloc(PointerT pointer)
}
return pointer;
}
__declspec(noinline) inline std::wstring GetUnexpectedExceptionMessage(std::runtime_error& ex)
{
return format(L"Unexpected exception: %S", ex.what());
}

View File

@ -41,7 +41,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
}
else if (!ends_with(expandedProcessPath, L".exe", true))
{
throw StartupParametersResolutionException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str()));
throw InvalidOperationException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str()));
}
// Check if the absolute path is to dotnet or not.
@ -51,7 +51,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
if (applicationArguments.empty())
{
throw StartupParametersResolutionException(L"Application arguments are empty.");
throw InvalidOperationException(L"Application arguments are empty.");
}
if (dotnetExePath.empty())
@ -92,7 +92,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
LOG_INFOF(L"Checking application.dll at '%ls'", applicationDllPath.c_str());
if (!is_regular_file(applicationDllPath))
{
throw StartupParametersResolutionException(format(L"Application .dll was not found at %s", applicationDllPath.c_str()));
throw InvalidOperationException(format(L"Application .dll was not found at %s", applicationDllPath.c_str()));
}
hostFxrDllPath = executablePath.parent_path() / "hostfxr.dll";
@ -131,7 +131,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
// If the processPath file does not exist and it doesn't include dotnet.exe or dotnet
// then it is an invalid argument.
//
throw StartupParametersResolutionException(format(L"Executable was not found at '%s'", executablePath.c_str()));
throw InvalidOperationException(format(L"Executable was not found at '%s'", executablePath.c_str()));
}
}
}
@ -185,7 +185,7 @@ HOSTFXR_UTILITY::AppendArguments(
auto pwzArgs = std::unique_ptr<LPWSTR[], LocalFreeDeleter>(CommandLineToArgvW(applicationArguments.c_str(), &argc));
if (!pwzArgs)
{
throw StartupParametersResolutionException(format(L"Unable parse command line arguments '%s'", applicationArguments.c_str()));
throw InvalidOperationException(format(L"Unable parse command line arguments '%s'", applicationArguments.c_str()));
}
for (int intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++)
@ -246,7 +246,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
{
LOG_INFOF(L"Absolute path to dotnet.exe was not found at '%ls'", requestedPath.c_str());
throw StartupParametersResolutionException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str()));
throw InvalidOperationException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str()));
}
const auto dotnetViaWhere = InvokeWhereToFindDotnet();
@ -266,7 +266,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
}
LOG_INFOF(L"dotnet.exe not found");
throw StartupParametersResolutionException(format(
throw InvalidOperationException(format(
L"Could not find dotnet.exe at '%s' or using the system PATH environment variable."
" Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process.",
processPath.c_str()));
@ -284,14 +284,14 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
if (!is_directory(hostFxrBase))
{
throw StartupParametersResolutionException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str()));
throw InvalidOperationException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str()));
}
FindDotNetFolders(hostFxrBase, versionFolders);
if (versionFolders.empty())
{
throw StartupParametersResolutionException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str()));
throw InvalidOperationException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str()));
}
const auto highestVersion = FindHighestDotNetVersion(versionFolders);
@ -299,7 +299,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
if (!is_regular_file(hostFxrPath))
{
throw StartupParametersResolutionException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str()));
throw InvalidOperationException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str()));
}
LOG_INFOF(L"hostfxr.dll located at '%ls'", hostFxrPath.c_str());

View File

@ -29,20 +29,6 @@ public:
std::vector<std::wstring> &arguments
);
class StartupParametersResolutionException: public std::runtime_error
{
public:
StartupParametersResolutionException(std::wstring msg)
: runtime_error("Startup parameter resulution error occured"), message(std::move(msg))
{
}
std::wstring get_message() const { return message; }
private:
std::wstring message;
};
static
void
AppendArguments(
@ -96,7 +82,7 @@ private:
struct LocalFreeDeleter
{
void operator ()(LPWSTR* ptr) const
void operator ()(_In_ LPWSTR* ptr) const
{
LocalFree(ptr);
}

View File

@ -9,18 +9,19 @@
#include "EventLog.h"
HRESULT HOSTFXR_OPTIONS::Create(
_In_ PCWSTR pcwzDotnetExePath,
_In_ PCWSTR pcwzProcessPath,
_In_ PCWSTR pcwzApplicationPhysicalPath,
_In_ PCWSTR pcwzArguments,
_In_ const std::wstring& pcwzDotnetExePath,
_In_ const std::wstring& pcwzProcessPath,
_In_ const std::wstring& pcwzApplicationPhysicalPath,
_In_ const std::wstring& pcwzArguments,
_Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper)
{
std::filesystem::path knownDotnetLocation;
if (pcwzDotnetExePath != nullptr)
if (!pcwzDotnetExePath.empty())
{
knownDotnetLocation = pcwzDotnetExePath;
}
try
{
std::filesystem::path hostFxrDllPath;
@ -40,17 +41,25 @@ HRESULT HOSTFXR_OPTIONS::Create(
}
ppWrapper = std::make_unique<HOSTFXR_OPTIONS>(knownDotnetLocation, hostFxrDllPath, arguments);
}
catch (HOSTFXR_UTILITY::StartupParametersResolutionException &resolutionException)
catch (InvalidOperationException &ex)
{
OBSERVE_CAUGHT_EXCEPTION();
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
pcwzApplicationPhysicalPath,
resolutionException.get_message().c_str());
pcwzApplicationPhysicalPath.c_str(),
ex.as_wstring().c_str());
return E_FAIL;
RETURN_CAUGHT_EXCEPTION();
}
catch (std::runtime_error &ex)
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
pcwzApplicationPhysicalPath.c_str(),
GetUnexpectedExceptionMessage(ex).c_str());
RETURN_CAUGHT_EXCEPTION();
}
CATCH_RETURN();

View File

@ -48,10 +48,10 @@ public:
static
HRESULT Create(
_In_ PCWSTR pcwzExeLocation,
_In_ PCWSTR pcwzProcessPath,
_In_ PCWSTR pcwzApplicationPhysicalPath,
_In_ PCWSTR pcwzArguments,
_In_ const std::wstring& pcwzExeLocation,
_In_ const std::wstring& pcwzProcessPath,
_In_ const std::wstring& pcwzApplicationPhysicalPath,
_In_ const std::wstring& pcwzArguments,
_Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper);
private:

View File

@ -24,14 +24,14 @@
#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'."
#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'."
#define ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown application '%s'."
#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application, ErrorCode = '0x%x."
#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application. %s"
#define ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG L"Application '%s' has shutdown."
#define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool."
#define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%d' other than the one of running application(s)."
#define ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG L"Configuration load error. %s"
#define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'."
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x'. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
#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_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, exit code = '%d'. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, exit code = '%d'. 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_MONITOR_APPOFFLINE_ERROR_MSG L"Monitoring app_offline.htm failed for application '%s', ErrorCode '0x%x'. "
@ -39,7 +39,8 @@
#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"
#define ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG L"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '%s'."
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information."
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, exception code = '0x%x. Please check the stderr logs for more information."
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, exception code = '0x%x. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
#define ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG L"Could not find the assembly '%s' for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application. Captured output: %s"
#define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress."
#define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully."

View File

@ -3,8 +3,6 @@
#include "InProcessApplicationBase.h"
hostfxr_main_fn InProcessApplicationBase::s_fMainCallback = NULL;
InProcessApplicationBase::InProcessApplicationBase(
IHttpServer& pHttpServer,
IHttpApplication& pHttpApplication)
@ -35,7 +33,21 @@ InProcessApplicationBase::StopInternal(bool fServerInitiated)
}
else
{
exit(0);
// Send WM_QUIT to the main window to initiate graceful shutdown
EnumWindows([](HWND hwnd, LPARAM) -> BOOL
{
DWORD processId;
if (GetWindowThreadProcessId(hwnd, &processId) &&
processId == GetCurrentProcessId() &&
GetConsoleWindow() != hwnd)
{
PostMessage(hwnd, WM_QUIT, 0, 0);
return false;
}
return true;
}, 0);
}
}

View File

@ -3,7 +3,6 @@
#pragma once
#include "application.h"
#include "AppOfflineTrackingApplication.h"
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs
@ -23,8 +22,5 @@ public:
protected:
BOOL m_fRecycleCalled;
IHttpServer& m_pHttpServer;
// Allows to override call to hostfxr_main with custome callback
// used in testing
static hostfxr_main_fn s_fMainCallback;
};

View File

@ -2,6 +2,41 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "InProcessOptions.h"
#include "InvalidOperationException.h"
#include "EventLog.h"
HRESULT InProcessOptions::Create(
IHttpServer& pServer,
IHttpApplication& pHttpApplication,
std::unique_ptr<InProcessOptions>& options)
{
try
{
const WebConfigConfigurationSource configurationSource(pServer.GetAdminManager(), pHttpApplication);
options = std::make_unique<InProcessOptions>(configurationSource);
}
catch (InvalidOperationException& ex)
{
EventLog::Error(
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
ex.as_wstring().c_str());
RETURN_CAUGHT_EXCEPTION();
}
catch (std::runtime_error& ex)
{
EventLog::Error(
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
GetUnexpectedExceptionMessage(ex).c_str());
RETURN_CAUGHT_EXCEPTION();
}
CATCH_RETURN();
return S_OK;
}
InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSource) :
m_fStdoutLogEnabled(false),
@ -18,6 +53,8 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE);
m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE);
m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES);
m_dwStartupTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT) * 1000;
m_dwShutdownTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT) * 1000;
const auto basicAuthSection = configurationSource.GetSection(CS_BASIC_AUTHENTICATION_SECTION);
m_fBasicAuthEnabled = basicAuthSection && basicAuthSection->GetBool(CS_ENABLED).value_or(false);

View File

@ -5,6 +5,7 @@
#include <string>
#include "ConfigurationSource.h"
#include "WebConfigConfigurationSource.h"
class InProcessOptions: NonCopyable
{
@ -60,12 +61,22 @@ public:
DWORD
QueryStartupTimeLimitInMS() const
{
if (IsDebuggerPresent())
{
return INFINITE;
}
return m_dwStartupTimeLimitInMS;
}
DWORD
QueryShutdownTimeLimitInMS() const
{
if (IsDebuggerPresent())
{
return INFINITE;
}
return m_dwShutdownTimeLimitInMS;
}
@ -76,6 +87,12 @@ public:
}
InProcessOptions(const ConfigurationSource &configurationSource);
static
HRESULT InProcessOptions::Create(
IHttpServer& pServer,
IHttpApplication& pHttpApplication,
std::unique_ptr<InProcessOptions>& options);
private:
std::wstring m_strArguments;

View File

@ -5,6 +5,6 @@
HRESULT StartupExceptionApplication::CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler)
{
*pRequestHandler = new StartupExceptionHandler(pHttpContext, m_disableLogs, this);
*pRequestHandler = new StartupExceptionHandler(pHttpContext, m_disableLogs);
return S_OK;
}

View File

@ -16,29 +16,6 @@ public:
: m_disableLogs(disableLogs),
InProcessApplicationBase(pServer, pApplication)
{
html500Page = std::string("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \
<html xmlns=\"http://www.w3.org/1999/xhtml\"> \
<head> \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" /> \
<title> IIS 500.30 Error </title><style type=\"text/css\"></style></head> \
<body> <div id = \"content\"> \
<div class = \"content-container\"><h3> HTTP Error 500.30 - ANCM In-Process Start Failure </h3></div> \
<div class = \"content-container\"> \
<fieldset> <h4> Common causes of this issue: </h4> \
<ul><li> The application failed to start </li> \
<li> The application started but then stopped </li> \
<li> The application started but threw an exception during startup </li></ul></fieldset> \
</div> \
<div class = \"content-container\"> \
<fieldset><h4> Troubleshooting steps: </h4> \
<ul><li> Check the system event log for error messages </li> \
<li> Enable logging the application process' stdout messages </li> \
<li> Attach a debugger to the application process and inspect </li></ul></fieldset> \
<fieldset><h4> For more information visit: \
<a href=\"https://go.microsoft.com/fwlink/?LinkID=808681\"> <cite> https://go.microsoft.com/fwlink/?LinkID=808681 </cite></a></h4> \
</fieldset> \
</div> \
</div></body></html>");
}
~StartupExceptionApplication() = default;

View File

@ -4,6 +4,30 @@
#include "StartupExceptionApplication.h"
#include "StartupExceptionHandler.h"
std::string StartupExceptionHandler::s_html500Page = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \
<html xmlns=\"http://www.w3.org/1999/xhtml\"> \
<head> \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" /> \
<title> IIS 500.30 Error </title><style type=\"text/css\"></style></head> \
<body> <div id = \"content\"> \
<div class = \"content-container\"><h3> HTTP Error 500.30 - ANCM In-Process Start Failure </h3></div> \
<div class = \"content-container\"> \
<fieldset> <h4> Common causes of this issue: </h4> \
<ul><li> The application failed to start </li> \
<li> The application started but then stopped </li> \
<li> The application started but threw an exception during startup </li></ul></fieldset> \
</div> \
<div class = \"content-container\"> \
<fieldset><h4> Troubleshooting steps: </h4> \
<ul><li> Check the system event log for error messages </li> \
<li> Enable logging the application process' stdout messages </li> \
<li> Attach a debugger to the application process and inspect </li></ul></fieldset> \
<fieldset><h4> For more information visit: \
<a href=\"https://go.microsoft.com/fwlink/?LinkID=808681\"> <cite> https://go.microsoft.com/fwlink/?LinkID=808681 </cite></a></h4> \
</fieldset> \
</div> \
</div></body></html>";
REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler()
{
if (!m_disableLogs)
@ -16,11 +40,9 @@ REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler()
(USHORT)strlen("text/html"),
FALSE
);
const std::string& html500Page = m_pApplication->GetStaticHtml500Content();
DataChunk.DataChunkType = HttpDataChunkFromMemory;
DataChunk.FromMemory.pBuffer = (PVOID)html500Page.c_str();
DataChunk.FromMemory.BufferLength = (ULONG)html500Page.size();
DataChunk.FromMemory.pBuffer = (PVOID)s_html500Page.c_str();
DataChunk.FromMemory.BufferLength = (ULONG)s_html500Page.size();
pResponse->WriteEntityChunkByReference(&DataChunk);
}
else

View File

@ -3,6 +3,7 @@
#pragma once
#include <string>
#include "requesthandler.h"
class StartupExceptionApplication;
@ -10,19 +11,23 @@ class StartupExceptionApplication;
class StartupExceptionHandler : public REQUEST_HANDLER
{
public:
StartupExceptionHandler(IHttpContext* pContext, BOOL disableLogs, StartupExceptionApplication* pApplication)
StartupExceptionHandler(IHttpContext* pContext, BOOL disableLogs)
:
m_pContext(pContext),
m_disableLogs(disableLogs),
m_pApplication(pApplication)
m_disableLogs(disableLogs)
{
}
~StartupExceptionHandler()
{
}
REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override;
private:
IHttpContext * m_pContext;
BOOL m_disableLogs;
StartupExceptionApplication* m_pApplication;
static
std::string s_html500Page;
};

View File

@ -110,39 +110,25 @@ CreateApplication(
return S_OK;
}
const WebConfigConfigurationSource configurationSource(pServer->GetAdminManager(), *pHttpApplication);
auto pConfig = std::make_unique<InProcessOptions>(configurationSource);
BOOL disableStartupPage = pConfig->QueryDisableStartUpErrorPage();
auto pApplication = std::make_unique<IN_PROCESS_APPLICATION>(*pServer, *pHttpApplication, std::move(pConfig), pParameters, nParameters);
// never create two inprocess applications in one process
g_fInProcessApplicationCreated = true;
if (FAILED_LOG(pApplication->LoadManagedApplication()))
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> inProcessApplication;
if (!FAILED_LOG(IN_PROCESS_APPLICATION::Start(*pServer, *pHttpApplication, pParameters, nParameters, inProcessApplication)))
{
*ppApplication = inProcessApplication.release();
}
else
{
std::unique_ptr<InProcessOptions> options;
THROW_IF_FAILED(InProcessOptions::Create(*pServer, *pHttpApplication, options));
// Set the currently running application to a fake application that returns startup exceptions.
auto pErrorApplication = std::make_unique<StartupExceptionApplication>(*pServer, *pHttpApplication, disableStartupPage);
auto pErrorApplication = std::make_unique<StartupExceptionApplication>(*pServer, *pHttpApplication, options->QueryDisableStartUpErrorPage());
RETURN_IF_FAILED(pErrorApplication->StartMonitoringAppOffline());
*ppApplication = pErrorApplication.release();
}
else
{
RETURN_IF_FAILED(pApplication->StartMonitoringAppOffline());
*ppApplication = pApplication.release();
}
}
catch(ConfigurationLoadException &ex)
{
EventLog::Error(
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
ex.get_message().c_str());
RETURN_HR(E_FAIL);
return S_OK;
}
CATCH_RETURN();
return S_OK;
}

View File

@ -6,13 +6,11 @@
#include "hostfxroptions.h"
#include "requesthandler_config.h"
#include "environmentvariablehelpers.h"
#include "SRWExclusiveLock.h"
#include "exceptions.h"
#include "LoggingHelpers.h"
#include "resources.h"
#include "EventLog.h"
const LPCSTR IN_PROCESS_APPLICATION::s_exeLocationParameterName = "InProcessExeLocation";
#include "ModuleHelpers.h"
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
@ -23,10 +21,9 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
APPLICATION_PARAMETER *pParameters,
DWORD nParameters) :
InProcessApplicationBase(pHttpServer, pApplication),
m_ProcessExitCode(0),
m_fBlockCallbacksIntoManaged(FALSE),
m_fShutdownCalledFromNative(FALSE),
m_fShutdownCalledFromManaged(FALSE),
m_Initialized(false),
m_blockManagedCallbacks(true),
m_waitForShutdown(true),
m_pConfig(std::move(pConfig))
{
DBG_ASSERT(m_pConfig);
@ -35,161 +32,54 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
{
if (_stricmp(pParameters[i].pzName, s_exeLocationParameterName) == 0)
{
m_struExeLocation.Copy(reinterpret_cast<PCWSTR>(pParameters[i].pValue));
m_dotnetExeKnownLocation = reinterpret_cast<PCWSTR>(pParameters[i].pValue);
}
}
m_status = MANAGED_APPLICATION_STATUS::STARTING;
}
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
{
s_Application = NULL;
s_Application = nullptr;
}
//static
DWORD WINAPI
IN_PROCESS_APPLICATION::DoShutDown(
LPVOID lpParam
)
{
IN_PROCESS_APPLICATION* pApplication = static_cast<IN_PROCESS_APPLICATION*>(lpParam);
DBG_ASSERT(pApplication);
pApplication->ShutDownInternal();
return 0;
}
__override
VOID
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
{
UNREFERENCED_PARAMETER(fServerInitiated);
HRESULT hr = S_OK;
CHandle hThread;
DWORD dwThreadStatus = 0;
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
if (IsDebuggerPresent())
{
dwTimeout = INFINITE;
}
hThread.Attach(CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE)DoShutDown,
this, // thread function arguments
0, // default creation flags
NULL)); // receive thread identifier
if ((HANDLE)hThread == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (WaitForSingleObject(hThread, dwTimeout) != WAIT_OBJECT_0)
{
// if the thread is still running, we need kill it first before exit to avoid AV
if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE)
{
// Calling back into managed at this point is prone to have AVs
// Calling terminate thread here may be our best solution.
TerminateThread(hThread, STATUS_CONTROL_C_EXIT);
hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
}
}
Finished:
if (FAILED(hr))
{
EventLog::Warn(
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG,
QueryConfigPath().c_str());
}
else
{
EventLog::Info(
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL,
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG,
QueryConfigPath().c_str());
}
{
StopClr();
InProcessApplicationBase::StopInternal(fServerInitiated);
}
VOID
IN_PROCESS_APPLICATION::ShutDownInternal()
{
DWORD dwThreadStatus = 0;
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
IN_PROCESS_APPLICATION::StopClr()
{
LOG_INFO(L"Stopping CLR");
if (IsDebuggerPresent())
if (!m_blockManagedCallbacks)
{
dwTimeout = INFINITE;
}
// We cannot call into managed if the dll is detaching from the process.
// Calling into managed code when the dll is detaching is strictly a bad idea,
// and usually results in an AV saying "The string binding is invalid"
const auto shutdownHandler = m_ShutdownHandler;
if (m_fShutdownCalledFromNative ||
m_status == MANAGED_APPLICATION_STATUS::STARTING ||
m_status == MANAGED_APPLICATION_STATUS::FAIL)
{
return;
}
{
if (m_fShutdownCalledFromNative ||
m_status == MANAGED_APPLICATION_STATUS::STARTING ||
m_status == MANAGED_APPLICATION_STATUS::FAIL)
if (!g_fProcessDetach && shutdownHandler != nullptr)
{
return;
}
// We need to keep track of when both managed and native initiate shutdown
// to avoid AVs. If shutdown has already been initiated in managed, we don't want to call into
// 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 = MANAGED_APPLICATION_STATUS::SHUTDOWN;
if (!m_fShutdownCalledFromManaged)
{
// We cannot call into managed if the dll is detaching from the process.
// Calling into managed code when the dll is detaching is strictly a bad idea,
// and usually results in an AV saying "The string binding is invalid"
if (!g_fProcessDetach)
{
m_ShutdownHandler(m_ShutdownHandlerContext);
m_ShutdownHandler = NULL;
}
}
// Release the lock before we wait on the thread to exit.
}
if (!m_fShutdownCalledFromManaged)
{
if (m_hThread != NULL &&
GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 &&
dwThreadStatus == STILL_ACTIVE)
{
// wait for graceful shutdown, i.e., the exit of the background thread or timeout
if (WaitForSingleObject(m_hThread, dwTimeout) != WAIT_OBJECT_0)
{
// if the thread is still running, we need kill it first before exit to avoid AV
if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE)
{
// Calling back into managed at this point is prone to have AVs
// Calling terminate thread here may be our best solution.
TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT);
}
}
shutdownHandler(m_ShutdownHandlerContext);
}
}
s_Application = NULL;
// Signal shutdown
if (m_pShutdownEvent != nullptr)
{
LOG_IF_FAILED(SetEvent(m_pShutdownEvent));
}
if (m_workerThread.joinable())
{
// Worker thread would wait for clr to finish and log error if required
m_workerThread.join();
}
s_Application = nullptr;
}
VOID
@ -201,169 +91,320 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
_In_ VOID* pvShutdownHandlerContext
)
{
LOG_INFO(L"In-process callbacks set");
m_RequestHandler = request_handler;
m_RequestHandlerContext = pvRequstHandlerContext;
m_ShutdownHandler = shutdown_handler;
m_ShutdownHandlerContext = pvShutdownHandlerContext;
m_AsyncCompletionHandler = async_completion_handler;
m_blockManagedCallbacks = false;
m_Initialized = true;
// Can't check the std err handle as it isn't a critical error
// Initialization complete
EventLog::Info(
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS,
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG,
QueryApplicationPhysicalPath().c_str());
SetEvent(m_pInitalizeEvent);
m_fInitialized = TRUE;
SetEvent(m_pInitializeEvent);
}
// Will be called by the inprocesshandler
HRESULT
IN_PROCESS_APPLICATION::LoadManagedApplication
(
VOID
)
IN_PROCESS_APPLICATION::LoadManagedApplication()
{
HRESULT hr = S_OK;
DWORD dwTimeout;
DWORD dwResult;
THROW_LAST_ERROR_IF_NULL(m_pInitializeEvent = CreateEvent(
nullptr, // default security attributes
TRUE, // manual reset event
FALSE, // not set
nullptr)); // name
ReferenceApplication();
THROW_LAST_ERROR_IF_NULL(m_pShutdownEvent = CreateEvent(
nullptr, // default security attributes
TRUE, // manual reset event
FALSE, // not set
nullptr)); // name
if (m_status != MANAGED_APPLICATION_STATUS::STARTING)
m_workerThread = std::thread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
{
// Core CLR has already been loaded.
// Cannot load more than once even there was a failure
if (m_status == MANAGED_APPLICATION_STATUS::FAIL)
{
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
}
else if (m_status == MANAGED_APPLICATION_STATUS::SHUTDOWN)
{
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
}
LOG_INFO(L"Starting in-process worker thread");
application->ExecuteApplication();
LOG_INFO(L"Stopping in-process worker thread");
}, ::ReferenceApplication(this));
goto Finished;
LOG_INFO(L"Waiting for initialization");
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
// Wait for shutdown request
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
if (waitResult == WAIT_TIMEOUT)
{
// If server wasn't initialized in time shut application down without waiting for CLR thread to exit
m_waitForShutdown = false;
StopClr();
throw InvalidOperationException(format(L"Managed server didn't initialize after %u ms.", m_pConfig->QueryStartupTimeLimitInMS()));
}
// WAIT_OBJECT_0 + 1 is the worker thead handle
if (waitResult == WAIT_OBJECT_0 + 1)
{
SRWExclusiveLock lock(m_stateLock);
// Worker thread exited stop
StopClr();
throw InvalidOperationException(format(L"CLR worker thread exited prematurely"));
}
if (m_status != MANAGED_APPLICATION_STATUS::STARTING)
THROW_IF_FAILED(StartMonitoringAppOffline());
return S_OK;
}
void
IN_PROCESS_APPLICATION::ExecuteApplication()
{
try
{
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
auto context = std::make_shared<ExecuteClrContext>();
auto pProc = s_fMainCallback;
if (pProc == nullptr)
{
if (m_status == MANAGED_APPLICATION_STATUS::FAIL)
HMODULE hModule;
// hostfxr should already be loaded by the shim. If not, then we will need
// to load it ourselves by finding hostfxr again.
THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
// Get the entry point for main
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
THROW_LAST_ERROR_IF_NULL(pProc);
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
m_dotnetExeKnownLocation,
m_pConfig->QueryProcessPath(),
QueryApplicationPhysicalPath(),
m_pConfig->QueryArguments(),
hostFxrOptions
));
hostFxrOptions->GetArguments(context->m_argc, context->m_argv);
THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
}
context->m_pProc = pProc;
if (m_pLoggerProvider == nullptr)
{
THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
m_pConfig->QueryStdoutLogEnabled(),
!m_pHttpServer.IsCommandLineLaunch(),
m_pConfig->QueryStdoutLogFile().c_str(),
QueryApplicationPhysicalPath().c_str(),
m_pLoggerProvider));
LOG_IF_FAILED(m_pLoggerProvider->Start());
}
// There can only ever be a single instance of .NET Core
// loaded in the process but we need to get config information to boot it up in the
// first place. This is happening in an execute request handler and everyone waits
// until this initialization is done.
// We set a static so that managed code can call back into this instance and
// set the callbacks
s_Application = this;
//Start CLR thread
m_clrThread = std::thread(ClrThreadEntryPoint, context);
// Wait for thread exit or shutdown event
const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
// Wait for shutdown request
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
bool clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
// shutdown was signaled
// only wait for shutdown in case of successful startup
if (m_waitForShutdown)
{
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
}
LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
// At this point CLR thread either finished or timed out, abandon it.
m_clrThread.detach();
LOG_IF_FAILED(m_pLoggerProvider->Stop());
if (m_fStopCalled)
{
if (clrThreadExited)
{
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
EventLog::Info(
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL,
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG,
QueryConfigPath().c_str());
}
else if (m_status == MANAGED_APPLICATION_STATUS::SHUTDOWN)
else
{
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
EventLog::Warn(
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG,
QueryConfigPath().c_str());
}
goto Finished;
}
m_hThread = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE)ExecuteAspNetCoreProcess,
this, // thread function arguments
0, // default creation flags
NULL); // receive thread identifier
if (m_hThread == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
m_pInitalizeEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not set
NULL); // name
if (m_pInitalizeEvent == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
// If the debugger is attached, never timeout
if (IsDebuggerPresent())
{
dwTimeout = INFINITE;
}
else
{
dwTimeout = m_pConfig->QueryStartupTimeLimitInMS();
if (clrThreadExited)
{
UnexpectedThreadExit(*context);
// If the inprocess server was initialized, we need to cause recycle to be called on the worker process.
// in case when it was not initialized we need to keep server running to serve 502 page
if (m_Initialized)
{
QueueStop();
}
}
}
const HANDLE pHandles[2]{ m_hThread, m_pInitalizeEvent };
// Wait on either the thread to complete or the event to be set
dwResult = WaitForMultipleObjects(2, pHandles, FALSE, dwTimeout);
// It all timed out
if (dwResult == WAIT_TIMEOUT)
{
// kill the backend thread as loading dotnet timedout
TerminateThread(m_hThread, 0);
hr = HRESULT_FROM_WIN32(dwResult);
goto Finished;
}
else if (dwResult == WAIT_FAILED)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
// The thread ended it means that something failed
if (dwResult == WAIT_OBJECT_0)
{
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
goto Finished;
}
m_status = MANAGED_APPLICATION_STATUS::RUNNING_MANAGED;
}
Finished:
if (FAILED(hr))
catch (InvalidOperationException& ex)
{
m_status = MANAGED_APPLICATION_STATUS::FAIL;
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
hr);
}
DereferenceApplication();
ex.as_wstring().c_str());
return hr;
OBSERVE_CAUGHT_EXCEPTION();
}
catch (std::runtime_error& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
GetUnexpectedExceptionMessage(ex).c_str());
OBSERVE_CAUGHT_EXCEPTION();
}
}
// static
VOID
IN_PROCESS_APPLICATION::ExecuteAspNetCoreProcess(
_In_ LPVOID pContext
)
void IN_PROCESS_APPLICATION::QueueStop()
{
HRESULT hr = S_OK;
IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext;
DBG_ASSERT(pApplication != NULL);
hr = pApplication->ExecuteApplication();
//
// no need to log the error here as if error happened, the thread will exit
// the error will ba catched by caller LoadManagedApplication which will log an error
//
if (m_fStopCalled)
{
return;
}
LOG_INFO(L"Queueing in-process stop thread");
std::thread stoppingThread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
{
LOG_INFO(L"Starting in-process stop thread");
application->Stop(false);
LOG_INFO(L"Stopping in-process stop thread");
}, ::ReferenceApplication(this));
stoppingThread.detach();
}
HRESULT IN_PROCESS_APPLICATION::Start(
IHttpServer& pServer,
IHttpApplication& pHttpApplication,
APPLICATION_PARAMETER* pParameters,
DWORD nParameters,
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>& application)
{
try
{
std::unique_ptr<InProcessOptions> options;
THROW_IF_FAILED(InProcessOptions::Create(pServer, pHttpApplication, options));
application = std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>(
new IN_PROCESS_APPLICATION(pServer, pHttpApplication, std::move(options), pParameters, nParameters));
THROW_IF_FAILED(application->LoadManagedApplication());
return S_OK;
}
catch (InvalidOperationException& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
pHttpApplication.GetApplicationId(),
pHttpApplication.GetApplicationPhysicalPath(),
ex.as_wstring().c_str());
RETURN_CAUGHT_EXCEPTION();
}
catch (std::runtime_error& ex)
{
EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
pHttpApplication.GetApplicationId(),
pHttpApplication.GetApplicationPhysicalPath(),
GetUnexpectedExceptionMessage(ex).c_str());
RETURN_CAUGHT_EXCEPTION();
}
CATCH_RETURN();
}
// Required because __try and objects with destructors can not be mixed
void
IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& context)
{
__try
{
auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get());
LOG_INFOF(L"Managed application exited with code %d", exitCode);
context->m_exitCode = exitCode;
}
__except(GetExceptionCode() != 0)
{
LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode());
context->m_exceptionCode = GetExceptionCode();
}
}
//
// Calls hostfxr_main with the hostfxr and application as arguments.
// This method should not access IN_PROCESS_APPLICATION instance as it may be already freed
// in case of startup timeout
//
VOID
IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context)
{
LOG_INFO(L"Starting CLR thread");
// Keep aspnetcorev2_inprocess.dll loaded while this thread is running
// this is required because thread might be abandoned
HandleWrapper<ModuleHandleTraits> moduleHandle;
ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle);
ExecuteClr(context);
FreeLibraryAndExitThread(moduleHandle.release(), 0);
}
HRESULT
IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess(
VOID
)
IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
{
auto variables = m_pConfig->QueryEnvironmentVariables();
auto inputTable = std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER>(new ENVIRONMENT_VAR_HASH());
@ -397,128 +438,53 @@ IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess(
return S_OK;
}
HRESULT
IN_PROCESS_APPLICATION::ExecuteApplication(
VOID
)
{
HRESULT hr;
HMODULE hModule = nullptr;
hostfxr_main_fn pProc;
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions = NULL;
DWORD hostfxrArgc = 0;
std::unique_ptr<PCWSTR[]> hostfxrArgv;
DBG_ASSERT(m_status == MANAGED_APPLICATION_STATUS::STARTING);
pProc = s_fMainCallback;
if (pProc == nullptr)
{
// hostfxr should already be loaded by the shim. If not, then we will need
// to load it ourselves by finding hostfxr again.
hModule = LoadLibraryW(L"hostfxr.dll");
if (hModule == NULL)
{
// .NET Core not installed (we can log a more detailed error message here)
hr = LOG_IF_FAILED(ERROR_BAD_ENVIRONMENT);
goto Finished;
}
// Get the entry point for main
pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main");
if (pProc == NULL)
{
hr = LOG_IF_FAILED(ERROR_BAD_ENVIRONMENT);
goto Finished;
}
FINISHED_IF_FAILED(hr = HOSTFXR_OPTIONS::Create(
m_struExeLocation.QueryStr(),
m_pConfig->QueryProcessPath().c_str(),
QueryApplicationPhysicalPath().c_str(),
m_pConfig->QueryArguments().c_str(),
hostFxrOptions
));
hostFxrOptions->GetArguments(hostfxrArgc, hostfxrArgv);
FINISHED_IF_FAILED(SetEnvironementVariablesOnWorkerProcess());
}
LOG_INFO(L"Starting managed application");
if (m_pLoggerProvider == NULL)
{
FINISHED_IF_FAILED(hr = LoggingHelpers::CreateLoggingProvider(
m_pConfig->QueryStdoutLogEnabled(),
!m_pHttpServer.IsCommandLineLaunch(),
m_pConfig->QueryStdoutLogFile().c_str(),
QueryApplicationPhysicalPath().c_str(),
m_pLoggerProvider));
LOG_IF_FAILED(m_pLoggerProvider->Start());
}
// There can only ever be a single instance of .NET Core
// loaded in the process but we need to get config information to boot it up in the
// first place. This is happening in an execute request handler and everyone waits
// until this initialization is done.
// We set a static so that managed code can call back into this instance and
// set the callbacks
s_Application = this;
hr = RunDotnetApplication(hostfxrArgc, hostfxrArgv.get(), pProc);
Finished:
//
// this method is called by the background thread and should never exit unless shutdown
// If main returned and shutdown was not called in managed, we want to block native from calling into
// managed. To do this, we can say that shutdown was called from managed.
// Don't bother locking here as there will always be a race between receiving a native shutdown
// notification and unexpected managed exit.
//
m_status = MANAGED_APPLICATION_STATUS::SHUTDOWN;
m_fShutdownCalledFromManaged = TRUE;
m_pLoggerProvider->Stop();
if (!m_fShutdownCalledFromNative)
{
LogErrorsOnMainExit(hr);
if (m_fInitialized)
{
//
// If the inprocess server was initialized, we need to cause recycle to be called on the worker process.
//
Stop(/*fServerInitiated*/ false);
}
}
return hr;
}
VOID
IN_PROCESS_APPLICATION::LogErrorsOnMainExit(
HRESULT hr
)
IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
{
STRA straStdErrOutput;
STRU struStdMsg;
auto hasStdOut = m_pLoggerProvider->GetStdOutContent(&straStdErrOutput) &&
SUCCEEDED(struStdMsg.CopyA(straStdErrOutput.QueryStr()));
if (context.m_exceptionCode != 0)
{
if (hasStdOut)
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exceptionCode,
struStdMsg.QueryStr());
}
else
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exceptionCode
);
}
return;
}
//
// Ungraceful shutdown, try to log an error message.
// This will be a common place for errors as it means the hostfxr_main returned
// or there was an exception.
//
STRA straStdErrOutput;
STRU struStdMsg;
if (m_pLoggerProvider->GetStdOutContent(&straStdErrOutput))
if (hasStdOut)
{
if (SUCCEEDED(struStdMsg.CopyA(straStdErrOutput.QueryStr()))) {
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
hr,
struStdMsg.QueryStr());
}
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
context.m_exitCode,
struStdMsg.QueryStr());
}
else
{
@ -527,53 +493,20 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
hr);
context.m_exitCode);
}
}
//
// Calls hostfxr_main with the hostfxr and application as arguments.
//
HRESULT
IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hostfxr_main_fn pProc)
{
HRESULT hr = S_OK;
__try
{
m_ProcessExitCode = pProc(argc, argv);
if (m_ProcessExitCode != 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
LOG_INFOF(L"Managed application exited with code %d", m_ProcessExitCode);
}
__except(GetExceptionCode() != 0)
{
LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode());
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
HRESULT
IN_PROCESS_APPLICATION::CreateHandler(
_In_ IHttpContext *pHttpContext,
_Out_ IREQUEST_HANDLER **pRequestHandler)
{
HRESULT hr = S_OK;
IREQUEST_HANDLER* pHandler = NULL;
pHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
if (pHandler == NULL)
try
{
hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
}
*pRequestHandler = pHandler;
return hr;
CATCH_RETURN();
return S_OK;
}

View File

@ -3,6 +3,7 @@
#pragma once
#include <thread>
#include "InProcessApplicationBase.h"
#include "IOutputManager.h"
#include "InProcessOptions.h"
@ -45,35 +46,26 @@ public:
override;
// Executes the .NET Core process
void
ExecuteApplication();
HRESULT
ExecuteApplication(
VOID
);
LoadManagedApplication();
HRESULT
LoadManagedApplication(
VOID
);
VOID
LogErrorsOnMainExit(
HRESULT hr
);
VOID
StopCallsIntoManaged(
VOID
)
void
QueueStop();
void
StopIncomingRequests()
{
m_fBlockCallbacksIntoManaged = TRUE;
QueueStop();
}
VOID
StopIncomingRequests(
VOID
)
void
StopCallsIntoManaged()
{
m_fShutdownCalledFromManaged = TRUE;
m_blockManagedCallbacks = true;
}
static
@ -84,44 +76,64 @@ public:
static
IN_PROCESS_APPLICATION*
GetInstance(
VOID
)
GetInstance()
{
return s_Application;
}
PCWSTR
QueryExeLocation()
const std::wstring&
QueryExeLocation() const
{
return m_struExeLocation.QueryStr();
return m_dotnetExeKnownLocation;
}
const InProcessOptions&
QueryConfig() const
{
return *m_pConfig.get();
return *m_pConfig;
}
bool
QueryBlockCallbacksIntoManaged() const
{
return m_fBlockCallbacksIntoManaged;
return m_blockManagedCallbacks;
}
static
HRESULT Start(
IHttpServer& pServer,
IHttpApplication& pHttpApplication,
APPLICATION_PARAMETER* pParameters,
DWORD nParameters,
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>& application);
private:
enum MANAGED_APPLICATION_STATUS
struct ExecuteClrContext: std::enable_shared_from_this<ExecuteClrContext>
{
UNKNOWN = 0,
STARTING,
RUNNING_MANAGED,
SHUTDOWN,
FAIL
};
ExecuteClrContext():
m_argc(0),
m_pProc(nullptr),
m_exitCode(0),
m_exceptionCode(0)
{
}
// Thread executing the .NET Core process
HandleWrapper<InvalidHandleTraits> m_hThread;
DWORD m_argc;
std::unique_ptr<PCWSTR[]> m_argv;
hostfxr_main_fn m_pProc;
int m_exitCode;
int m_exceptionCode;
};
// Thread executing the .NET Core process this might be abandoned in timeout cases
std::thread m_clrThread;
// Thread tracking the CLR thread, this one is always joined on shutdown
std::thread m_workerThread;
// The event that gets triggered when managed initialization is complete
HandleWrapper<NullHandleTraits> m_pInitializeEvent;
// The event that gets triggered when worker thread should exit
HandleWrapper<NullHandleTraits> m_pShutdownEvent;
// The request handler callback from managed code
PFN_REQUEST_HANDLER m_RequestHandler;
@ -133,53 +145,38 @@ private:
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
// The event that gets triggered when managed initialization is complete
HandleWrapper<InvalidHandleTraits> m_pInitalizeEvent;
std::wstring m_dotnetExeKnownLocation;
STRU m_struExeLocation;
std::atomic_bool m_blockManagedCallbacks;
bool m_Initialized;
bool m_waitForShutdown;
// The exit code of the .NET Core process
INT m_ProcessExitCode;
volatile BOOL m_fBlockCallbacksIntoManaged;
volatile BOOL m_fShutdownCalledFromNative;
volatile BOOL m_fShutdownCalledFromManaged;
BOOL m_fInitialized;
MANAGED_APPLICATION_STATUS m_status;
std::unique_ptr<InProcessOptions> m_pConfig;
static IN_PROCESS_APPLICATION* s_Application;
std::unique_ptr<IOutputManager> m_pLoggerProvider;
static const LPCSTR s_exeLocationParameterName;
static
VOID
ExecuteAspNetCoreProcess(
_In_ LPVOID pContext
);
HRESULT
SetEnvironementVariablesOnWorkerProcess(
VOID
);
HRESULT
RunDotnetApplication(
DWORD argc,
CONST PCWSTR* argv,
hostfxr_main_fn pProc
);
static
DWORD WINAPI
DoShutDown(
LPVOID lpParam
);
inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation";
VOID
ShutDownInternal(
VOID
);
UnexpectedThreadExit(const ExecuteClrContext& context) const;
HRESULT
SetEnvironmentVariablesOnWorkerProcess();
void
StopClr();
static
void
ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context);
static
void
ExecuteClr(const std::shared_ptr<ExecuteClrContext> &context);
// Allows to override call to hostfxr_main with custom callback
// used in testing
inline static hostfxr_main_fn s_fMainCallback = nullptr;
};

View File

@ -40,9 +40,7 @@ public:
) override;
IHttpContext*
QueryHttpContext(
VOID
) const
QueryHttpContext() const
{
return m_pW3Context;
}
@ -53,9 +51,7 @@ public:
);
VOID
IndicateManagedRequestComplete(
VOID
);
IndicateManagedRequestComplete();
VOID
SetAsyncCompletionStatus(
@ -68,11 +64,11 @@ public:
static
HRESULT
StaticInitialize(VOID);
StaticInitialize();
static
void
StaticTerminate(VOID);
StaticTerminate();
private:
REQUEST_NOTIFICATION_STATUS

View File

@ -60,5 +60,25 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
deploymentParameters.WebConfigActionList.Add(
WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine(path, "std")));
}
public static void TransformPath(this IISDeploymentParameters parameters, Func<string, string, string> transformation)
{
parameters.WebConfigActionList.Add(
(config, contentRoot) =>
{
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
aspNetCoreElement.SetAttributeValue("processPath", transformation((string)aspNetCoreElement.Attribute("processPath"), contentRoot));
});
}
public static void TransformArguments(this IISDeploymentParameters parameters, Func<string, string, string> transformation)
{
parameters.WebConfigActionList.Add(
(config, contentRoot) =>
{
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot));
});
}
}
}

View File

@ -483,7 +483,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
}
else
{
throw new InvalidOperationException($"iisexpress Process {hostProcess.Id} crashed before shutdown was triggered.");
throw new InvalidOperationException($"iisexpress Process {hostProcess?.Id} crashed before shutdown was triggered.");
}
}
}

View File

@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
await deploymentResult.HttpClient.RetryRequestAsync("/HelloWorld", r => r.StatusCode == HttpStatusCode.InternalServerError);
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Could not find the assembly 'aspnetcorev2_inprocess.dll'");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not find the assembly 'aspnetcorev2_inprocess.dll'");
}
[ConditionalTheory]

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Application '.+' started the coreclr in-process successfully.");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' started the coreclr in-process successfully.");
}
[ConditionalFact]
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Application '.+' has shutdown.");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' has shutdown.");
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
@ -23,19 +24,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalTheory]
[InlineData("CheckLogFile")]
[InlineData("CheckErrLogFile")]
public async Task CheckStdoutWithRandomNumber(string path)
public async Task CheckStdoutWithRandomNumber(string mode)
{
// Forcing publish for now to have parity between IIS and IISExpress
// Reason is because by default for IISExpress, we expect there to not be a web.config file.
// However, for IIS, we need a web.config file because the default on generated on publish
// doesn't include V2. We can remove the publish flag once IIS supports non-publish running
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString();
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString;
deploymentParameters.TransformArguments((a, _) => $"{a} {mode} {randomNumberString}");
await AssertFailsToStart(path, deploymentParameters);
await AssertFailsToStart(deploymentParameters);
Assert.Contains(TestSink.Writes, context => context.Message.Contains($"Random number: {randomNumberString}"));
}
@ -45,12 +41,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[InlineData("CheckLargeStdOutWrites")]
[InlineData("CheckOversizedStdErrWrites")]
[InlineData("CheckOversizedStdOutWrites")]
public async Task CheckStdoutWithLargeWrites(string path)
public async Task CheckStdoutWithLargeWrites(string mode)
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
deploymentParameters.TransformArguments((a, _) => $"{a} {mode}");
await AssertFailsToStart(path, deploymentParameters);
await AssertFailsToStart(deploymentParameters);
Assert.Contains(TestSink.Writes, context => context.Message.Contains(new string('a', 4096)));
}
@ -58,20 +54,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalFact]
public async Task CheckValidConsoleFunctions()
{
var path = "CheckConsoleFunctions";
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
deploymentParameters.TransformArguments((a, _) => $"{a} CheckConsoleFunctions");
await AssertFailsToStart(path, deploymentParameters);
await AssertFailsToStart(deploymentParameters);
Assert.Contains(TestSink.Writes, context => context.Message.Contains("Is Console redirection: True"));
}
private async Task AssertFailsToStart(string path, IntegrationTesting.IIS.IISDeploymentParameters deploymentParameters)
private async Task AssertFailsToStart(IntegrationTesting.IIS.IISDeploymentParameters deploymentParameters)
{
var deploymentResult = await DeployAsync(deploymentParameters);
var response = await deploymentResult.HttpClient.GetAsync(path);
var response = await deploymentResult.HttpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
@ -92,5 +87,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
var responseText = await response.Content.ReadAsStringAsync();
Assert.Contains("500.30 - ANCM In-Process Start Failure", responseText);
}
}
}

View File

@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(200, (int)result1.StatusCode);
Assert.Equal(500, (int)result2.StatusCode);
StopServer();
EventLogHelpers.VerifyEventLogEvent(result, TestSink, "Only one inprocess application is allowed per IIS application pool");
EventLogHelpers.VerifyEventLogEvent(result, "Only one inprocess application is allowed per IIS application pool");
}
[ConditionalTheory]
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(200, (int)result1.StatusCode);
Assert.Equal(500, (int)result2.StatusCode);
StopServer();
EventLogHelpers.VerifyEventLogEvent(result, TestSink, "Mixed hosting model is not supported.");
EventLogHelpers.VerifyEventLogEvent(result, "Mixed hosting model is not supported.");
}
private void SetHostingModel(string directory, HostingModel model)

View File

@ -1,54 +1,64 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
public class EventLogHelpers
{
private static readonly Regex EventLogRegex = new Regex("Event Log: (?<EventLogMessage>.+?)End Event Log Message.", RegexOptions.Singleline | RegexOptions.Compiled);
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, ITestSink testSink, string expectedRegexMatchString)
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString)
{
Assert.True(deploymentResult.HostProcess.HasExited);
var builder = new StringBuilder();
var entries = GetEntries(deploymentResult);
AssertSingleEntry(expectedRegexMatchString, entries);
}
public static void VerifyEventLogEvents(IISDeploymentResult deploymentResult, params string[] expectedRegexMatchString)
{
Assert.True(deploymentResult.HostProcess.HasExited);
foreach (var context in testSink.Writes)
var entries = GetEntries(deploymentResult).ToList();
foreach (var regexString in expectedRegexMatchString)
{
builder.Append(context.Message);
}
var matchedEntries = AssertSingleEntry(regexString, entries);
var count = 0;
var expectedRegex = new Regex(expectedRegexMatchString, RegexOptions.Singleline);
foreach (Match match in EventLogRegex.Matches(builder.ToString()))
{
var eventLogText = match.Groups["EventLogMessage"].Value;
if (expectedRegex.IsMatch(eventLogText))
foreach (var matchedEntry in matchedEntries)
{
count++;
entries.Remove(matchedEntry);
}
}
Assert.True(0 == entries.Count, $"Some entries were not matched by any regex {FormatEntries(entries)}");
}
Assert.True(count > 0, $"'{expectedRegexMatchString}' didn't match any event log messaged");
Assert.True(count < 2, $"'{expectedRegexMatchString}' matched more then one event log message");
private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable<EventLogEntry> entries)
{
var expectedRegex = new Regex(regexString, RegexOptions.Singleline);
var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray();
Assert.True(matchedEntries.Length > 0, $"No entries matched by '{regexString}'");
Assert.True(matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
return matchedEntries;
}
private static string FormatEntries(IEnumerable<EventLogEntry> entries)
{
return string.Join(",", entries.Select(e => e.Message));
}
private static IEnumerable<EventLogEntry> GetEntries(IISDeploymentResult deploymentResult)
{
var eventLog = new EventLog("Application");
// Eventlog is already sorted based on time of event in ascending time.
// Check results in reverse order.
var expectedRegexEventLog = new Regex(expectedRegexMatchString);
var processIdString = $"Process Id: {deploymentResult.HostProcess.Id}.";
// Event log messages round down to the nearest second, so subtract a second
@ -67,18 +77,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
continue;
}
// ReplacementStings == EventData collection in EventLog
// This is unaffected if event providers are not registered correctly
if (eventLogEntry.Source == AncmVersionToMatch(deploymentResult) &&
processIdString == eventLogEntry.ReplacementStrings[1] &&
expectedRegex.IsMatch(eventLogEntry.ReplacementStrings[0]))
processIdString == eventLogEntry.ReplacementStrings[1])
{
return;
yield return eventLogEntry;
}
}
Assert.True(false, $"'{expectedRegexMatchString}' didn't match any event log messaged.");
}
private static string AncmVersionToMatch(IISDeploymentResult deploymentResult)
@ -88,5 +95,51 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
"AspNetCore Module" +
(deploymentResult.DeploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2 ? " V2" : "");
}
public static string InProcessStarted(IISDeploymentResult deploymentResult)
{
return $"Application '{EscapedContentRoot(deploymentResult)}' started the coreclr in-process successfully";
}
public static string InProcessFailedToStart(IISDeploymentResult deploymentResult, string reason)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to load clr and managed application. {reason}";
}
public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason)
{
return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/HTTPTESTSITE'.";
}
public static string InProcessThreadException(IISDeploymentResult deploymentResult, string reason)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed exception{reason}";
}
public static string InProcessThreadExit(IISDeploymentResult deploymentResult, string code)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed background thread exit, exit code = '{code}'.";
}
public static string FailedToStartApplication(IISDeploymentResult deploymentResult, string code)
{
return $"Failed to start application '/LM/W3SVC/1/ROOT', ErrorCode '{code}'.";
}
public static string ConfigurationLoadError(IISDeploymentResult deploymentResult, string reason)
{
return $"Configuration load error. {reason}";
}
private static string EscapedContentRoot(IISDeploymentResult deploymentResult)
{
var contentRoot = deploymentResult.ContentRoot;
if (!contentRoot.EndsWith('\\'))
{
contentRoot += '\\';
}
return Regex.Escape(contentRoot);
}
}
}

View File

@ -101,7 +101,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\;</AdditionalLibraryDirectories>
<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;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<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;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
<Lib>
@ -128,7 +128,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\;</AdditionalLibraryDirectories>
<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;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<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;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
<Lib>
@ -156,7 +156,7 @@
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\;</AdditionalLibraryDirectories>
<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;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<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;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
<Lib>
@ -184,7 +184,7 @@
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\;</AdditionalLibraryDirectories>
<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;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<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;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
<Lib>

View File

@ -64,7 +64,7 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
struHostFxrDllLocation,
struExeLocation,
bstrArray), // args array.
HOSTFXR_UTILITY::StartupParametersResolutionException);
InvalidOperationException);
}
TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
@ -118,5 +118,5 @@ TEST(GetHostFxrArguments, InvalidParams)
struHostFxrDllLocation,
struExeLocation,
bstrArray), // args array.
HOSTFXR_UTILITY::StartupParametersResolutionException);
InvalidOperationException);
}

View File

@ -40,7 +40,7 @@ namespace InprocessTests
IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), parameters.data(), 1);
ASSERT_STREQ(app->QueryExeLocation(), L"hello");
ASSERT_STREQ(app->QueryExeLocation().c_str(), L"hello");
}
TEST(InProcessTest, GeneratesVirtualPath)

View File

@ -51,7 +51,7 @@ namespace IIS.FunctionalTests.Inprocess
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink,
EventLogHelpers.VerifyEventLogEvent(deploymentResult,
"The specified framework 'Microsoft.NETCore.App', version '2.9.9' was not found.");
}
@ -75,7 +75,7 @@ namespace IIS.FunctionalTests.Inprocess
var contents = File.ReadAllText(Helpers.GetExpectedLogName(deploymentResult, _logFolderPath));
var expectedString = "The specified framework 'Microsoft.NETCore.App', version '2.9.9' was not found.";
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, expectedString);
EventLogHelpers.VerifyEventLogEvent(deploymentResult, expectedString);
Assert.Contains(expectedString, contents);
}
@ -99,7 +99,7 @@ namespace IIS.FunctionalTests.Inprocess
var fileInDirectory = Directory.GetFiles(_logFolderPath).Single();
var contents = File.ReadAllText(fileInDirectory);
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Invoked hostfxr");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr");
Assert.Contains("Invoked hostfxr", contents);
}
@ -124,7 +124,7 @@ namespace IIS.FunctionalTests.Inprocess
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Invoked hostfxr");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr");
}
[ConditionalTheory]
@ -153,7 +153,7 @@ namespace IIS.FunctionalTests.Inprocess
var fileInDirectory = Directory.GetFiles(_logFolderPath).First();
var contents = File.ReadAllText(fileInDirectory);
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Invoked hostfxr");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr");
Assert.Contains("Invoked hostfxr", contents);
}

View File

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting;
@ -26,16 +27,42 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
public async Task ServerShutsDownWhenMainExits()
{
var parameters = _fixture.GetBaseDeploymentParameters(publish: true);
var result = await DeployAsync(parameters);
var deploymentResult = await DeployAsync(parameters);
try
{
await result.HttpClient.GetAsync("/Shutdown");
await deploymentResult.HttpClient.GetAsync("/Shutdown");
}
catch (HttpRequestException ex) when (ex.InnerException is IOException)
{
// Server might close a connection before request completes
}
Assert.True(result.HostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout));
deploymentResult.AssertWorkerProcessStop();
}
[ConditionalFact]
public async Task ServerShutsDownWhenMainExitsStress()
{
var parameters = _fixture.GetBaseDeploymentParameters(publish: true);
var deploymentResult = await StartAsync(parameters);
var load = Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => {
var statusCode = (int)response.StatusCode;
Assert.True(statusCode == 200 || statusCode == 503, "Status code was " + statusCode);
});
try
{
await deploymentResult.HttpClient.GetAsync("/Shutdown");
await load;
}
catch (HttpRequestException ex) when (ex.InnerException is IOException | ex.InnerException is SocketException)
{
// Server might close a connection before request completes
}
deploymentResult.AssertWorkerProcessStop();
}
[ConditionalFact]

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}");
}
[ConditionalFact]
@ -137,11 +137,67 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(_fixture.OverriddenServerWebSite, publish: true));
var response = await deploymentResult.HttpClient.GetAsync("/");
Assert.False(response.IsSuccessStatusCode);
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
StopServer();
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"),
EventLogHelpers.InProcessThreadException(deploymentResult, ".*?Application is running inside IIS process but is not configured to use IIS server"));
}
[ConditionalFact]
public async Task LogsUnexpectedThreadExitError()
{
var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true));
var response = await deploymentResult.HttpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
StopServer();
Assert.Contains(TestSink.Writes, context => context.Message.Contains("Application is running inside IIS process but is not configured to use IIS server"));
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"),
EventLogHelpers.InProcessThreadExit(deploymentResult, "12"));
}
[ConditionalFact]
public async Task StartupTimeoutIsApplied()
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
deploymentParameters.TransformArguments((a, _) => $"{a} Hang");
deploymentParameters.WebConfigActionList.Add(
WebConfigHelpers.AddOrModifyAspNetCoreSection("startupTimeLimit", "1"));
var deploymentResult = await DeployAsync(deploymentParameters);
var response = await deploymentResult.HttpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
StopServer();
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
EventLogHelpers.InProcessFailedToStart(deploymentResult, "Managed server didn't initialize after 1000 ms.")
);
}
[ConditionalFact]
public async Task ShutdownTimeoutIsApplied()
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
deploymentParameters.TransformArguments((a, _) => $"{a} HangOnStop");
deploymentParameters.WebConfigActionList.Add(
WebConfigHelpers.AddOrModifyAspNetCoreSection("shutdownTimeLimit", "1"));
var deploymentResult = await DeployAsync(deploymentParameters);
Assert.Equal("OK", await deploymentResult.HttpClient.GetStringAsync("/"));
StopServer();
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
EventLogHelpers.InProcessStarted(deploymentResult),
EventLogHelpers.InProcessFailedToStop(deploymentResult, ""));
}
[ConditionalFact]
@ -158,10 +214,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file.");
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
EventLogHelpers.FailedToStartApplication(deploymentResult, "0x80004005"),
EventLogHelpers.ConfigurationLoadError(deploymentResult, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file.")
);
}
private static Dictionary<string, (string, Action<XElement>)> InvalidConfigTransformations = InitInvalidConfigTransformations();
public static IEnumerable<object[]> InvalidConfigTransformationsScenarios => InvalidConfigTransformations.ToTheoryData();
@ -177,7 +235,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode);
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Configuration load error. " + expectedError);
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
EventLogHelpers.FailedToStartApplication(deploymentResult, "0x80004005"),
EventLogHelpers.ConfigurationLoadError(deploymentResult, expectedError)
);
}
public static Dictionary<string, (string, Action<XElement>)> InitInvalidConfigTransformations()
@ -223,42 +284,42 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
dictionary.Add("App in bin subdirectory full path to dll using exec and quotes",
parameters => {
MoveApplication(parameters, "bin");
TransformArguments(parameters, (arguments, root) => "exec " + Path.Combine(root, "bin", arguments));
parameters.TransformArguments((arguments, root) => "exec " + Path.Combine(root, "bin", arguments));
return "";
});
dictionary.Add("App in subdirectory with space",
parameters => {
MoveApplication(parameters, pathWithSpace);
TransformArguments(parameters, (arguments, root) => Path.Combine(pathWithSpace, arguments));
parameters.TransformArguments((arguments, root) => Path.Combine(pathWithSpace, arguments));
return "";
});
dictionary.Add("App in subdirectory with space and full path to dll",
parameters => {
MoveApplication(parameters, pathWithSpace);
TransformArguments(parameters, (arguments, root) => Path.Combine(root, pathWithSpace, arguments));
parameters.TransformArguments((arguments, root) => Path.Combine(root, pathWithSpace, arguments));
return "";
});
dictionary.Add("App in bin subdirectory with space full path to dll using exec and quotes",
parameters => {
MoveApplication(parameters, pathWithSpace);
TransformArguments(parameters, (arguments, root) => "exec \"" + Path.Combine(root, pathWithSpace, arguments) + "\" extra arguments");
parameters.TransformArguments((arguments, root) => "exec \"" + Path.Combine(root, pathWithSpace, arguments) + "\" extra arguments");
return "extra|arguments";
});
dictionary.Add("App in bin subdirectory and quoted argument",
parameters => {
MoveApplication(parameters, "bin");
TransformArguments(parameters, (arguments, root) => Path.Combine("bin", arguments) + " \"extra argument\"");
parameters.TransformArguments((arguments, root) => Path.Combine("bin", arguments) + " \"extra argument\"");
return "extra argument";
});
dictionary.Add("App in bin subdirectory full path to dll",
parameters => {
MoveApplication(parameters, "bin");
TransformArguments(parameters, (arguments, root) => Path.Combine(root, "bin", arguments) + " extra arguments");
parameters.TransformArguments((arguments, root) => Path.Combine(root, "bin", arguments) + " extra arguments");
return "extra|arguments";
});
return dictionary;
@ -288,16 +349,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
dictionary.Add("App in subdirectory",
parameters => {
MoveApplication(parameters, pathWithSpace);
TransformPath(parameters, (path, root) => Path.Combine(pathWithSpace, path));
TransformArguments(parameters, (arguments, root) => "\"additional argument\"");
parameters.TransformPath((path, root) => Path.Combine(pathWithSpace, path));
parameters.TransformArguments((arguments, root) => "\"additional argument\"");
return "additional argument";
});
dictionary.Add("App in bin subdirectory full path",
parameters => {
MoveApplication(parameters, pathWithSpace);
TransformPath(parameters, (path, root) => Path.Combine(root, pathWithSpace, path));
TransformArguments(parameters, (arguments, root) => "additional arguments");
parameters.TransformPath((path, root) => Path.Combine(root, pathWithSpace, path));
parameters.TransformArguments((arguments, root) => "additional arguments");
return "additional|arguments";
});
@ -322,26 +383,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
}
});
}
private static void TransformPath(IISDeploymentParameters parameters, Func<string, string, string> transformation)
{
parameters.WebConfigActionList.Add(
(config, contentRoot) =>
{
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
aspNetCoreElement.SetAttributeValue("processPath", transformation((string)aspNetCoreElement.Attribute("processPath"), contentRoot));
});
}
private static void TransformArguments(IISDeploymentParameters parameters, Func<string, string, string> transformation)
{
parameters.WebConfigActionList.Add(
(config, contentRoot) =>
{
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot));
});
}
}
}

View File

@ -2,54 +2,75 @@
// 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.Linq;
using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
namespace IISTestSite
{
public static class Program
{
public static void Main(string[] args)
public static int Main(string[] args)
{
var envVariable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_STARTUP_VALUE");
var randomNumber = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_RANDOM_VALUE");
var mode = args.FirstOrDefault();
// Semicolons are appended to env variables; removing them.
if (envVariable == "CheckLargeStdOutWrites")
switch (mode)
{
Console.WriteLine(new string('a', 4096));
}
else if (envVariable == "CheckLargeStdErrWrites")
{
Console.Error.WriteLine(new string('a', 4096));
Console.Error.Flush();
}
else if (envVariable == "CheckLogFile")
{
Console.WriteLine($"Random number: {randomNumber}");
}
else if (envVariable == "CheckErrLogFile")
{
Console.Error.WriteLine($"Random number: {randomNumber}");
Console.Error.Flush();
}
else if (envVariable == "CheckOversizedStdErrWrites")
{
Console.WriteLine(new string('a', 5000));
// Semicolons are appended to env variables; removing them.
case "CheckLargeStdOutWrites":
Console.WriteLine(new string('a', 4096));
break;
case "CheckLargeStdErrWrites":
Console.Error.WriteLine(new string('a', 4096));
Console.Error.Flush();
break;
case "CheckLogFile":
Console.WriteLine($"Random number: {args[1]}");
break;
case "CheckErrLogFile":
Console.Error.WriteLine($"Random number: {args[1]}");
Console.Error.Flush();
break;
case "CheckOversizedStdErrWrites":
Console.WriteLine(new string('a', 5000));
break;
case "CheckOversizedStdOutWrites":
Console.Error.WriteLine(new string('a', 4096));
Console.Error.Flush();
break;
case "Hang":
Thread.Sleep(Timeout.Infinite);
break;
case "HangOnStop":
var host = new WebHostBuilder()
.UseIIS()
.UseStartup<Startup>()
.Build();
host.Run();
Thread.Sleep(Timeout.Infinite);
break;
case "CheckConsoleFunctions":
// Call a bunch of console functions and make sure none return invalid handle.
Console.OutputEncoding = Encoding.UTF8;
Console.Title = "Test";
Console.WriteLine($"Is Console redirection: {Console.IsOutputRedirected}");
Console.BackgroundColor = ConsoleColor.Blue;
break;
}
else if (envVariable == "CheckOversizedStdOutWrites")
return 12;
}
public partial class Startup
{
public void Configure(IApplicationBuilder app)
{
Console.Error.WriteLine(new string('a', 4096));
Console.Error.Flush();
}
else if (envVariable == "CheckConsoleFunctions")
{
// Call a bunch of console functions and make sure none return invalid handle.
Console.OutputEncoding = Encoding.UTF8;
Console.Title = "Test";
Console.WriteLine($"Is Console redirection: {Console.IsOutputRedirected}");
Console.BackgroundColor = ConsoleColor.Blue;
app.Run(async context => await context.Response.WriteAsync("OK"));
}
}
}