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; std::unique_ptr<IOutputManager> outputManager;
RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create( RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create(
NULL, L"",
pConfiguration.QueryProcessPath().c_str(), pConfiguration.QueryProcessPath(),
pApplication.GetApplicationPhysicalPath(), pApplication.GetApplicationPhysicalPath(),
pConfiguration.QueryArguments().c_str(), pConfiguration.QueryArguments(),
options)); options));
location = options->GetDotnetExeLocation(); location = options->GetDotnetExeLocation();

View File

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

View File

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

View File

@ -26,6 +26,16 @@ bool ConfigurationSection::GetRequiredBool(const std::wstring& name) const
return result.value(); 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 DWORD ConfigurationSection::GetRequiredTimespan(const std::wstring& name) const
{ {
auto result = GetTimespan(name); auto result = GetTimespan(name);

View File

@ -17,6 +17,8 @@
#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" #define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath"
#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" #define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments"
#define CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT L"" #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_OUTOFPROCESS L"outofprocess"
#define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess" #define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess"
#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" #define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel"
@ -31,10 +33,12 @@ public:
virtual ~ConfigurationSection() = default; virtual ~ConfigurationSection() = default;
virtual std::optional<std::wstring> GetString(const std::wstring& name) const = 0; 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<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; virtual std::optional<DWORD> GetTimespan(const std::wstring& name) const = 0;
std::wstring GetRequiredString(const std::wstring& name) const; std::wstring GetRequiredString(const std::wstring& name) const;
bool GetRequiredBool(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; DWORD GetRequiredTimespan(const std::wstring& name) const;
virtual std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(const std::wstring& name) const = 0; 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); 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 std::optional<DWORD> WebConfigConfigurationSection::GetTimespan(const std::wstring& name) const
{ {
ULONGLONG result; ULONGLONG result;

View File

@ -17,6 +17,7 @@ public:
std::optional<std::wstring> GetString(const std::wstring& name) const override; std::optional<std::wstring> GetString(const std::wstring& name) const override;
std::optional<bool> GetBool(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::optional<DWORD> GetTimespan(const std::wstring& name) const override;
std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(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 "iapplication.h"
#include "ntassert.h" #include "ntassert.h"
#include "SRWExclusiveLock.h" #include "SRWExclusiveLock.h"
#include "SRWSharedLock.h"
class APPLICATION : public IAPPLICATION class APPLICATION : public IAPPLICATION
{ {
@ -15,11 +16,6 @@ public:
APPLICATION(const APPLICATION&) = delete; APPLICATION(const APPLICATION&) = delete;
const APPLICATION& operator=(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) APPLICATION(const IHttpApplication& pHttpApplication)
: m_fStopCalled(false), : m_fStopCalled(false),
@ -32,6 +28,12 @@ public:
m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath); m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath);
} }
APPLICATION_STATUS
QueryStatus() override
{
SRWSharedLock stateLock(m_stateLock);
return m_fStopCalled ? APPLICATION_STATUS::RECYCLED : APPLICATION_STATUS::RUNNING;
}
VOID VOID
Stop(bool fServerInitiated) override Stop(bool fServerInitiated) override

View File

@ -3,10 +3,12 @@
#pragma once #pragma once
#include <exception>
#include <system_error> #include <system_error>
#include "debugutil.h" #include "debugutil.h"
#include "StringHelpers.h" #include "StringHelpers.h"
#include "InvalidOperationException.h"
#define LOCATION_INFO_ENABLED TRUE #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(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 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_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(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(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) #define THROW_IF_NULL_ALLOC(ptr) Throw_IfNullAlloc(ptr)
@ -152,3 +154,7 @@ template <typename PointerT> auto Throw_IfNullAlloc(PointerT pointer)
} }
return 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)) 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. // Check if the absolute path is to dotnet or not.
@ -51,7 +51,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
if (applicationArguments.empty()) if (applicationArguments.empty())
{ {
throw StartupParametersResolutionException(L"Application arguments are empty."); throw InvalidOperationException(L"Application arguments are empty.");
} }
if (dotnetExePath.empty()) if (dotnetExePath.empty())
@ -92,7 +92,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
LOG_INFOF(L"Checking application.dll at '%ls'", applicationDllPath.c_str()); LOG_INFOF(L"Checking application.dll at '%ls'", applicationDllPath.c_str());
if (!is_regular_file(applicationDllPath)) 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"; 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 // If the processPath file does not exist and it doesn't include dotnet.exe or dotnet
// then it is an invalid argument. // 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)); auto pwzArgs = std::unique_ptr<LPWSTR[], LocalFreeDeleter>(CommandLineToArgvW(applicationArguments.c_str(), &argc));
if (!pwzArgs) 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++) 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()); 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(); const auto dotnetViaWhere = InvokeWhereToFindDotnet();
@ -266,7 +266,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
} }
LOG_INFOF(L"dotnet.exe not found"); 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." 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.", " 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())); processPath.c_str()));
@ -284,14 +284,14 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
if (!is_directory(hostFxrBase)) 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); FindDotNetFolders(hostFxrBase, versionFolders);
if (versionFolders.empty()) 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); const auto highestVersion = FindHighestDotNetVersion(versionFolders);
@ -299,7 +299,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
if (!is_regular_file(hostFxrPath)) 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()); LOG_INFOF(L"hostfxr.dll located at '%ls'", hostFxrPath.c_str());

View File

@ -29,20 +29,6 @@ public:
std::vector<std::wstring> &arguments 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 static
void void
AppendArguments( AppendArguments(
@ -96,7 +82,7 @@ private:
struct LocalFreeDeleter struct LocalFreeDeleter
{ {
void operator ()(LPWSTR* ptr) const void operator ()(_In_ LPWSTR* ptr) const
{ {
LocalFree(ptr); LocalFree(ptr);
} }

View File

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

View File

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

View File

@ -24,14 +24,14 @@
#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." #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_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_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_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_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_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_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_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_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, ErrorCode = '0x%x'. Please check the stderr logs for more information." #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_APP_IN_SHUTDOWN_MSG L"Application shutting down."
#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' was recycled after detecting the app_offline file." #define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_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'. " #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_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_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_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_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_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." #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" #include "InProcessApplicationBase.h"
hostfxr_main_fn InProcessApplicationBase::s_fMainCallback = NULL;
InProcessApplicationBase::InProcessApplicationBase( InProcessApplicationBase::InProcessApplicationBase(
IHttpServer& pHttpServer, IHttpServer& pHttpServer,
IHttpApplication& pHttpApplication) IHttpApplication& pHttpApplication)
@ -35,7 +33,21 @@ InProcessApplicationBase::StopInternal(bool fServerInitiated)
} }
else 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 #pragma once
#include "application.h"
#include "AppOfflineTrackingApplication.h" #include "AppOfflineTrackingApplication.h"
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs
@ -23,8 +22,5 @@ public:
protected: protected:
BOOL m_fRecycleCalled; BOOL m_fRecycleCalled;
IHttpServer& m_pHttpServer; 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. // Licensed under the MIT License. See License.txt in the project root for license information.
#include "InProcessOptions.h" #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) : InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSource) :
m_fStdoutLogEnabled(false), m_fStdoutLogEnabled(false),
@ -18,6 +53,8 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE);
m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE);
m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES); 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); const auto basicAuthSection = configurationSource.GetSection(CS_BASIC_AUTHENTICATION_SECTION);
m_fBasicAuthEnabled = basicAuthSection && basicAuthSection->GetBool(CS_ENABLED).value_or(false); m_fBasicAuthEnabled = basicAuthSection && basicAuthSection->GetBool(CS_ENABLED).value_or(false);

View File

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

View File

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

View File

@ -16,29 +16,6 @@ public:
: m_disableLogs(disableLogs), : m_disableLogs(disableLogs),
InProcessApplicationBase(pServer, pApplication) 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; ~StartupExceptionApplication() = default;

View File

@ -4,6 +4,30 @@
#include "StartupExceptionApplication.h" #include "StartupExceptionApplication.h"
#include "StartupExceptionHandler.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() REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler()
{ {
if (!m_disableLogs) if (!m_disableLogs)
@ -16,11 +40,9 @@ REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler()
(USHORT)strlen("text/html"), (USHORT)strlen("text/html"),
FALSE FALSE
); );
const std::string& html500Page = m_pApplication->GetStaticHtml500Content();
DataChunk.DataChunkType = HttpDataChunkFromMemory; DataChunk.DataChunkType = HttpDataChunkFromMemory;
DataChunk.FromMemory.pBuffer = (PVOID)html500Page.c_str(); DataChunk.FromMemory.pBuffer = (PVOID)s_html500Page.c_str();
DataChunk.FromMemory.BufferLength = (ULONG)html500Page.size(); DataChunk.FromMemory.BufferLength = (ULONG)s_html500Page.size();
pResponse->WriteEntityChunkByReference(&DataChunk); pResponse->WriteEntityChunkByReference(&DataChunk);
} }
else else

View File

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

View File

@ -110,39 +110,25 @@ CreateApplication(
return S_OK; 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 // never create two inprocess applications in one process
g_fInProcessApplicationCreated = true; 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. // 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()); RETURN_IF_FAILED(pErrorApplication->StartMonitoringAppOffline());
*ppApplication = pErrorApplication.release(); *ppApplication = pErrorApplication.release();
} }
else return S_OK;
{
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);
} }
CATCH_RETURN(); CATCH_RETURN();
return S_OK;
} }

View File

@ -6,13 +6,11 @@
#include "hostfxroptions.h" #include "hostfxroptions.h"
#include "requesthandler_config.h" #include "requesthandler_config.h"
#include "environmentvariablehelpers.h" #include "environmentvariablehelpers.h"
#include "SRWExclusiveLock.h"
#include "exceptions.h" #include "exceptions.h"
#include "LoggingHelpers.h" #include "LoggingHelpers.h"
#include "resources.h" #include "resources.h"
#include "EventLog.h" #include "EventLog.h"
#include "ModuleHelpers.h"
const LPCSTR IN_PROCESS_APPLICATION::s_exeLocationParameterName = "InProcessExeLocation";
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
@ -23,10 +21,9 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
APPLICATION_PARAMETER *pParameters, APPLICATION_PARAMETER *pParameters,
DWORD nParameters) : DWORD nParameters) :
InProcessApplicationBase(pHttpServer, pApplication), InProcessApplicationBase(pHttpServer, pApplication),
m_ProcessExitCode(0), m_Initialized(false),
m_fBlockCallbacksIntoManaged(FALSE), m_blockManagedCallbacks(true),
m_fShutdownCalledFromNative(FALSE), m_waitForShutdown(true),
m_fShutdownCalledFromManaged(FALSE),
m_pConfig(std::move(pConfig)) m_pConfig(std::move(pConfig))
{ {
DBG_ASSERT(m_pConfig); DBG_ASSERT(m_pConfig);
@ -35,161 +32,54 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
{ {
if (_stricmp(pParameters[i].pzName, s_exeLocationParameterName) == 0) 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() 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 VOID
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated) IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
{ {
UNREFERENCED_PARAMETER(fServerInitiated); StopClr();
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());
}
InProcessApplicationBase::StopInternal(fServerInitiated); InProcessApplicationBase::StopInternal(fServerInitiated);
} }
VOID VOID
IN_PROCESS_APPLICATION::ShutDownInternal() IN_PROCESS_APPLICATION::StopClr()
{ {
DWORD dwThreadStatus = 0; LOG_INFO(L"Stopping CLR");
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
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 || if (!g_fProcessDetach && shutdownHandler != nullptr)
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)
{ {
return; shutdownHandler(m_ShutdownHandlerContext);
}
// 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);
}
}
} }
} }
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 VOID
@ -201,169 +91,320 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
_In_ VOID* pvShutdownHandlerContext _In_ VOID* pvShutdownHandlerContext
) )
{ {
LOG_INFO(L"In-process callbacks set");
m_RequestHandler = request_handler; m_RequestHandler = request_handler;
m_RequestHandlerContext = pvRequstHandlerContext; m_RequestHandlerContext = pvRequstHandlerContext;
m_ShutdownHandler = shutdown_handler; m_ShutdownHandler = shutdown_handler;
m_ShutdownHandlerContext = pvShutdownHandlerContext; m_ShutdownHandlerContext = pvShutdownHandlerContext;
m_AsyncCompletionHandler = async_completion_handler; 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 // Can't check the std err handle as it isn't a critical error
// Initialization complete // Initialization complete
EventLog::Info( EventLog::Info(
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS, ASPNETCORE_EVENT_INPROCESS_START_SUCCESS,
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG, ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG,
QueryApplicationPhysicalPath().c_str()); QueryApplicationPhysicalPath().c_str());
SetEvent(m_pInitalizeEvent);
m_fInitialized = TRUE; SetEvent(m_pInitializeEvent);
} }
// Will be called by the inprocesshandler
HRESULT HRESULT
IN_PROCESS_APPLICATION::LoadManagedApplication IN_PROCESS_APPLICATION::LoadManagedApplication()
(
VOID
)
{ {
HRESULT hr = S_OK; THROW_LAST_ERROR_IF_NULL(m_pInitializeEvent = CreateEvent(
DWORD dwTimeout; nullptr, // default security attributes
DWORD dwResult; 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. LOG_INFO(L"Starting in-process worker thread");
// Cannot load more than once even there was a failure application->ExecuteApplication();
if (m_status == MANAGED_APPLICATION_STATUS::FAIL) LOG_INFO(L"Stopping in-process worker thread");
{ }, ::ReferenceApplication(this));
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
}
else if (m_status == MANAGED_APPLICATION_STATUS::SHUTDOWN)
{
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
}
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 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: catch (InvalidOperationException& ex)
if (FAILED(hr))
{ {
m_status = MANAGED_APPLICATION_STATUS::FAIL;
EventLog::Error( EventLog::Error(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE, ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
QueryApplicationId().c_str(), QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(), QueryApplicationPhysicalPath().c_str(),
hr); ex.as_wstring().c_str());
}
DereferenceApplication();
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::QueueStop()
VOID
IN_PROCESS_APPLICATION::ExecuteAspNetCoreProcess(
_In_ LPVOID pContext
)
{ {
HRESULT hr = S_OK; if (m_fStopCalled)
IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext; {
DBG_ASSERT(pApplication != NULL); return;
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
//
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 HRESULT
IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess( IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
VOID
)
{ {
auto variables = m_pConfig->QueryEnvironmentVariables(); auto variables = m_pConfig->QueryEnvironmentVariables();
auto inputTable = std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER>(new ENVIRONMENT_VAR_HASH()); 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; 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 VOID
IN_PROCESS_APPLICATION::LogErrorsOnMainExit( IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
HRESULT hr
)
{ {
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. // Ungraceful shutdown, try to log an error message.
// This will be a common place for errors as it means the hostfxr_main returned // This will be a common place for errors as it means the hostfxr_main returned
// or there was an exception. // or there was an exception.
// //
STRA straStdErrOutput; if (hasStdOut)
STRU struStdMsg;
if (m_pLoggerProvider->GetStdOutContent(&straStdErrOutput))
{ {
if (SUCCEEDED(struStdMsg.CopyA(straStdErrOutput.QueryStr()))) { EventLog::Error(
EventLog::Error( ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT, ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG, QueryApplicationId().c_str(),
QueryApplicationId().c_str(), QueryApplicationPhysicalPath().c_str(),
QueryApplicationPhysicalPath().c_str(), context.m_exitCode,
hr, struStdMsg.QueryStr());
struStdMsg.QueryStr());
}
} }
else else
{ {
@ -527,53 +493,20 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
QueryApplicationId().c_str(), QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().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 HRESULT
IN_PROCESS_APPLICATION::CreateHandler( IN_PROCESS_APPLICATION::CreateHandler(
_In_ IHttpContext *pHttpContext, _In_ IHttpContext *pHttpContext,
_Out_ IREQUEST_HANDLER **pRequestHandler) _Out_ IREQUEST_HANDLER **pRequestHandler)
{ {
HRESULT hr = S_OK; try
IREQUEST_HANDLER* pHandler = NULL;
pHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
if (pHandler == NULL)
{ {
hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); *pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
} }
CATCH_RETURN();
*pRequestHandler = pHandler;
return hr; return S_OK;
} }

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <thread>
#include "InProcessApplicationBase.h" #include "InProcessApplicationBase.h"
#include "IOutputManager.h" #include "IOutputManager.h"
#include "InProcessOptions.h" #include "InProcessOptions.h"
@ -45,35 +46,26 @@ public:
override; override;
// Executes the .NET Core process // Executes the .NET Core process
void
ExecuteApplication();
HRESULT HRESULT
ExecuteApplication( LoadManagedApplication();
VOID
);
HRESULT
LoadManagedApplication(
VOID
);
VOID void
LogErrorsOnMainExit( QueueStop();
HRESULT hr
); void
StopIncomingRequests()
VOID
StopCallsIntoManaged(
VOID
)
{ {
m_fBlockCallbacksIntoManaged = TRUE; QueueStop();
} }
VOID void
StopIncomingRequests( StopCallsIntoManaged()
VOID
)
{ {
m_fShutdownCalledFromManaged = TRUE; m_blockManagedCallbacks = true;
} }
static static
@ -84,44 +76,64 @@ public:
static static
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION*
GetInstance( GetInstance()
VOID
)
{ {
return s_Application; return s_Application;
} }
PCWSTR const std::wstring&
QueryExeLocation() QueryExeLocation() const
{ {
return m_struExeLocation.QueryStr(); return m_dotnetExeKnownLocation;
} }
const InProcessOptions& const InProcessOptions&
QueryConfig() const QueryConfig() const
{ {
return *m_pConfig.get(); return *m_pConfig;
} }
bool bool
QueryBlockCallbacksIntoManaged() const 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: private:
struct ExecuteClrContext: std::enable_shared_from_this<ExecuteClrContext>
enum MANAGED_APPLICATION_STATUS
{ {
UNKNOWN = 0, ExecuteClrContext():
STARTING, m_argc(0),
RUNNING_MANAGED, m_pProc(nullptr),
SHUTDOWN, m_exitCode(0),
FAIL m_exceptionCode(0)
}; {
}
// Thread executing the .NET Core process DWORD m_argc;
HandleWrapper<InvalidHandleTraits> m_hThread; 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 // The request handler callback from managed code
PFN_REQUEST_HANDLER m_RequestHandler; PFN_REQUEST_HANDLER m_RequestHandler;
@ -133,53 +145,38 @@ private:
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler; PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
// The event that gets triggered when managed initialization is complete std::wstring m_dotnetExeKnownLocation;
HandleWrapper<InvalidHandleTraits> m_pInitalizeEvent;
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; std::unique_ptr<InProcessOptions> m_pConfig;
static IN_PROCESS_APPLICATION* s_Application; static IN_PROCESS_APPLICATION* s_Application;
std::unique_ptr<IOutputManager> m_pLoggerProvider; std::unique_ptr<IOutputManager> m_pLoggerProvider;
static const LPCSTR s_exeLocationParameterName; inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation";
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
);
VOID VOID
ShutDownInternal( UnexpectedThreadExit(const ExecuteClrContext& context) const;
VOID
); 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; ) override;
IHttpContext* IHttpContext*
QueryHttpContext( QueryHttpContext() const
VOID
) const
{ {
return m_pW3Context; return m_pW3Context;
} }
@ -53,9 +51,7 @@ public:
); );
VOID VOID
IndicateManagedRequestComplete( IndicateManagedRequestComplete();
VOID
);
VOID VOID
SetAsyncCompletionStatus( SetAsyncCompletionStatus(
@ -68,11 +64,11 @@ public:
static static
HRESULT HRESULT
StaticInitialize(VOID); StaticInitialize();
static static
void void
StaticTerminate(VOID); StaticTerminate();
private: private:
REQUEST_NOTIFICATION_STATUS REQUEST_NOTIFICATION_STATUS

View File

@ -60,5 +60,25 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
deploymentParameters.WebConfigActionList.Add( deploymentParameters.WebConfigActionList.Add(
WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine(path, "std"))); 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 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); await deploymentResult.HttpClient.RetryRequestAsync("/HelloWorld", r => r.StatusCode == HttpStatusCode.InternalServerError);
StopServer(); 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] [ConditionalTheory]

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer(); StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Application '.+' started the coreclr in-process successfully."); EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' started the coreclr in-process successfully.");
} }
[ConditionalFact] [ConditionalFact]
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer(); 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.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Xunit; using Xunit;
@ -23,19 +24,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalTheory] [ConditionalTheory]
[InlineData("CheckLogFile")] [InlineData("CheckLogFile")]
[InlineData("CheckErrLogFile")] [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 deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString(); var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString();
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path; deploymentParameters.TransformArguments((a, _) => $"{a} {mode} {randomNumberString}");
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString;
await AssertFailsToStart(path, deploymentParameters); await AssertFailsToStart(deploymentParameters);
Assert.Contains(TestSink.Writes, context => context.Message.Contains($"Random number: {randomNumberString}")); Assert.Contains(TestSink.Writes, context => context.Message.Contains($"Random number: {randomNumberString}"));
} }
@ -45,12 +41,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[InlineData("CheckLargeStdOutWrites")] [InlineData("CheckLargeStdOutWrites")]
[InlineData("CheckOversizedStdErrWrites")] [InlineData("CheckOversizedStdErrWrites")]
[InlineData("CheckOversizedStdOutWrites")] [InlineData("CheckOversizedStdOutWrites")]
public async Task CheckStdoutWithLargeWrites(string path) public async Task CheckStdoutWithLargeWrites(string mode)
{ {
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true); 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))); Assert.Contains(TestSink.Writes, context => context.Message.Contains(new string('a', 4096)));
} }
@ -58,20 +54,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalFact] [ConditionalFact]
public async Task CheckValidConsoleFunctions() public async Task CheckValidConsoleFunctions()
{ {
var path = "CheckConsoleFunctions";
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true); 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")); 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 deploymentResult = await DeployAsync(deploymentParameters);
var response = await deploymentResult.HttpClient.GetAsync(path); var response = await deploymentResult.HttpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
@ -92,5 +87,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
var responseText = await response.Content.ReadAsStringAsync(); var responseText = await response.Content.ReadAsStringAsync();
Assert.Contains("500.30 - ANCM In-Process Start Failure", responseText); 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(200, (int)result1.StatusCode);
Assert.Equal(500, (int)result2.StatusCode); Assert.Equal(500, (int)result2.StatusCode);
StopServer(); 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] [ConditionalTheory]
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(200, (int)result1.StatusCode); Assert.Equal(200, (int)result1.StatusCode);
Assert.Equal(500, (int)result2.StatusCode); Assert.Equal(500, (int)result2.StatusCode);
StopServer(); 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) private void SetHostingModel(string directory, HostingModel model)

View File

@ -1,54 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.Extensions.Logging.Testing;
using Xunit; using Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
public class EventLogHelpers 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, string expectedRegexMatchString)
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, ITestSink testSink, string expectedRegexMatchString)
{ {
Assert.True(deploymentResult.HostProcess.HasExited); 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; foreach (var matchedEntry in matchedEntries)
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))
{ {
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"); private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable<EventLogEntry> entries)
Assert.True(count < 2, $"'{expectedRegexMatchString}' matched more then one event log message"); {
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"); var eventLog = new EventLog("Application");
// Eventlog is already sorted based on time of event in ascending time. // Eventlog is already sorted based on time of event in ascending time.
// Check results in reverse order. // Check results in reverse order.
var expectedRegexEventLog = new Regex(expectedRegexMatchString);
var processIdString = $"Process Id: {deploymentResult.HostProcess.Id}."; var processIdString = $"Process Id: {deploymentResult.HostProcess.Id}.";
// Event log messages round down to the nearest second, so subtract a second // Event log messages round down to the nearest second, so subtract a second
@ -67,18 +77,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
continue; continue;
} }
// ReplacementStings == EventData collection in EventLog // ReplacementStings == EventData collection in EventLog
// This is unaffected if event providers are not registered correctly // This is unaffected if event providers are not registered correctly
if (eventLogEntry.Source == AncmVersionToMatch(deploymentResult) && if (eventLogEntry.Source == AncmVersionToMatch(deploymentResult) &&
processIdString == eventLogEntry.ReplacementStrings[1] && processIdString == eventLogEntry.ReplacementStrings[1])
expectedRegex.IsMatch(eventLogEntry.ReplacementStrings[0]))
{ {
return; yield return eventLogEntry;
} }
} }
Assert.True(false, $"'{expectedRegexMatchString}' didn't match any event log messaged.");
} }
private static string AncmVersionToMatch(IISDeploymentResult deploymentResult) private static string AncmVersionToMatch(IISDeploymentResult deploymentResult)
@ -88,5 +95,51 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
"AspNetCore Module" + "AspNetCore Module" +
(deploymentResult.DeploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2 ? " V2" : ""); (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> <GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\;</AdditionalLibraryDirectories> <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> <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link> </Link>
<Lib> <Lib>
@ -128,7 +128,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\;</AdditionalLibraryDirectories> <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> <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link> </Link>
<Lib> <Lib>
@ -156,7 +156,7 @@
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\;</AdditionalLibraryDirectories> <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> <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link> </Link>
<Lib> <Lib>
@ -184,7 +184,7 @@
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\;</AdditionalLibraryDirectories> <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> <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link> </Link>
<Lib> <Lib>

View File

@ -64,7 +64,7 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
struHostFxrDllLocation, struHostFxrDllLocation,
struExeLocation, struExeLocation,
bstrArray), // args array. bstrArray), // args array.
HOSTFXR_UTILITY::StartupParametersResolutionException); InvalidOperationException);
} }
TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
@ -118,5 +118,5 @@ TEST(GetHostFxrArguments, InvalidParams)
struHostFxrDllLocation, struHostFxrDllLocation,
struExeLocation, struExeLocation,
bstrArray), // args array. 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); 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) TEST(InProcessTest, GeneratesVirtualPath)

View File

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

View File

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
@ -26,16 +27,42 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
public async Task ServerShutsDownWhenMainExits() public async Task ServerShutsDownWhenMainExits()
{ {
var parameters = _fixture.GetBaseDeploymentParameters(publish: true); var parameters = _fixture.GetBaseDeploymentParameters(publish: true);
var result = await DeployAsync(parameters); var deploymentResult = await DeployAsync(parameters);
try try
{ {
await result.HttpClient.GetAsync("/Shutdown"); await deploymentResult.HttpClient.GetAsync("/Shutdown");
} }
catch (HttpRequestException ex) when (ex.InnerException is IOException) catch (HttpRequestException ex) when (ex.InnerException is IOException)
{ {
// Server might close a connection before request completes // 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] [ConditionalFact]

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer(); 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] [ConditionalFact]
@ -137,11 +137,67 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(_fixture.OverriddenServerWebSite, publish: true)); var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(_fixture.OverriddenServerWebSite, publish: true));
var response = await deploymentResult.HttpClient.GetAsync("/"); 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(); 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] [ConditionalFact]
@ -158,10 +214,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer(); 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(); private static Dictionary<string, (string, Action<XElement>)> InvalidConfigTransformations = InitInvalidConfigTransformations();
public static IEnumerable<object[]> InvalidConfigTransformationsScenarios => InvalidConfigTransformations.ToTheoryData(); public static IEnumerable<object[]> InvalidConfigTransformationsScenarios => InvalidConfigTransformations.ToTheoryData();
@ -177,7 +235,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode); Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode);
StopServer(); 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() 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", dictionary.Add("App in bin subdirectory full path to dll using exec and quotes",
parameters => { parameters => {
MoveApplication(parameters, "bin"); MoveApplication(parameters, "bin");
TransformArguments(parameters, (arguments, root) => "exec " + Path.Combine(root, "bin", arguments)); parameters.TransformArguments((arguments, root) => "exec " + Path.Combine(root, "bin", arguments));
return ""; return "";
}); });
dictionary.Add("App in subdirectory with space", dictionary.Add("App in subdirectory with space",
parameters => { parameters => {
MoveApplication(parameters, pathWithSpace); MoveApplication(parameters, pathWithSpace);
TransformArguments(parameters, (arguments, root) => Path.Combine(pathWithSpace, arguments)); parameters.TransformArguments((arguments, root) => Path.Combine(pathWithSpace, arguments));
return ""; return "";
}); });
dictionary.Add("App in subdirectory with space and full path to dll", dictionary.Add("App in subdirectory with space and full path to dll",
parameters => { parameters => {
MoveApplication(parameters, pathWithSpace); MoveApplication(parameters, pathWithSpace);
TransformArguments(parameters, (arguments, root) => Path.Combine(root, pathWithSpace, arguments)); parameters.TransformArguments((arguments, root) => Path.Combine(root, pathWithSpace, arguments));
return ""; return "";
}); });
dictionary.Add("App in bin subdirectory with space full path to dll using exec and quotes", dictionary.Add("App in bin subdirectory with space full path to dll using exec and quotes",
parameters => { parameters => {
MoveApplication(parameters, pathWithSpace); 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"; return "extra|arguments";
}); });
dictionary.Add("App in bin subdirectory and quoted argument", dictionary.Add("App in bin subdirectory and quoted argument",
parameters => { parameters => {
MoveApplication(parameters, "bin"); 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"; return "extra argument";
}); });
dictionary.Add("App in bin subdirectory full path to dll", dictionary.Add("App in bin subdirectory full path to dll",
parameters => { parameters => {
MoveApplication(parameters, "bin"); 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 "extra|arguments";
}); });
return dictionary; return dictionary;
@ -288,16 +349,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
dictionary.Add("App in subdirectory", dictionary.Add("App in subdirectory",
parameters => { parameters => {
MoveApplication(parameters, pathWithSpace); MoveApplication(parameters, pathWithSpace);
TransformPath(parameters, (path, root) => Path.Combine(pathWithSpace, path)); parameters.TransformPath((path, root) => Path.Combine(pathWithSpace, path));
TransformArguments(parameters, (arguments, root) => "\"additional argument\""); parameters.TransformArguments((arguments, root) => "\"additional argument\"");
return "additional argument"; return "additional argument";
}); });
dictionary.Add("App in bin subdirectory full path", dictionary.Add("App in bin subdirectory full path",
parameters => { parameters => {
MoveApplication(parameters, pathWithSpace); MoveApplication(parameters, pathWithSpace);
TransformPath(parameters, (path, root) => Path.Combine(root, pathWithSpace, path)); parameters.TransformPath((path, root) => Path.Combine(root, pathWithSpace, path));
TransformArguments(parameters, (arguments, root) => "additional arguments"); parameters.TransformArguments((arguments, root) => "additional arguments");
return "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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
namespace IISTestSite namespace IISTestSite
{ {
public static class Program 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 mode = args.FirstOrDefault();
var randomNumber = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_RANDOM_VALUE");
// Semicolons are appended to env variables; removing them. switch (mode)
if (envVariable == "CheckLargeStdOutWrites")
{ {
Console.WriteLine(new string('a', 4096)); // Semicolons are appended to env variables; removing them.
} case "CheckLargeStdOutWrites":
else if (envVariable == "CheckLargeStdErrWrites") Console.WriteLine(new string('a', 4096));
{ break;
Console.Error.WriteLine(new string('a', 4096)); case "CheckLargeStdErrWrites":
Console.Error.Flush(); Console.Error.WriteLine(new string('a', 4096));
} Console.Error.Flush();
else if (envVariable == "CheckLogFile") break;
{ case "CheckLogFile":
Console.WriteLine($"Random number: {randomNumber}"); Console.WriteLine($"Random number: {args[1]}");
} break;
else if (envVariable == "CheckErrLogFile") case "CheckErrLogFile":
{ Console.Error.WriteLine($"Random number: {args[1]}");
Console.Error.WriteLine($"Random number: {randomNumber}"); Console.Error.Flush();
Console.Error.Flush(); break;
} case "CheckOversizedStdErrWrites":
else if (envVariable == "CheckOversizedStdErrWrites") Console.WriteLine(new string('a', 5000));
{ break;
Console.WriteLine(new string('a', 5000)); 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)); app.Run(async context => await context.Response.WriteAsync("OK"));
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;
} }
} }
} }