Fix dotnet.exe abort exception and startup/shutdown timeouts (#1352)
This commit is contained in:
parent
db01ae3717
commit
0459b6d0d4
|
|
@ -56,10 +56,10 @@ HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, Shim
|
|||
std::unique_ptr<IOutputManager> outputManager;
|
||||
|
||||
RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create(
|
||||
NULL,
|
||||
pConfiguration.QueryProcessPath().c_str(),
|
||||
L"",
|
||||
pConfiguration.QueryProcessPath(),
|
||||
pApplication.GetApplicationPhysicalPath(),
|
||||
pConfiguration.QueryArguments().c_str(),
|
||||
pConfiguration.QueryArguments(),
|
||||
options));
|
||||
|
||||
location = options->GetDotnetExeLocation();
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@
|
|||
#include "exceptions.h"
|
||||
#include <unordered_map>
|
||||
|
||||
#define DEFAULT_HASH_BUCKETS 17
|
||||
|
||||
//
|
||||
// This class will manage the lifecycle of all Asp.Net Core applciation
|
||||
// It should be global singleton.
|
||||
|
|
|
|||
|
|
@ -195,7 +195,6 @@
|
|||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="application.h" />
|
||||
<ClInclude Include="ConfigurationLoadException.h" />
|
||||
<ClInclude Include="ConfigurationSection.h" />
|
||||
<ClInclude Include="ConfigurationSource.h" />
|
||||
<ClInclude Include="config_utility.h" />
|
||||
|
|
@ -211,9 +210,11 @@
|
|||
<ClInclude Include="hostfxr_utility.h" />
|
||||
<ClInclude Include="iapplication.h" />
|
||||
<ClInclude Include="debugutil.h" />
|
||||
<ClInclude Include="InvalidOperationException.h" />
|
||||
<ClInclude Include="IOutputManager.h" />
|
||||
<ClInclude Include="irequesthandler.h" />
|
||||
<ClInclude Include="LoggingHelpers.h" />
|
||||
<ClInclude Include="ModuleHelpers.h" />
|
||||
<ClInclude Include="NonCopyable.h" />
|
||||
<ClInclude Include="NullOutputManager.h" />
|
||||
<ClInclude Include="PipeOutputManager.h" />
|
||||
|
|
|
|||
|
|
@ -26,6 +26,16 @@ bool ConfigurationSection::GetRequiredBool(const std::wstring& name) const
|
|||
return result.value();
|
||||
}
|
||||
|
||||
DWORD ConfigurationSection::GetRequiredLong(const std::wstring& name) const
|
||||
{
|
||||
auto result = GetLong(name);
|
||||
if (!result.has_value())
|
||||
{
|
||||
ThrowRequiredException(name);
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
|
||||
DWORD ConfigurationSection::GetRequiredTimespan(const std::wstring& name) const
|
||||
{
|
||||
auto result = GetTimespan(name);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath"
|
||||
#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments"
|
||||
#define CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT L""
|
||||
#define CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT L"startupTimeLimit"
|
||||
#define CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT L"shutdownTimeLimit"
|
||||
#define CS_ASPNETCORE_HOSTING_MODEL_OUTOFPROCESS L"outofprocess"
|
||||
#define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess"
|
||||
#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel"
|
||||
|
|
@ -31,10 +33,12 @@ public:
|
|||
virtual ~ConfigurationSection() = default;
|
||||
virtual std::optional<std::wstring> GetString(const std::wstring& name) const = 0;
|
||||
virtual std::optional<bool> GetBool(const std::wstring& name) const = 0;
|
||||
virtual std::optional<DWORD> GetLong(const std::wstring& name) const = 0;
|
||||
virtual std::optional<DWORD> GetTimespan(const std::wstring& name) const = 0;
|
||||
|
||||
std::wstring GetRequiredString(const std::wstring& name) const;
|
||||
bool GetRequiredBool(const std::wstring& name) const;
|
||||
DWORD GetRequiredLong(const std::wstring& name) const;
|
||||
DWORD GetRequiredTimespan(const std::wstring& name) const;
|
||||
|
||||
virtual std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(const std::wstring& name) const = 0;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
@ -28,6 +28,17 @@ std::optional<bool> WebConfigConfigurationSection::GetBool(const std::wstring& n
|
|||
return std::make_optional(result);
|
||||
}
|
||||
|
||||
std::optional<DWORD> WebConfigConfigurationSection::GetLong(const std::wstring& name) const
|
||||
{
|
||||
DWORD result;
|
||||
if (FAILED_LOG(GetElementDWORDProperty(m_element, name.c_str(), &result)))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional(result);
|
||||
}
|
||||
|
||||
std::optional<DWORD> WebConfigConfigurationSection::GetTimespan(const std::wstring& name) const
|
||||
{
|
||||
ULONGLONG result;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public:
|
|||
|
||||
std::optional<std::wstring> GetString(const std::wstring& name) const override;
|
||||
std::optional<bool> GetBool(const std::wstring& name) const override;
|
||||
std::optional<DWORD> GetLong(const std::wstring& name) const override;
|
||||
std::optional<DWORD> GetTimespan(const std::wstring& name) const override;
|
||||
std::vector<std::pair<std::wstring, std::wstring>> GetKeyValuePairs(const std::wstring& name) const override;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "iapplication.h"
|
||||
#include "ntassert.h"
|
||||
#include "SRWExclusiveLock.h"
|
||||
#include "SRWSharedLock.h"
|
||||
|
||||
class APPLICATION : public IAPPLICATION
|
||||
{
|
||||
|
|
@ -15,11 +16,6 @@ public:
|
|||
APPLICATION(const APPLICATION&) = delete;
|
||||
const APPLICATION& operator=(const APPLICATION&) = delete;
|
||||
|
||||
APPLICATION_STATUS
|
||||
QueryStatus() override
|
||||
{
|
||||
return m_fStopCalled ? APPLICATION_STATUS::RECYCLED : APPLICATION_STATUS::RUNNING;
|
||||
}
|
||||
|
||||
APPLICATION(const IHttpApplication& pHttpApplication)
|
||||
: m_fStopCalled(false),
|
||||
|
|
@ -32,6 +28,12 @@ public:
|
|||
m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath);
|
||||
}
|
||||
|
||||
APPLICATION_STATUS
|
||||
QueryStatus() override
|
||||
{
|
||||
SRWSharedLock stateLock(m_stateLock);
|
||||
return m_fStopCalled ? APPLICATION_STATUS::RECYCLED : APPLICATION_STATUS::RUNNING;
|
||||
}
|
||||
|
||||
VOID
|
||||
Stop(bool fServerInitiated) override
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <system_error>
|
||||
|
||||
#include "debugutil.h"
|
||||
#include "StringHelpers.h"
|
||||
#include "InvalidOperationException.h"
|
||||
|
||||
#define LOCATION_INFO_ENABLED TRUE
|
||||
|
||||
|
|
@ -41,10 +43,10 @@
|
|||
#define FINISHED_LAST_ERROR_IF(condition) do { if (condition) { hr = LogLastError(LOCATION_INFO); goto Finished; }} while (0, 0)
|
||||
#define FINISHED_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { hr = LogLastError(LOCATION_INFO); goto Finished; }} while (0, 0)
|
||||
|
||||
#define THROW_LAST_ERROR() do { ThrowResultException(LogLastError(LOCATION_INFO)); } while (0, 0)
|
||||
#define THROW_LAST_ERROR() do { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); } while (0, 0)
|
||||
#define THROW_IF_FAILED(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { ThrowResultException(LOCATION_INFO, __hrRet); }} while (0, 0)
|
||||
#define THROW_LAST_ERROR_IF(condition) do { if (condition) { ThrowResultException(LogLastError(LOCATION_INFO)); }} while (0, 0)
|
||||
#define THROW_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { ThrowResultException(LogLastError(LOCATION_INFO)); }} while (0, 0)
|
||||
#define THROW_LAST_ERROR_IF(condition) do { if (condition) { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); }} while (0, 0)
|
||||
#define THROW_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); }} while (0, 0)
|
||||
|
||||
#define THROW_IF_NULL_ALLOC(ptr) Throw_IfNullAlloc(ptr)
|
||||
|
||||
|
|
@ -152,3 +154,7 @@ template <typename PointerT> auto Throw_IfNullAlloc(PointerT pointer)
|
|||
}
|
||||
return pointer;
|
||||
}
|
||||
__declspec(noinline) inline std::wstring GetUnexpectedExceptionMessage(std::runtime_error& ex)
|
||||
{
|
||||
return format(L"Unexpected exception: %S", ex.what());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
|
|||
}
|
||||
else if (!ends_with(expandedProcessPath, L".exe", true))
|
||||
{
|
||||
throw StartupParametersResolutionException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str()));
|
||||
throw InvalidOperationException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str()));
|
||||
}
|
||||
|
||||
// Check if the absolute path is to dotnet or not.
|
||||
|
|
@ -51,7 +51,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
|
|||
|
||||
if (applicationArguments.empty())
|
||||
{
|
||||
throw StartupParametersResolutionException(L"Application arguments are empty.");
|
||||
throw InvalidOperationException(L"Application arguments are empty.");
|
||||
}
|
||||
|
||||
if (dotnetExePath.empty())
|
||||
|
|
@ -92,7 +92,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
|
|||
LOG_INFOF(L"Checking application.dll at '%ls'", applicationDllPath.c_str());
|
||||
if (!is_regular_file(applicationDllPath))
|
||||
{
|
||||
throw StartupParametersResolutionException(format(L"Application .dll was not found at %s", applicationDllPath.c_str()));
|
||||
throw InvalidOperationException(format(L"Application .dll was not found at %s", applicationDllPath.c_str()));
|
||||
}
|
||||
|
||||
hostFxrDllPath = executablePath.parent_path() / "hostfxr.dll";
|
||||
|
|
@ -131,7 +131,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
|
|||
// If the processPath file does not exist and it doesn't include dotnet.exe or dotnet
|
||||
// then it is an invalid argument.
|
||||
//
|
||||
throw StartupParametersResolutionException(format(L"Executable was not found at '%s'", executablePath.c_str()));
|
||||
throw InvalidOperationException(format(L"Executable was not found at '%s'", executablePath.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ HOSTFXR_UTILITY::AppendArguments(
|
|||
auto pwzArgs = std::unique_ptr<LPWSTR[], LocalFreeDeleter>(CommandLineToArgvW(applicationArguments.c_str(), &argc));
|
||||
if (!pwzArgs)
|
||||
{
|
||||
throw StartupParametersResolutionException(format(L"Unable parse command line arguments '%s'", applicationArguments.c_str()));
|
||||
throw InvalidOperationException(format(L"Unable parse command line arguments '%s'", applicationArguments.c_str()));
|
||||
}
|
||||
|
||||
for (int intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++)
|
||||
|
|
@ -246,7 +246,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
{
|
||||
LOG_INFOF(L"Absolute path to dotnet.exe was not found at '%ls'", requestedPath.c_str());
|
||||
|
||||
throw StartupParametersResolutionException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str()));
|
||||
throw InvalidOperationException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str()));
|
||||
}
|
||||
|
||||
const auto dotnetViaWhere = InvokeWhereToFindDotnet();
|
||||
|
|
@ -266,7 +266,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
}
|
||||
|
||||
LOG_INFOF(L"dotnet.exe not found");
|
||||
throw StartupParametersResolutionException(format(
|
||||
throw InvalidOperationException(format(
|
||||
L"Could not find dotnet.exe at '%s' or using the system PATH environment variable."
|
||||
" Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process.",
|
||||
processPath.c_str()));
|
||||
|
|
@ -284,14 +284,14 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
|||
|
||||
if (!is_directory(hostFxrBase))
|
||||
{
|
||||
throw StartupParametersResolutionException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str()));
|
||||
throw InvalidOperationException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str()));
|
||||
}
|
||||
|
||||
FindDotNetFolders(hostFxrBase, versionFolders);
|
||||
|
||||
if (versionFolders.empty())
|
||||
{
|
||||
throw StartupParametersResolutionException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str()));
|
||||
throw InvalidOperationException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str()));
|
||||
}
|
||||
|
||||
const auto highestVersion = FindHighestDotNetVersion(versionFolders);
|
||||
|
|
@ -299,7 +299,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
|||
|
||||
if (!is_regular_file(hostFxrPath))
|
||||
{
|
||||
throw StartupParametersResolutionException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str()));
|
||||
throw InvalidOperationException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str()));
|
||||
}
|
||||
|
||||
LOG_INFOF(L"hostfxr.dll located at '%ls'", hostFxrPath.c_str());
|
||||
|
|
|
|||
|
|
@ -29,20 +29,6 @@ public:
|
|||
std::vector<std::wstring> &arguments
|
||||
);
|
||||
|
||||
class StartupParametersResolutionException: public std::runtime_error
|
||||
{
|
||||
public:
|
||||
StartupParametersResolutionException(std::wstring msg)
|
||||
: runtime_error("Startup parameter resulution error occured"), message(std::move(msg))
|
||||
{
|
||||
}
|
||||
|
||||
std::wstring get_message() const { return message; }
|
||||
|
||||
private:
|
||||
std::wstring message;
|
||||
};
|
||||
|
||||
static
|
||||
void
|
||||
AppendArguments(
|
||||
|
|
@ -96,7 +82,7 @@ private:
|
|||
|
||||
struct LocalFreeDeleter
|
||||
{
|
||||
void operator ()(LPWSTR* ptr) const
|
||||
void operator ()(_In_ LPWSTR* ptr) const
|
||||
{
|
||||
LocalFree(ptr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,18 +9,19 @@
|
|||
#include "EventLog.h"
|
||||
|
||||
HRESULT HOSTFXR_OPTIONS::Create(
|
||||
_In_ PCWSTR pcwzDotnetExePath,
|
||||
_In_ PCWSTR pcwzProcessPath,
|
||||
_In_ PCWSTR pcwzApplicationPhysicalPath,
|
||||
_In_ PCWSTR pcwzArguments,
|
||||
_In_ const std::wstring& pcwzDotnetExePath,
|
||||
_In_ const std::wstring& pcwzProcessPath,
|
||||
_In_ const std::wstring& pcwzApplicationPhysicalPath,
|
||||
_In_ const std::wstring& pcwzArguments,
|
||||
_Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper)
|
||||
{
|
||||
std::filesystem::path knownDotnetLocation;
|
||||
|
||||
if (pcwzDotnetExePath != nullptr)
|
||||
if (!pcwzDotnetExePath.empty())
|
||||
{
|
||||
knownDotnetLocation = pcwzDotnetExePath;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::filesystem::path hostFxrDllPath;
|
||||
|
|
@ -40,17 +41,25 @@ HRESULT HOSTFXR_OPTIONS::Create(
|
|||
}
|
||||
ppWrapper = std::make_unique<HOSTFXR_OPTIONS>(knownDotnetLocation, hostFxrDllPath, arguments);
|
||||
}
|
||||
catch (HOSTFXR_UTILITY::StartupParametersResolutionException &resolutionException)
|
||||
catch (InvalidOperationException &ex)
|
||||
{
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
|
||||
pcwzApplicationPhysicalPath,
|
||||
resolutionException.get_message().c_str());
|
||||
pcwzApplicationPhysicalPath.c_str(),
|
||||
ex.as_wstring().c_str());
|
||||
|
||||
return E_FAIL;
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
catch (std::runtime_error &ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
|
||||
pcwzApplicationPhysicalPath.c_str(),
|
||||
GetUnexpectedExceptionMessage(ex).c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ public:
|
|||
|
||||
static
|
||||
HRESULT Create(
|
||||
_In_ PCWSTR pcwzExeLocation,
|
||||
_In_ PCWSTR pcwzProcessPath,
|
||||
_In_ PCWSTR pcwzApplicationPhysicalPath,
|
||||
_In_ PCWSTR pcwzArguments,
|
||||
_In_ const std::wstring& pcwzExeLocation,
|
||||
_In_ const std::wstring& pcwzProcessPath,
|
||||
_In_ const std::wstring& pcwzApplicationPhysicalPath,
|
||||
_In_ const std::wstring& pcwzArguments,
|
||||
_Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper);
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@
|
|||
#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'."
|
||||
#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'."
|
||||
#define ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown application '%s'."
|
||||
#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application, ErrorCode = '0x%x."
|
||||
#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application. %s"
|
||||
#define ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG L"Application '%s' has shutdown."
|
||||
#define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool."
|
||||
#define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%d' other than the one of running application(s)."
|
||||
#define ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG L"Configuration load error. %s"
|
||||
#define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'."
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x'. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x'. Please check the stderr logs for more information."
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, exit code = '%d'. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, exit code = '%d'. Please check the stderr logs for more information."
|
||||
#define ASPNETCORE_EVENT_APP_IN_SHUTDOWN_MSG L"Application shutting down."
|
||||
#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' was recycled after detecting the app_offline file."
|
||||
#define ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR_MSG L"Monitoring app_offline.htm failed for application '%s', ErrorCode '0x%x'. "
|
||||
|
|
@ -39,7 +39,8 @@
|
|||
#define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process."
|
||||
#define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled"
|
||||
#define ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG L"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '%s'."
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information."
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, exception code = '0x%x. Please check the stderr logs for more information."
|
||||
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, exception code = '0x%x. Last 4KB characters of captured stdout and stderr logs:\r\n%s"
|
||||
#define ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG L"Could not find the assembly '%s' for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application. Captured output: %s"
|
||||
#define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress."
|
||||
#define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully."
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include "InProcessApplicationBase.h"
|
||||
|
||||
hostfxr_main_fn InProcessApplicationBase::s_fMainCallback = NULL;
|
||||
|
||||
InProcessApplicationBase::InProcessApplicationBase(
|
||||
IHttpServer& pHttpServer,
|
||||
IHttpApplication& pHttpApplication)
|
||||
|
|
@ -35,7 +33,21 @@ InProcessApplicationBase::StopInternal(bool fServerInitiated)
|
|||
}
|
||||
else
|
||||
{
|
||||
exit(0);
|
||||
// Send WM_QUIT to the main window to initiate graceful shutdown
|
||||
EnumWindows([](HWND hwnd, LPARAM) -> BOOL
|
||||
{
|
||||
DWORD processId;
|
||||
|
||||
if (GetWindowThreadProcessId(hwnd, &processId) &&
|
||||
processId == GetCurrentProcessId() &&
|
||||
GetConsoleWindow() != hwnd)
|
||||
{
|
||||
PostMessage(hwnd, WM_QUIT, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "application.h"
|
||||
#include "AppOfflineTrackingApplication.h"
|
||||
|
||||
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs
|
||||
|
|
@ -23,8 +22,5 @@ public:
|
|||
protected:
|
||||
BOOL m_fRecycleCalled;
|
||||
IHttpServer& m_pHttpServer;
|
||||
// Allows to override call to hostfxr_main with custome callback
|
||||
// used in testing
|
||||
static hostfxr_main_fn s_fMainCallback;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,41 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "InProcessOptions.h"
|
||||
#include "InvalidOperationException.h"
|
||||
#include "EventLog.h"
|
||||
|
||||
HRESULT InProcessOptions::Create(
|
||||
IHttpServer& pServer,
|
||||
IHttpApplication& pHttpApplication,
|
||||
std::unique_ptr<InProcessOptions>& options)
|
||||
{
|
||||
try
|
||||
{
|
||||
const WebConfigConfigurationSource configurationSource(pServer.GetAdminManager(), pHttpApplication);
|
||||
options = std::make_unique<InProcessOptions>(configurationSource);
|
||||
}
|
||||
catch (InvalidOperationException& ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
|
||||
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
|
||||
ex.as_wstring().c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
|
||||
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
|
||||
GetUnexpectedExceptionMessage(ex).c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSource) :
|
||||
m_fStdoutLogEnabled(false),
|
||||
|
|
@ -18,6 +53,8 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
|
|||
m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE);
|
||||
m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE);
|
||||
m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES);
|
||||
m_dwStartupTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT) * 1000;
|
||||
m_dwShutdownTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT) * 1000;
|
||||
|
||||
const auto basicAuthSection = configurationSource.GetSection(CS_BASIC_AUTHENTICATION_SECTION);
|
||||
m_fBasicAuthEnabled = basicAuthSection && basicAuthSection->GetBool(CS_ENABLED).value_or(false);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <string>
|
||||
#include "ConfigurationSource.h"
|
||||
#include "WebConfigConfigurationSource.h"
|
||||
|
||||
class InProcessOptions: NonCopyable
|
||||
{
|
||||
|
|
@ -60,12 +61,22 @@ public:
|
|||
DWORD
|
||||
QueryStartupTimeLimitInMS() const
|
||||
{
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
return INFINITE;
|
||||
}
|
||||
|
||||
return m_dwStartupTimeLimitInMS;
|
||||
}
|
||||
|
||||
DWORD
|
||||
QueryShutdownTimeLimitInMS() const
|
||||
{
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
return INFINITE;
|
||||
}
|
||||
|
||||
return m_dwShutdownTimeLimitInMS;
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +87,12 @@ public:
|
|||
}
|
||||
|
||||
InProcessOptions(const ConfigurationSource &configurationSource);
|
||||
|
||||
static
|
||||
HRESULT InProcessOptions::Create(
|
||||
IHttpServer& pServer,
|
||||
IHttpApplication& pHttpApplication,
|
||||
std::unique_ptr<InProcessOptions>& options);
|
||||
|
||||
private:
|
||||
std::wstring m_strArguments;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@
|
|||
|
||||
HRESULT StartupExceptionApplication::CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler)
|
||||
{
|
||||
*pRequestHandler = new StartupExceptionHandler(pHttpContext, m_disableLogs, this);
|
||||
*pRequestHandler = new StartupExceptionHandler(pHttpContext, m_disableLogs);
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,29 +16,6 @@ public:
|
|||
: m_disableLogs(disableLogs),
|
||||
InProcessApplicationBase(pServer, pApplication)
|
||||
{
|
||||
html500Page = std::string("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \
|
||||
<html xmlns=\"http://www.w3.org/1999/xhtml\"> \
|
||||
<head> \
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" /> \
|
||||
<title> IIS 500.30 Error </title><style type=\"text/css\"></style></head> \
|
||||
<body> <div id = \"content\"> \
|
||||
<div class = \"content-container\"><h3> HTTP Error 500.30 - ANCM In-Process Start Failure </h3></div> \
|
||||
<div class = \"content-container\"> \
|
||||
<fieldset> <h4> Common causes of this issue: </h4> \
|
||||
<ul><li> The application failed to start </li> \
|
||||
<li> The application started but then stopped </li> \
|
||||
<li> The application started but threw an exception during startup </li></ul></fieldset> \
|
||||
</div> \
|
||||
<div class = \"content-container\"> \
|
||||
<fieldset><h4> Troubleshooting steps: </h4> \
|
||||
<ul><li> Check the system event log for error messages </li> \
|
||||
<li> Enable logging the application process' stdout messages </li> \
|
||||
<li> Attach a debugger to the application process and inspect </li></ul></fieldset> \
|
||||
<fieldset><h4> For more information visit: \
|
||||
<a href=\"https://go.microsoft.com/fwlink/?LinkID=808681\"> <cite> https://go.microsoft.com/fwlink/?LinkID=808681 </cite></a></h4> \
|
||||
</fieldset> \
|
||||
</div> \
|
||||
</div></body></html>");
|
||||
}
|
||||
|
||||
~StartupExceptionApplication() = default;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,30 @@
|
|||
#include "StartupExceptionApplication.h"
|
||||
#include "StartupExceptionHandler.h"
|
||||
|
||||
std::string StartupExceptionHandler::s_html500Page = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \
|
||||
<html xmlns=\"http://www.w3.org/1999/xhtml\"> \
|
||||
<head> \
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" /> \
|
||||
<title> IIS 500.30 Error </title><style type=\"text/css\"></style></head> \
|
||||
<body> <div id = \"content\"> \
|
||||
<div class = \"content-container\"><h3> HTTP Error 500.30 - ANCM In-Process Start Failure </h3></div> \
|
||||
<div class = \"content-container\"> \
|
||||
<fieldset> <h4> Common causes of this issue: </h4> \
|
||||
<ul><li> The application failed to start </li> \
|
||||
<li> The application started but then stopped </li> \
|
||||
<li> The application started but threw an exception during startup </li></ul></fieldset> \
|
||||
</div> \
|
||||
<div class = \"content-container\"> \
|
||||
<fieldset><h4> Troubleshooting steps: </h4> \
|
||||
<ul><li> Check the system event log for error messages </li> \
|
||||
<li> Enable logging the application process' stdout messages </li> \
|
||||
<li> Attach a debugger to the application process and inspect </li></ul></fieldset> \
|
||||
<fieldset><h4> For more information visit: \
|
||||
<a href=\"https://go.microsoft.com/fwlink/?LinkID=808681\"> <cite> https://go.microsoft.com/fwlink/?LinkID=808681 </cite></a></h4> \
|
||||
</fieldset> \
|
||||
</div> \
|
||||
</div></body></html>";
|
||||
|
||||
REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler()
|
||||
{
|
||||
if (!m_disableLogs)
|
||||
|
|
@ -16,11 +40,9 @@ REQUEST_NOTIFICATION_STATUS StartupExceptionHandler::OnExecuteRequestHandler()
|
|||
(USHORT)strlen("text/html"),
|
||||
FALSE
|
||||
);
|
||||
const std::string& html500Page = m_pApplication->GetStaticHtml500Content();
|
||||
|
||||
DataChunk.DataChunkType = HttpDataChunkFromMemory;
|
||||
DataChunk.FromMemory.pBuffer = (PVOID)html500Page.c_str();
|
||||
DataChunk.FromMemory.BufferLength = (ULONG)html500Page.size();
|
||||
DataChunk.FromMemory.pBuffer = (PVOID)s_html500Page.c_str();
|
||||
DataChunk.FromMemory.BufferLength = (ULONG)s_html500Page.size();
|
||||
pResponse->WriteEntityChunkByReference(&DataChunk);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "requesthandler.h"
|
||||
|
||||
class StartupExceptionApplication;
|
||||
|
|
@ -10,19 +11,23 @@ class StartupExceptionApplication;
|
|||
class StartupExceptionHandler : public REQUEST_HANDLER
|
||||
{
|
||||
public:
|
||||
StartupExceptionHandler(IHttpContext* pContext, BOOL disableLogs, StartupExceptionApplication* pApplication)
|
||||
StartupExceptionHandler(IHttpContext* pContext, BOOL disableLogs)
|
||||
:
|
||||
m_pContext(pContext),
|
||||
m_disableLogs(disableLogs),
|
||||
m_pApplication(pApplication)
|
||||
m_disableLogs(disableLogs)
|
||||
{
|
||||
}
|
||||
|
||||
~StartupExceptionHandler()
|
||||
{
|
||||
|
||||
}
|
||||
REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() override;
|
||||
|
||||
private:
|
||||
IHttpContext * m_pContext;
|
||||
BOOL m_disableLogs;
|
||||
StartupExceptionApplication* m_pApplication;
|
||||
|
||||
static
|
||||
std::string s_html500Page;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -110,39 +110,25 @@ CreateApplication(
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
const WebConfigConfigurationSource configurationSource(pServer->GetAdminManager(), *pHttpApplication);
|
||||
auto pConfig = std::make_unique<InProcessOptions>(configurationSource);
|
||||
|
||||
BOOL disableStartupPage = pConfig->QueryDisableStartUpErrorPage();
|
||||
|
||||
auto pApplication = std::make_unique<IN_PROCESS_APPLICATION>(*pServer, *pHttpApplication, std::move(pConfig), pParameters, nParameters);
|
||||
|
||||
// never create two inprocess applications in one process
|
||||
g_fInProcessApplicationCreated = true;
|
||||
if (FAILED_LOG(pApplication->LoadManagedApplication()))
|
||||
|
||||
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> inProcessApplication;
|
||||
if (!FAILED_LOG(IN_PROCESS_APPLICATION::Start(*pServer, *pHttpApplication, pParameters, nParameters, inProcessApplication)))
|
||||
{
|
||||
*ppApplication = inProcessApplication.release();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_ptr<InProcessOptions> options;
|
||||
THROW_IF_FAILED(InProcessOptions::Create(*pServer, *pHttpApplication, options));
|
||||
// Set the currently running application to a fake application that returns startup exceptions.
|
||||
auto pErrorApplication = std::make_unique<StartupExceptionApplication>(*pServer, *pHttpApplication, disableStartupPage);
|
||||
auto pErrorApplication = std::make_unique<StartupExceptionApplication>(*pServer, *pHttpApplication, options->QueryDisableStartUpErrorPage());
|
||||
|
||||
RETURN_IF_FAILED(pErrorApplication->StartMonitoringAppOffline());
|
||||
*ppApplication = pErrorApplication.release();
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_FAILED(pApplication->StartMonitoringAppOffline());
|
||||
*ppApplication = pApplication.release();
|
||||
}
|
||||
}
|
||||
catch(ConfigurationLoadException &ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
|
||||
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
|
||||
ex.get_message().c_str());
|
||||
|
||||
RETURN_HR(E_FAIL);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@
|
|||
#include "hostfxroptions.h"
|
||||
#include "requesthandler_config.h"
|
||||
#include "environmentvariablehelpers.h"
|
||||
#include "SRWExclusiveLock.h"
|
||||
#include "exceptions.h"
|
||||
#include "LoggingHelpers.h"
|
||||
#include "resources.h"
|
||||
#include "EventLog.h"
|
||||
|
||||
const LPCSTR IN_PROCESS_APPLICATION::s_exeLocationParameterName = "InProcessExeLocation";
|
||||
#include "ModuleHelpers.h"
|
||||
|
||||
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
|
||||
|
||||
|
|
@ -23,10 +21,9 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
|
|||
APPLICATION_PARAMETER *pParameters,
|
||||
DWORD nParameters) :
|
||||
InProcessApplicationBase(pHttpServer, pApplication),
|
||||
m_ProcessExitCode(0),
|
||||
m_fBlockCallbacksIntoManaged(FALSE),
|
||||
m_fShutdownCalledFromNative(FALSE),
|
||||
m_fShutdownCalledFromManaged(FALSE),
|
||||
m_Initialized(false),
|
||||
m_blockManagedCallbacks(true),
|
||||
m_waitForShutdown(true),
|
||||
m_pConfig(std::move(pConfig))
|
||||
{
|
||||
DBG_ASSERT(m_pConfig);
|
||||
|
|
@ -35,161 +32,54 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
|
|||
{
|
||||
if (_stricmp(pParameters[i].pzName, s_exeLocationParameterName) == 0)
|
||||
{
|
||||
m_struExeLocation.Copy(reinterpret_cast<PCWSTR>(pParameters[i].pValue));
|
||||
m_dotnetExeKnownLocation = reinterpret_cast<PCWSTR>(pParameters[i].pValue);
|
||||
}
|
||||
}
|
||||
|
||||
m_status = MANAGED_APPLICATION_STATUS::STARTING;
|
||||
}
|
||||
|
||||
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
|
||||
{
|
||||
s_Application = NULL;
|
||||
s_Application = nullptr;
|
||||
}
|
||||
|
||||
//static
|
||||
DWORD WINAPI
|
||||
IN_PROCESS_APPLICATION::DoShutDown(
|
||||
LPVOID lpParam
|
||||
)
|
||||
{
|
||||
IN_PROCESS_APPLICATION* pApplication = static_cast<IN_PROCESS_APPLICATION*>(lpParam);
|
||||
DBG_ASSERT(pApplication);
|
||||
pApplication->ShutDownInternal();
|
||||
return 0;
|
||||
}
|
||||
|
||||
__override
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(fServerInitiated);
|
||||
HRESULT hr = S_OK;
|
||||
CHandle hThread;
|
||||
DWORD dwThreadStatus = 0;
|
||||
|
||||
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
|
||||
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
dwTimeout = INFINITE;
|
||||
}
|
||||
|
||||
hThread.Attach(CreateThread(
|
||||
NULL, // default security attributes
|
||||
0, // default stack size
|
||||
(LPTHREAD_START_ROUTINE)DoShutDown,
|
||||
this, // thread function arguments
|
||||
0, // default creation flags
|
||||
NULL)); // receive thread identifier
|
||||
|
||||
if ((HANDLE)hThread == NULL)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(hThread, dwTimeout) != WAIT_OBJECT_0)
|
||||
{
|
||||
// if the thread is still running, we need kill it first before exit to avoid AV
|
||||
if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
// Calling back into managed at this point is prone to have AVs
|
||||
// Calling terminate thread here may be our best solution.
|
||||
TerminateThread(hThread, STATUS_CONTROL_C_EXIT);
|
||||
hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
Finished:
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
EventLog::Warn(
|
||||
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
|
||||
ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG,
|
||||
QueryConfigPath().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
EventLog::Info(
|
||||
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL,
|
||||
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG,
|
||||
QueryConfigPath().c_str());
|
||||
}
|
||||
|
||||
{
|
||||
StopClr();
|
||||
InProcessApplicationBase::StopInternal(fServerInitiated);
|
||||
}
|
||||
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::ShutDownInternal()
|
||||
{
|
||||
DWORD dwThreadStatus = 0;
|
||||
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
|
||||
IN_PROCESS_APPLICATION::StopClr()
|
||||
{
|
||||
LOG_INFO(L"Stopping CLR");
|
||||
|
||||
if (IsDebuggerPresent())
|
||||
if (!m_blockManagedCallbacks)
|
||||
{
|
||||
dwTimeout = INFINITE;
|
||||
}
|
||||
// We cannot call into managed if the dll is detaching from the process.
|
||||
// Calling into managed code when the dll is detaching is strictly a bad idea,
|
||||
// and usually results in an AV saying "The string binding is invalid"
|
||||
const auto shutdownHandler = m_ShutdownHandler;
|
||||
|
||||
if (m_fShutdownCalledFromNative ||
|
||||
m_status == MANAGED_APPLICATION_STATUS::STARTING ||
|
||||
m_status == MANAGED_APPLICATION_STATUS::FAIL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
if (m_fShutdownCalledFromNative ||
|
||||
m_status == MANAGED_APPLICATION_STATUS::STARTING ||
|
||||
m_status == MANAGED_APPLICATION_STATUS::FAIL)
|
||||
if (!g_fProcessDetach && shutdownHandler != nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to keep track of when both managed and native initiate shutdown
|
||||
// to avoid AVs. If shutdown has already been initiated in managed, we don't want to call into
|
||||
// managed. We still need to wait on main exiting no matter what. m_fShutdownCalledFromNative
|
||||
// is used for detecting redundant calls and blocking more requests to OnExecuteRequestHandler.
|
||||
m_fShutdownCalledFromNative = TRUE;
|
||||
m_status = MANAGED_APPLICATION_STATUS::SHUTDOWN;
|
||||
|
||||
if (!m_fShutdownCalledFromManaged)
|
||||
{
|
||||
// We cannot call into managed if the dll is detaching from the process.
|
||||
// Calling into managed code when the dll is detaching is strictly a bad idea,
|
||||
// and usually results in an AV saying "The string binding is invalid"
|
||||
if (!g_fProcessDetach)
|
||||
{
|
||||
m_ShutdownHandler(m_ShutdownHandlerContext);
|
||||
m_ShutdownHandler = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Release the lock before we wait on the thread to exit.
|
||||
}
|
||||
|
||||
if (!m_fShutdownCalledFromManaged)
|
||||
{
|
||||
if (m_hThread != NULL &&
|
||||
GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 &&
|
||||
dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
// wait for graceful shutdown, i.e., the exit of the background thread or timeout
|
||||
if (WaitForSingleObject(m_hThread, dwTimeout) != WAIT_OBJECT_0)
|
||||
{
|
||||
// if the thread is still running, we need kill it first before exit to avoid AV
|
||||
if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
// Calling back into managed at this point is prone to have AVs
|
||||
// Calling terminate thread here may be our best solution.
|
||||
TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT);
|
||||
}
|
||||
}
|
||||
shutdownHandler(m_ShutdownHandlerContext);
|
||||
}
|
||||
}
|
||||
|
||||
s_Application = NULL;
|
||||
// Signal shutdown
|
||||
if (m_pShutdownEvent != nullptr)
|
||||
{
|
||||
LOG_IF_FAILED(SetEvent(m_pShutdownEvent));
|
||||
}
|
||||
|
||||
if (m_workerThread.joinable())
|
||||
{
|
||||
// Worker thread would wait for clr to finish and log error if required
|
||||
m_workerThread.join();
|
||||
}
|
||||
|
||||
s_Application = nullptr;
|
||||
}
|
||||
|
||||
VOID
|
||||
|
|
@ -201,169 +91,320 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
|
|||
_In_ VOID* pvShutdownHandlerContext
|
||||
)
|
||||
{
|
||||
LOG_INFO(L"In-process callbacks set");
|
||||
|
||||
m_RequestHandler = request_handler;
|
||||
m_RequestHandlerContext = pvRequstHandlerContext;
|
||||
m_ShutdownHandler = shutdown_handler;
|
||||
m_ShutdownHandlerContext = pvShutdownHandlerContext;
|
||||
m_AsyncCompletionHandler = async_completion_handler;
|
||||
|
||||
m_blockManagedCallbacks = false;
|
||||
m_Initialized = true;
|
||||
|
||||
// Can't check the std err handle as it isn't a critical error
|
||||
// Initialization complete
|
||||
EventLog::Info(
|
||||
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS,
|
||||
ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG,
|
||||
QueryApplicationPhysicalPath().c_str());
|
||||
SetEvent(m_pInitalizeEvent);
|
||||
m_fInitialized = TRUE;
|
||||
|
||||
SetEvent(m_pInitializeEvent);
|
||||
}
|
||||
|
||||
// Will be called by the inprocesshandler
|
||||
HRESULT
|
||||
IN_PROCESS_APPLICATION::LoadManagedApplication
|
||||
(
|
||||
VOID
|
||||
)
|
||||
IN_PROCESS_APPLICATION::LoadManagedApplication()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
DWORD dwTimeout;
|
||||
DWORD dwResult;
|
||||
THROW_LAST_ERROR_IF_NULL(m_pInitializeEvent = CreateEvent(
|
||||
nullptr, // default security attributes
|
||||
TRUE, // manual reset event
|
||||
FALSE, // not set
|
||||
nullptr)); // name
|
||||
|
||||
ReferenceApplication();
|
||||
THROW_LAST_ERROR_IF_NULL(m_pShutdownEvent = CreateEvent(
|
||||
nullptr, // default security attributes
|
||||
TRUE, // manual reset event
|
||||
FALSE, // not set
|
||||
nullptr)); // name
|
||||
|
||||
if (m_status != MANAGED_APPLICATION_STATUS::STARTING)
|
||||
m_workerThread = std::thread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
|
||||
{
|
||||
// Core CLR has already been loaded.
|
||||
// Cannot load more than once even there was a failure
|
||||
if (m_status == MANAGED_APPLICATION_STATUS::FAIL)
|
||||
{
|
||||
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
|
||||
}
|
||||
else if (m_status == MANAGED_APPLICATION_STATUS::SHUTDOWN)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
|
||||
}
|
||||
LOG_INFO(L"Starting in-process worker thread");
|
||||
application->ExecuteApplication();
|
||||
LOG_INFO(L"Stopping in-process worker thread");
|
||||
}, ::ReferenceApplication(this));
|
||||
|
||||
goto Finished;
|
||||
LOG_INFO(L"Waiting for initialization");
|
||||
|
||||
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
|
||||
|
||||
// Wait for shutdown request
|
||||
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
|
||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||
|
||||
if (waitResult == WAIT_TIMEOUT)
|
||||
{
|
||||
// If server wasn't initialized in time shut application down without waiting for CLR thread to exit
|
||||
m_waitForShutdown = false;
|
||||
StopClr();
|
||||
throw InvalidOperationException(format(L"Managed server didn't initialize after %u ms.", m_pConfig->QueryStartupTimeLimitInMS()));
|
||||
}
|
||||
|
||||
// WAIT_OBJECT_0 + 1 is the worker thead handle
|
||||
if (waitResult == WAIT_OBJECT_0 + 1)
|
||||
{
|
||||
SRWExclusiveLock lock(m_stateLock);
|
||||
// Worker thread exited stop
|
||||
StopClr();
|
||||
throw InvalidOperationException(format(L"CLR worker thread exited prematurely"));
|
||||
}
|
||||
|
||||
if (m_status != MANAGED_APPLICATION_STATUS::STARTING)
|
||||
THROW_IF_FAILED(StartMonitoringAppOffline());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IN_PROCESS_APPLICATION::ExecuteApplication()
|
||||
{
|
||||
try
|
||||
{
|
||||
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
|
||||
|
||||
auto context = std::make_shared<ExecuteClrContext>();
|
||||
|
||||
auto pProc = s_fMainCallback;
|
||||
if (pProc == nullptr)
|
||||
{
|
||||
if (m_status == MANAGED_APPLICATION_STATUS::FAIL)
|
||||
HMODULE hModule;
|
||||
// hostfxr should already be loaded by the shim. If not, then we will need
|
||||
// to load it ourselves by finding hostfxr again.
|
||||
THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
|
||||
|
||||
// Get the entry point for main
|
||||
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
|
||||
THROW_LAST_ERROR_IF_NULL(pProc);
|
||||
|
||||
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
|
||||
m_dotnetExeKnownLocation,
|
||||
m_pConfig->QueryProcessPath(),
|
||||
QueryApplicationPhysicalPath(),
|
||||
m_pConfig->QueryArguments(),
|
||||
hostFxrOptions
|
||||
));
|
||||
|
||||
hostFxrOptions->GetArguments(context->m_argc, context->m_argv);
|
||||
THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
|
||||
}
|
||||
context->m_pProc = pProc;
|
||||
|
||||
if (m_pLoggerProvider == nullptr)
|
||||
{
|
||||
THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
|
||||
m_pConfig->QueryStdoutLogEnabled(),
|
||||
!m_pHttpServer.IsCommandLineLaunch(),
|
||||
m_pConfig->QueryStdoutLogFile().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
m_pLoggerProvider));
|
||||
|
||||
LOG_IF_FAILED(m_pLoggerProvider->Start());
|
||||
}
|
||||
|
||||
// There can only ever be a single instance of .NET Core
|
||||
// loaded in the process but we need to get config information to boot it up in the
|
||||
// first place. This is happening in an execute request handler and everyone waits
|
||||
// until this initialization is done.
|
||||
// We set a static so that managed code can call back into this instance and
|
||||
// set the callbacks
|
||||
s_Application = this;
|
||||
|
||||
//Start CLR thread
|
||||
m_clrThread = std::thread(ClrThreadEntryPoint, context);
|
||||
|
||||
// Wait for thread exit or shutdown event
|
||||
const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
|
||||
|
||||
// Wait for shutdown request
|
||||
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
|
||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||
|
||||
LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
|
||||
|
||||
bool clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
|
||||
// shutdown was signaled
|
||||
// only wait for shutdown in case of successful startup
|
||||
if (m_waitForShutdown)
|
||||
{
|
||||
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
|
||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||
|
||||
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
|
||||
|
||||
// At this point CLR thread either finished or timed out, abandon it.
|
||||
m_clrThread.detach();
|
||||
|
||||
LOG_IF_FAILED(m_pLoggerProvider->Stop());
|
||||
|
||||
if (m_fStopCalled)
|
||||
{
|
||||
if (clrThreadExited)
|
||||
{
|
||||
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
|
||||
EventLog::Info(
|
||||
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL,
|
||||
ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG,
|
||||
QueryConfigPath().c_str());
|
||||
}
|
||||
else if (m_status == MANAGED_APPLICATION_STATUS::SHUTDOWN)
|
||||
else
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED);
|
||||
EventLog::Warn(
|
||||
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
|
||||
ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG,
|
||||
QueryConfigPath().c_str());
|
||||
}
|
||||
|
||||
goto Finished;
|
||||
}
|
||||
m_hThread = CreateThread(
|
||||
NULL, // default security attributes
|
||||
0, // default stack size
|
||||
(LPTHREAD_START_ROUTINE)ExecuteAspNetCoreProcess,
|
||||
this, // thread function arguments
|
||||
0, // default creation flags
|
||||
NULL); // receive thread identifier
|
||||
|
||||
if (m_hThread == NULL)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
m_pInitalizeEvent = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual reset event
|
||||
FALSE, // not set
|
||||
NULL); // name
|
||||
|
||||
if (m_pInitalizeEvent == NULL)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
// If the debugger is attached, never timeout
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
dwTimeout = INFINITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
dwTimeout = m_pConfig->QueryStartupTimeLimitInMS();
|
||||
if (clrThreadExited)
|
||||
{
|
||||
UnexpectedThreadExit(*context);
|
||||
// If the inprocess server was initialized, we need to cause recycle to be called on the worker process.
|
||||
// in case when it was not initialized we need to keep server running to serve 502 page
|
||||
if (m_Initialized)
|
||||
{
|
||||
QueueStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HANDLE pHandles[2]{ m_hThread, m_pInitalizeEvent };
|
||||
|
||||
// Wait on either the thread to complete or the event to be set
|
||||
dwResult = WaitForMultipleObjects(2, pHandles, FALSE, dwTimeout);
|
||||
|
||||
// It all timed out
|
||||
if (dwResult == WAIT_TIMEOUT)
|
||||
{
|
||||
// kill the backend thread as loading dotnet timedout
|
||||
TerminateThread(m_hThread, 0);
|
||||
hr = HRESULT_FROM_WIN32(dwResult);
|
||||
goto Finished;
|
||||
}
|
||||
else if (dwResult == WAIT_FAILED)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
// The thread ended it means that something failed
|
||||
if (dwResult == WAIT_OBJECT_0)
|
||||
{
|
||||
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
m_status = MANAGED_APPLICATION_STATUS::RUNNING_MANAGED;
|
||||
}
|
||||
Finished:
|
||||
|
||||
if (FAILED(hr))
|
||||
catch (InvalidOperationException& ex)
|
||||
{
|
||||
m_status = MANAGED_APPLICATION_STATUS::FAIL;
|
||||
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
hr);
|
||||
}
|
||||
DereferenceApplication();
|
||||
ex.as_wstring().c_str());
|
||||
|
||||
return hr;
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
GetUnexpectedExceptionMessage(ex).c_str());
|
||||
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::ExecuteAspNetCoreProcess(
|
||||
_In_ LPVOID pContext
|
||||
)
|
||||
void IN_PROCESS_APPLICATION::QueueStop()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext;
|
||||
DBG_ASSERT(pApplication != NULL);
|
||||
hr = pApplication->ExecuteApplication();
|
||||
//
|
||||
// no need to log the error here as if error happened, the thread will exit
|
||||
// the error will ba catched by caller LoadManagedApplication which will log an error
|
||||
//
|
||||
if (m_fStopCalled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(L"Queueing in-process stop thread");
|
||||
|
||||
std::thread stoppingThread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
|
||||
{
|
||||
LOG_INFO(L"Starting in-process stop thread");
|
||||
application->Stop(false);
|
||||
LOG_INFO(L"Stopping in-process stop thread");
|
||||
}, ::ReferenceApplication(this));
|
||||
|
||||
stoppingThread.detach();
|
||||
}
|
||||
|
||||
HRESULT IN_PROCESS_APPLICATION::Start(
|
||||
IHttpServer& pServer,
|
||||
IHttpApplication& pHttpApplication,
|
||||
APPLICATION_PARAMETER* pParameters,
|
||||
DWORD nParameters,
|
||||
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>& application)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::unique_ptr<InProcessOptions> options;
|
||||
THROW_IF_FAILED(InProcessOptions::Create(pServer, pHttpApplication, options));
|
||||
application = std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>(
|
||||
new IN_PROCESS_APPLICATION(pServer, pHttpApplication, std::move(options), pParameters, nParameters));
|
||||
THROW_IF_FAILED(application->LoadManagedApplication());
|
||||
return S_OK;
|
||||
}
|
||||
catch (InvalidOperationException& ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
|
||||
pHttpApplication.GetApplicationId(),
|
||||
pHttpApplication.GetApplicationPhysicalPath(),
|
||||
ex.as_wstring().c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
|
||||
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
|
||||
pHttpApplication.GetApplicationId(),
|
||||
pHttpApplication.GetApplicationPhysicalPath(),
|
||||
GetUnexpectedExceptionMessage(ex).c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// Required because __try and objects with destructors can not be mixed
|
||||
void
|
||||
IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& context)
|
||||
{
|
||||
__try
|
||||
{
|
||||
auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get());
|
||||
|
||||
LOG_INFOF(L"Managed application exited with code %d", exitCode);
|
||||
|
||||
context->m_exitCode = exitCode;
|
||||
}
|
||||
__except(GetExceptionCode() != 0)
|
||||
{
|
||||
LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode());
|
||||
|
||||
context->m_exceptionCode = GetExceptionCode();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Calls hostfxr_main with the hostfxr and application as arguments.
|
||||
// This method should not access IN_PROCESS_APPLICATION instance as it may be already freed
|
||||
// in case of startup timeout
|
||||
//
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context)
|
||||
{
|
||||
LOG_INFO(L"Starting CLR thread");
|
||||
|
||||
// Keep aspnetcorev2_inprocess.dll loaded while this thread is running
|
||||
// this is required because thread might be abandoned
|
||||
HandleWrapper<ModuleHandleTraits> moduleHandle;
|
||||
ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle);
|
||||
|
||||
ExecuteClr(context);
|
||||
|
||||
FreeLibraryAndExitThread(moduleHandle.release(), 0);
|
||||
}
|
||||
|
||||
HRESULT
|
||||
IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess(
|
||||
VOID
|
||||
)
|
||||
IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
|
||||
{
|
||||
auto variables = m_pConfig->QueryEnvironmentVariables();
|
||||
auto inputTable = std::unique_ptr<ENVIRONMENT_VAR_HASH, ENVIRONMENT_VAR_HASH_DELETER>(new ENVIRONMENT_VAR_HASH());
|
||||
|
|
@ -397,128 +438,53 @@ IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess(
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
IN_PROCESS_APPLICATION::ExecuteApplication(
|
||||
VOID
|
||||
)
|
||||
{
|
||||
HRESULT hr;
|
||||
HMODULE hModule = nullptr;
|
||||
hostfxr_main_fn pProc;
|
||||
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions = NULL;
|
||||
DWORD hostfxrArgc = 0;
|
||||
std::unique_ptr<PCWSTR[]> hostfxrArgv;
|
||||
DBG_ASSERT(m_status == MANAGED_APPLICATION_STATUS::STARTING);
|
||||
|
||||
pProc = s_fMainCallback;
|
||||
if (pProc == nullptr)
|
||||
{
|
||||
// hostfxr should already be loaded by the shim. If not, then we will need
|
||||
// to load it ourselves by finding hostfxr again.
|
||||
hModule = LoadLibraryW(L"hostfxr.dll");
|
||||
|
||||
if (hModule == NULL)
|
||||
{
|
||||
// .NET Core not installed (we can log a more detailed error message here)
|
||||
hr = LOG_IF_FAILED(ERROR_BAD_ENVIRONMENT);
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
// Get the entry point for main
|
||||
pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main");
|
||||
if (pProc == NULL)
|
||||
{
|
||||
hr = LOG_IF_FAILED(ERROR_BAD_ENVIRONMENT);
|
||||
goto Finished;
|
||||
}
|
||||
|
||||
FINISHED_IF_FAILED(hr = HOSTFXR_OPTIONS::Create(
|
||||
m_struExeLocation.QueryStr(),
|
||||
m_pConfig->QueryProcessPath().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
m_pConfig->QueryArguments().c_str(),
|
||||
hostFxrOptions
|
||||
));
|
||||
hostFxrOptions->GetArguments(hostfxrArgc, hostfxrArgv);
|
||||
FINISHED_IF_FAILED(SetEnvironementVariablesOnWorkerProcess());
|
||||
}
|
||||
|
||||
LOG_INFO(L"Starting managed application");
|
||||
|
||||
if (m_pLoggerProvider == NULL)
|
||||
{
|
||||
FINISHED_IF_FAILED(hr = LoggingHelpers::CreateLoggingProvider(
|
||||
m_pConfig->QueryStdoutLogEnabled(),
|
||||
!m_pHttpServer.IsCommandLineLaunch(),
|
||||
m_pConfig->QueryStdoutLogFile().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
m_pLoggerProvider));
|
||||
|
||||
LOG_IF_FAILED(m_pLoggerProvider->Start());
|
||||
}
|
||||
|
||||
// There can only ever be a single instance of .NET Core
|
||||
// loaded in the process but we need to get config information to boot it up in the
|
||||
// first place. This is happening in an execute request handler and everyone waits
|
||||
// until this initialization is done.
|
||||
// We set a static so that managed code can call back into this instance and
|
||||
// set the callbacks
|
||||
s_Application = this;
|
||||
|
||||
hr = RunDotnetApplication(hostfxrArgc, hostfxrArgv.get(), pProc);
|
||||
|
||||
Finished:
|
||||
|
||||
//
|
||||
// this method is called by the background thread and should never exit unless shutdown
|
||||
// If main returned and shutdown was not called in managed, we want to block native from calling into
|
||||
// managed. To do this, we can say that shutdown was called from managed.
|
||||
// Don't bother locking here as there will always be a race between receiving a native shutdown
|
||||
// notification and unexpected managed exit.
|
||||
//
|
||||
m_status = MANAGED_APPLICATION_STATUS::SHUTDOWN;
|
||||
m_fShutdownCalledFromManaged = TRUE;
|
||||
|
||||
m_pLoggerProvider->Stop();
|
||||
|
||||
if (!m_fShutdownCalledFromNative)
|
||||
{
|
||||
LogErrorsOnMainExit(hr);
|
||||
if (m_fInitialized)
|
||||
{
|
||||
//
|
||||
// If the inprocess server was initialized, we need to cause recycle to be called on the worker process.
|
||||
//
|
||||
Stop(/*fServerInitiated*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
VOID
|
||||
IN_PROCESS_APPLICATION::LogErrorsOnMainExit(
|
||||
HRESULT hr
|
||||
)
|
||||
IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
|
||||
{
|
||||
STRA straStdErrOutput;
|
||||
STRU struStdMsg;
|
||||
auto hasStdOut = m_pLoggerProvider->GetStdOutContent(&straStdErrOutput) &&
|
||||
SUCCEEDED(struStdMsg.CopyA(straStdErrOutput.QueryStr()));
|
||||
|
||||
if (context.m_exceptionCode != 0)
|
||||
{
|
||||
if (hasStdOut)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
context.m_exceptionCode,
|
||||
struStdMsg.QueryStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
context.m_exceptionCode
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Ungraceful shutdown, try to log an error message.
|
||||
// This will be a common place for errors as it means the hostfxr_main returned
|
||||
// or there was an exception.
|
||||
//
|
||||
STRA straStdErrOutput;
|
||||
STRU struStdMsg;
|
||||
if (m_pLoggerProvider->GetStdOutContent(&straStdErrOutput))
|
||||
if (hasStdOut)
|
||||
{
|
||||
if (SUCCEEDED(struStdMsg.CopyA(straStdErrOutput.QueryStr()))) {
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
hr,
|
||||
struStdMsg.QueryStr());
|
||||
}
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT,
|
||||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
context.m_exitCode,
|
||||
struStdMsg.QueryStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -527,53 +493,20 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit(
|
|||
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
|
||||
QueryApplicationId().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
hr);
|
||||
context.m_exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Calls hostfxr_main with the hostfxr and application as arguments.
|
||||
//
|
||||
HRESULT
|
||||
IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hostfxr_main_fn pProc)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
__try
|
||||
{
|
||||
m_ProcessExitCode = pProc(argc, argv);
|
||||
if (m_ProcessExitCode != 0)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
LOG_INFOF(L"Managed application exited with code %d", m_ProcessExitCode);
|
||||
}
|
||||
__except(GetExceptionCode() != 0)
|
||||
{
|
||||
|
||||
LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode());
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
IN_PROCESS_APPLICATION::CreateHandler(
|
||||
_In_ IHttpContext *pHttpContext,
|
||||
_Out_ IREQUEST_HANDLER **pRequestHandler)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
IREQUEST_HANDLER* pHandler = NULL;
|
||||
|
||||
pHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
|
||||
|
||||
if (pHandler == NULL)
|
||||
try
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
|
||||
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
|
||||
}
|
||||
|
||||
*pRequestHandler = pHandler;
|
||||
return hr;
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include "InProcessApplicationBase.h"
|
||||
#include "IOutputManager.h"
|
||||
#include "InProcessOptions.h"
|
||||
|
|
@ -45,35 +46,26 @@ public:
|
|||
override;
|
||||
|
||||
// Executes the .NET Core process
|
||||
void
|
||||
ExecuteApplication();
|
||||
|
||||
HRESULT
|
||||
ExecuteApplication(
|
||||
VOID
|
||||
);
|
||||
LoadManagedApplication();
|
||||
|
||||
HRESULT
|
||||
LoadManagedApplication(
|
||||
VOID
|
||||
);
|
||||
|
||||
VOID
|
||||
LogErrorsOnMainExit(
|
||||
HRESULT hr
|
||||
);
|
||||
|
||||
VOID
|
||||
StopCallsIntoManaged(
|
||||
VOID
|
||||
)
|
||||
void
|
||||
QueueStop();
|
||||
|
||||
void
|
||||
StopIncomingRequests()
|
||||
{
|
||||
m_fBlockCallbacksIntoManaged = TRUE;
|
||||
QueueStop();
|
||||
}
|
||||
|
||||
VOID
|
||||
StopIncomingRequests(
|
||||
VOID
|
||||
)
|
||||
void
|
||||
StopCallsIntoManaged()
|
||||
{
|
||||
m_fShutdownCalledFromManaged = TRUE;
|
||||
m_blockManagedCallbacks = true;
|
||||
}
|
||||
|
||||
static
|
||||
|
|
@ -84,44 +76,64 @@ public:
|
|||
|
||||
static
|
||||
IN_PROCESS_APPLICATION*
|
||||
GetInstance(
|
||||
VOID
|
||||
)
|
||||
GetInstance()
|
||||
{
|
||||
return s_Application;
|
||||
}
|
||||
|
||||
PCWSTR
|
||||
QueryExeLocation()
|
||||
const std::wstring&
|
||||
QueryExeLocation() const
|
||||
{
|
||||
return m_struExeLocation.QueryStr();
|
||||
return m_dotnetExeKnownLocation;
|
||||
}
|
||||
|
||||
const InProcessOptions&
|
||||
QueryConfig() const
|
||||
{
|
||||
return *m_pConfig.get();
|
||||
return *m_pConfig;
|
||||
}
|
||||
|
||||
bool
|
||||
QueryBlockCallbacksIntoManaged() const
|
||||
{
|
||||
return m_fBlockCallbacksIntoManaged;
|
||||
return m_blockManagedCallbacks;
|
||||
}
|
||||
|
||||
static
|
||||
HRESULT Start(
|
||||
IHttpServer& pServer,
|
||||
IHttpApplication& pHttpApplication,
|
||||
APPLICATION_PARAMETER* pParameters,
|
||||
DWORD nParameters,
|
||||
std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER>& application);
|
||||
|
||||
private:
|
||||
|
||||
enum MANAGED_APPLICATION_STATUS
|
||||
struct ExecuteClrContext: std::enable_shared_from_this<ExecuteClrContext>
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
STARTING,
|
||||
RUNNING_MANAGED,
|
||||
SHUTDOWN,
|
||||
FAIL
|
||||
};
|
||||
ExecuteClrContext():
|
||||
m_argc(0),
|
||||
m_pProc(nullptr),
|
||||
m_exitCode(0),
|
||||
m_exceptionCode(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Thread executing the .NET Core process
|
||||
HandleWrapper<InvalidHandleTraits> m_hThread;
|
||||
DWORD m_argc;
|
||||
std::unique_ptr<PCWSTR[]> m_argv;
|
||||
hostfxr_main_fn m_pProc;
|
||||
|
||||
int m_exitCode;
|
||||
int m_exceptionCode;
|
||||
};
|
||||
|
||||
// Thread executing the .NET Core process this might be abandoned in timeout cases
|
||||
std::thread m_clrThread;
|
||||
// Thread tracking the CLR thread, this one is always joined on shutdown
|
||||
std::thread m_workerThread;
|
||||
// The event that gets triggered when managed initialization is complete
|
||||
HandleWrapper<NullHandleTraits> m_pInitializeEvent;
|
||||
// The event that gets triggered when worker thread should exit
|
||||
HandleWrapper<NullHandleTraits> m_pShutdownEvent;
|
||||
|
||||
// The request handler callback from managed code
|
||||
PFN_REQUEST_HANDLER m_RequestHandler;
|
||||
|
|
@ -133,53 +145,38 @@ private:
|
|||
|
||||
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
|
||||
|
||||
// The event that gets triggered when managed initialization is complete
|
||||
HandleWrapper<InvalidHandleTraits> m_pInitalizeEvent;
|
||||
std::wstring m_dotnetExeKnownLocation;
|
||||
|
||||
STRU m_struExeLocation;
|
||||
std::atomic_bool m_blockManagedCallbacks;
|
||||
bool m_Initialized;
|
||||
bool m_waitForShutdown;
|
||||
|
||||
// The exit code of the .NET Core process
|
||||
INT m_ProcessExitCode;
|
||||
|
||||
volatile BOOL m_fBlockCallbacksIntoManaged;
|
||||
volatile BOOL m_fShutdownCalledFromNative;
|
||||
volatile BOOL m_fShutdownCalledFromManaged;
|
||||
BOOL m_fInitialized;
|
||||
MANAGED_APPLICATION_STATUS m_status;
|
||||
std::unique_ptr<InProcessOptions> m_pConfig;
|
||||
|
||||
static IN_PROCESS_APPLICATION* s_Application;
|
||||
|
||||
std::unique_ptr<IOutputManager> m_pLoggerProvider;
|
||||
|
||||
static const LPCSTR s_exeLocationParameterName;
|
||||
|
||||
static
|
||||
VOID
|
||||
ExecuteAspNetCoreProcess(
|
||||
_In_ LPVOID pContext
|
||||
);
|
||||
|
||||
HRESULT
|
||||
SetEnvironementVariablesOnWorkerProcess(
|
||||
VOID
|
||||
);
|
||||
|
||||
HRESULT
|
||||
RunDotnetApplication(
|
||||
DWORD argc,
|
||||
CONST PCWSTR* argv,
|
||||
hostfxr_main_fn pProc
|
||||
);
|
||||
|
||||
static
|
||||
DWORD WINAPI
|
||||
DoShutDown(
|
||||
LPVOID lpParam
|
||||
);
|
||||
inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation";
|
||||
|
||||
VOID
|
||||
ShutDownInternal(
|
||||
VOID
|
||||
);
|
||||
UnexpectedThreadExit(const ExecuteClrContext& context) const;
|
||||
|
||||
HRESULT
|
||||
SetEnvironmentVariablesOnWorkerProcess();
|
||||
|
||||
void
|
||||
StopClr();
|
||||
|
||||
static
|
||||
void
|
||||
ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context);
|
||||
|
||||
static
|
||||
void
|
||||
ExecuteClr(const std::shared_ptr<ExecuteClrContext> &context);
|
||||
|
||||
// Allows to override call to hostfxr_main with custom callback
|
||||
// used in testing
|
||||
inline static hostfxr_main_fn s_fMainCallback = nullptr;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,9 +40,7 @@ public:
|
|||
) override;
|
||||
|
||||
IHttpContext*
|
||||
QueryHttpContext(
|
||||
VOID
|
||||
) const
|
||||
QueryHttpContext() const
|
||||
{
|
||||
return m_pW3Context;
|
||||
}
|
||||
|
|
@ -53,9 +51,7 @@ public:
|
|||
);
|
||||
|
||||
VOID
|
||||
IndicateManagedRequestComplete(
|
||||
VOID
|
||||
);
|
||||
IndicateManagedRequestComplete();
|
||||
|
||||
VOID
|
||||
SetAsyncCompletionStatus(
|
||||
|
|
@ -68,11 +64,11 @@ public:
|
|||
|
||||
static
|
||||
HRESULT
|
||||
StaticInitialize(VOID);
|
||||
StaticInitialize();
|
||||
|
||||
static
|
||||
void
|
||||
StaticTerminate(VOID);
|
||||
StaticTerminate();
|
||||
|
||||
private:
|
||||
REQUEST_NOTIFICATION_STATUS
|
||||
|
|
|
|||
|
|
@ -60,5 +60,25 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
|||
deploymentParameters.WebConfigActionList.Add(
|
||||
WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine(path, "std")));
|
||||
}
|
||||
|
||||
public static void TransformPath(this IISDeploymentParameters parameters, Func<string, string, string> transformation)
|
||||
{
|
||||
parameters.WebConfigActionList.Add(
|
||||
(config, contentRoot) =>
|
||||
{
|
||||
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
|
||||
aspNetCoreElement.SetAttributeValue("processPath", transformation((string)aspNetCoreElement.Attribute("processPath"), contentRoot));
|
||||
});
|
||||
}
|
||||
|
||||
public static void TransformArguments(this IISDeploymentParameters parameters, Func<string, string, string> transformation)
|
||||
{
|
||||
parameters.WebConfigActionList.Add(
|
||||
(config, contentRoot) =>
|
||||
{
|
||||
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
|
||||
aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"iisexpress Process {hostProcess.Id} crashed before shutdown was triggered.");
|
||||
throw new InvalidOperationException($"iisexpress Process {hostProcess?.Id} crashed before shutdown was triggered.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
await deploymentResult.HttpClient.RetryRequestAsync("/HelloWorld", r => r.StatusCode == HttpStatusCode.InternalServerError);
|
||||
|
||||
StopServer();
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Could not find the assembly 'aspnetcorev2_inprocess.dll'");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not find the assembly 'aspnetcorev2_inprocess.dll'");
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Application '.+' started the coreclr in-process successfully.");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' started the coreclr in-process successfully.");
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Application '.+' has shutdown.");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' has shutdown.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -23,19 +24,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
[ConditionalTheory]
|
||||
[InlineData("CheckLogFile")]
|
||||
[InlineData("CheckErrLogFile")]
|
||||
public async Task CheckStdoutWithRandomNumber(string path)
|
||||
public async Task CheckStdoutWithRandomNumber(string mode)
|
||||
{
|
||||
// Forcing publish for now to have parity between IIS and IISExpress
|
||||
// Reason is because by default for IISExpress, we expect there to not be a web.config file.
|
||||
// However, for IIS, we need a web.config file because the default on generated on publish
|
||||
// doesn't include V2. We can remove the publish flag once IIS supports non-publish running
|
||||
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
|
||||
|
||||
var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString();
|
||||
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
|
||||
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString;
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} {mode} {randomNumberString}");
|
||||
|
||||
await AssertFailsToStart(path, deploymentParameters);
|
||||
await AssertFailsToStart(deploymentParameters);
|
||||
|
||||
Assert.Contains(TestSink.Writes, context => context.Message.Contains($"Random number: {randomNumberString}"));
|
||||
}
|
||||
|
|
@ -45,12 +41,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
[InlineData("CheckLargeStdOutWrites")]
|
||||
[InlineData("CheckOversizedStdErrWrites")]
|
||||
[InlineData("CheckOversizedStdOutWrites")]
|
||||
public async Task CheckStdoutWithLargeWrites(string path)
|
||||
public async Task CheckStdoutWithLargeWrites(string mode)
|
||||
{
|
||||
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
|
||||
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} {mode}");
|
||||
|
||||
await AssertFailsToStart(path, deploymentParameters);
|
||||
await AssertFailsToStart(deploymentParameters);
|
||||
|
||||
Assert.Contains(TestSink.Writes, context => context.Message.Contains(new string('a', 4096)));
|
||||
}
|
||||
|
|
@ -58,20 +54,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
[ConditionalFact]
|
||||
public async Task CheckValidConsoleFunctions()
|
||||
{
|
||||
var path = "CheckConsoleFunctions";
|
||||
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
|
||||
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} CheckConsoleFunctions");
|
||||
|
||||
await AssertFailsToStart(path, deploymentParameters);
|
||||
await AssertFailsToStart(deploymentParameters);
|
||||
|
||||
Assert.Contains(TestSink.Writes, context => context.Message.Contains("Is Console redirection: True"));
|
||||
}
|
||||
|
||||
private async Task AssertFailsToStart(string path, IntegrationTesting.IIS.IISDeploymentParameters deploymentParameters)
|
||||
private async Task AssertFailsToStart(IntegrationTesting.IIS.IISDeploymentParameters deploymentParameters)
|
||||
{
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
var response = await deploymentResult.HttpClient.GetAsync(path);
|
||||
var response = await deploymentResult.HttpClient.GetAsync("/");
|
||||
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
|
||||
|
|
@ -92,5 +87,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("500.30 - ANCM In-Process Start Failure", responseText);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
Assert.Equal(200, (int)result1.StatusCode);
|
||||
Assert.Equal(500, (int)result2.StatusCode);
|
||||
StopServer();
|
||||
EventLogHelpers.VerifyEventLogEvent(result, TestSink, "Only one inprocess application is allowed per IIS application pool");
|
||||
EventLogHelpers.VerifyEventLogEvent(result, "Only one inprocess application is allowed per IIS application pool");
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
|
|
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
Assert.Equal(200, (int)result1.StatusCode);
|
||||
Assert.Equal(500, (int)result2.StatusCode);
|
||||
StopServer();
|
||||
EventLogHelpers.VerifyEventLogEvent(result, TestSink, "Mixed hosting model is not supported.");
|
||||
EventLogHelpers.VerifyEventLogEvent(result, "Mixed hosting model is not supported.");
|
||||
}
|
||||
|
||||
private void SetHostingModel(string directory, HostingModel model)
|
||||
|
|
|
|||
|
|
@ -1,54 +1,64 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
public class EventLogHelpers
|
||||
{
|
||||
private static readonly Regex EventLogRegex = new Regex("Event Log: (?<EventLogMessage>.+?)End Event Log Message.", RegexOptions.Singleline | RegexOptions.Compiled);
|
||||
|
||||
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, ITestSink testSink, string expectedRegexMatchString)
|
||||
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString)
|
||||
{
|
||||
Assert.True(deploymentResult.HostProcess.HasExited);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var entries = GetEntries(deploymentResult);
|
||||
AssertSingleEntry(expectedRegexMatchString, entries);
|
||||
}
|
||||
|
||||
public static void VerifyEventLogEvents(IISDeploymentResult deploymentResult, params string[] expectedRegexMatchString)
|
||||
{
|
||||
Assert.True(deploymentResult.HostProcess.HasExited);
|
||||
|
||||
foreach (var context in testSink.Writes)
|
||||
var entries = GetEntries(deploymentResult).ToList();
|
||||
foreach (var regexString in expectedRegexMatchString)
|
||||
{
|
||||
builder.Append(context.Message);
|
||||
}
|
||||
var matchedEntries = AssertSingleEntry(regexString, entries);
|
||||
|
||||
var count = 0;
|
||||
var expectedRegex = new Regex(expectedRegexMatchString, RegexOptions.Singleline);
|
||||
foreach (Match match in EventLogRegex.Matches(builder.ToString()))
|
||||
{
|
||||
var eventLogText = match.Groups["EventLogMessage"].Value;
|
||||
if (expectedRegex.IsMatch(eventLogText))
|
||||
foreach (var matchedEntry in matchedEntries)
|
||||
{
|
||||
count++;
|
||||
entries.Remove(matchedEntry);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(0 == entries.Count, $"Some entries were not matched by any regex {FormatEntries(entries)}");
|
||||
}
|
||||
|
||||
Assert.True(count > 0, $"'{expectedRegexMatchString}' didn't match any event log messaged");
|
||||
Assert.True(count < 2, $"'{expectedRegexMatchString}' matched more then one event log message");
|
||||
private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable<EventLogEntry> entries)
|
||||
{
|
||||
var expectedRegex = new Regex(regexString, RegexOptions.Singleline);
|
||||
var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray();
|
||||
Assert.True(matchedEntries.Length > 0, $"No entries matched by '{regexString}'");
|
||||
Assert.True(matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
|
||||
return matchedEntries;
|
||||
}
|
||||
|
||||
private static string FormatEntries(IEnumerable<EventLogEntry> entries)
|
||||
{
|
||||
return string.Join(",", entries.Select(e => e.Message));
|
||||
}
|
||||
|
||||
private static IEnumerable<EventLogEntry> GetEntries(IISDeploymentResult deploymentResult)
|
||||
{
|
||||
var eventLog = new EventLog("Application");
|
||||
|
||||
// Eventlog is already sorted based on time of event in ascending time.
|
||||
// Check results in reverse order.
|
||||
var expectedRegexEventLog = new Regex(expectedRegexMatchString);
|
||||
var processIdString = $"Process Id: {deploymentResult.HostProcess.Id}.";
|
||||
|
||||
// Event log messages round down to the nearest second, so subtract a second
|
||||
|
|
@ -67,18 +77,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// ReplacementStings == EventData collection in EventLog
|
||||
// This is unaffected if event providers are not registered correctly
|
||||
if (eventLogEntry.Source == AncmVersionToMatch(deploymentResult) &&
|
||||
processIdString == eventLogEntry.ReplacementStrings[1] &&
|
||||
expectedRegex.IsMatch(eventLogEntry.ReplacementStrings[0]))
|
||||
processIdString == eventLogEntry.ReplacementStrings[1])
|
||||
{
|
||||
return;
|
||||
yield return eventLogEntry;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(false, $"'{expectedRegexMatchString}' didn't match any event log messaged.");
|
||||
}
|
||||
|
||||
private static string AncmVersionToMatch(IISDeploymentResult deploymentResult)
|
||||
|
|
@ -88,5 +95,51 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
"AspNetCore Module" +
|
||||
(deploymentResult.DeploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2 ? " V2" : "");
|
||||
}
|
||||
|
||||
|
||||
public static string InProcessStarted(IISDeploymentResult deploymentResult)
|
||||
{
|
||||
return $"Application '{EscapedContentRoot(deploymentResult)}' started the coreclr in-process successfully";
|
||||
}
|
||||
|
||||
public static string InProcessFailedToStart(IISDeploymentResult deploymentResult, string reason)
|
||||
{
|
||||
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to load clr and managed application. {reason}";
|
||||
}
|
||||
|
||||
public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason)
|
||||
{
|
||||
return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/HTTPTESTSITE'.";
|
||||
}
|
||||
|
||||
public static string InProcessThreadException(IISDeploymentResult deploymentResult, string reason)
|
||||
{
|
||||
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed exception{reason}";
|
||||
}
|
||||
|
||||
public static string InProcessThreadExit(IISDeploymentResult deploymentResult, string code)
|
||||
{
|
||||
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed background thread exit, exit code = '{code}'.";
|
||||
}
|
||||
|
||||
public static string FailedToStartApplication(IISDeploymentResult deploymentResult, string code)
|
||||
{
|
||||
return $"Failed to start application '/LM/W3SVC/1/ROOT', ErrorCode '{code}'.";
|
||||
}
|
||||
|
||||
public static string ConfigurationLoadError(IISDeploymentResult deploymentResult, string reason)
|
||||
{
|
||||
return $"Configuration load error. {reason}";
|
||||
}
|
||||
|
||||
private static string EscapedContentRoot(IISDeploymentResult deploymentResult)
|
||||
{
|
||||
var contentRoot = deploymentResult.ContentRoot;
|
||||
if (!contentRoot.EndsWith('\\'))
|
||||
{
|
||||
contentRoot += '\\';
|
||||
}
|
||||
return Regex.Escape(contentRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\;</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
<Lib>
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\;</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
<Lib>
|
||||
|
|
@ -156,7 +156,7 @@
|
|||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\;</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
<Lib>
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<AdditionalLibraryDirectories>..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\;</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
<Lib>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
|
|||
struHostFxrDllLocation,
|
||||
struExeLocation,
|
||||
bstrArray), // args array.
|
||||
HOSTFXR_UTILITY::StartupParametersResolutionException);
|
||||
InvalidOperationException);
|
||||
}
|
||||
|
||||
TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
|
||||
|
|
@ -118,5 +118,5 @@ TEST(GetHostFxrArguments, InvalidParams)
|
|||
struHostFxrDllLocation,
|
||||
struExeLocation,
|
||||
bstrArray), // args array.
|
||||
HOSTFXR_UTILITY::StartupParametersResolutionException);
|
||||
InvalidOperationException);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace InprocessTests
|
|||
|
||||
IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), parameters.data(), 1);
|
||||
|
||||
ASSERT_STREQ(app->QueryExeLocation(), L"hello");
|
||||
ASSERT_STREQ(app->QueryExeLocation().c_str(), L"hello");
|
||||
}
|
||||
|
||||
TEST(InProcessTest, GeneratesVirtualPath)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink,
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult,
|
||||
"The specified framework 'Microsoft.NETCore.App', version '2.9.9' was not found.");
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
|
||||
var contents = File.ReadAllText(Helpers.GetExpectedLogName(deploymentResult, _logFolderPath));
|
||||
var expectedString = "The specified framework 'Microsoft.NETCore.App', version '2.9.9' was not found.";
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, expectedString);
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, expectedString);
|
||||
Assert.Contains(expectedString, contents);
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
|
||||
var fileInDirectory = Directory.GetFiles(_logFolderPath).Single();
|
||||
var contents = File.ReadAllText(fileInDirectory);
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Invoked hostfxr");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr");
|
||||
Assert.Contains("Invoked hostfxr", contents);
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Invoked hostfxr");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr");
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
|
|
@ -153,7 +153,7 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
var fileInDirectory = Directory.GetFiles(_logFolderPath).First();
|
||||
var contents = File.ReadAllText(fileInDirectory);
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Invoked hostfxr");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr");
|
||||
Assert.Contains("Invoked hostfxr", contents);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
|
|
@ -26,16 +27,42 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
public async Task ServerShutsDownWhenMainExits()
|
||||
{
|
||||
var parameters = _fixture.GetBaseDeploymentParameters(publish: true);
|
||||
var result = await DeployAsync(parameters);
|
||||
var deploymentResult = await DeployAsync(parameters);
|
||||
try
|
||||
{
|
||||
await result.HttpClient.GetAsync("/Shutdown");
|
||||
await deploymentResult.HttpClient.GetAsync("/Shutdown");
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.InnerException is IOException)
|
||||
{
|
||||
// Server might close a connection before request completes
|
||||
}
|
||||
Assert.True(result.HostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout));
|
||||
|
||||
deploymentResult.AssertWorkerProcessStop();
|
||||
}
|
||||
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ServerShutsDownWhenMainExitsStress()
|
||||
{
|
||||
var parameters = _fixture.GetBaseDeploymentParameters(publish: true);
|
||||
var deploymentResult = await StartAsync(parameters);
|
||||
|
||||
var load = Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => {
|
||||
var statusCode = (int)response.StatusCode;
|
||||
Assert.True(statusCode == 200 || statusCode == 503, "Status code was " + statusCode);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await deploymentResult.HttpClient.GetAsync("/Shutdown");
|
||||
await load;
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.InnerException is IOException | ex.InnerException is SocketException)
|
||||
{
|
||||
// Server might close a connection before request completes
|
||||
}
|
||||
|
||||
deploymentResult.AssertWorkerProcessStop();
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}");
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
|
|
@ -137,11 +137,67 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
{
|
||||
var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(_fixture.OverriddenServerWebSite, publish: true));
|
||||
var response = await deploymentResult.HttpClient.GetAsync("/");
|
||||
Assert.False(response.IsSuccessStatusCode);
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
|
||||
EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"),
|
||||
EventLogHelpers.InProcessThreadException(deploymentResult, ".*?Application is running inside IIS process but is not configured to use IIS server"));
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task LogsUnexpectedThreadExitError()
|
||||
{
|
||||
var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true));
|
||||
|
||||
var response = await deploymentResult.HttpClient.GetAsync("/");
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
|
||||
StopServer();
|
||||
|
||||
Assert.Contains(TestSink.Writes, context => context.Message.Contains("Application is running inside IIS process but is not configured to use IIS server"));
|
||||
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
|
||||
EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"),
|
||||
EventLogHelpers.InProcessThreadExit(deploymentResult, "12"));
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task StartupTimeoutIsApplied()
|
||||
{
|
||||
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} Hang");
|
||||
deploymentParameters.WebConfigActionList.Add(
|
||||
WebConfigHelpers.AddOrModifyAspNetCoreSection("startupTimeLimit", "1"));
|
||||
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
var response = await deploymentResult.HttpClient.GetAsync("/");
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
|
||||
EventLogHelpers.InProcessFailedToStart(deploymentResult, "Managed server didn't initialize after 1000 ms.")
|
||||
);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ShutdownTimeoutIsApplied()
|
||||
{
|
||||
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.StartupExceptionWebsite, publish: true);
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} HangOnStop");
|
||||
deploymentParameters.WebConfigActionList.Add(
|
||||
WebConfigHelpers.AddOrModifyAspNetCoreSection("shutdownTimeLimit", "1"));
|
||||
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
|
||||
Assert.Equal("OK", await deploymentResult.HttpClient.GetStringAsync("/"));
|
||||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
|
||||
EventLogHelpers.InProcessStarted(deploymentResult),
|
||||
EventLogHelpers.InProcessFailedToStop(deploymentResult, ""));
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
|
|
@ -158,10 +214,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
StopServer();
|
||||
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file.");
|
||||
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
|
||||
EventLogHelpers.FailedToStartApplication(deploymentResult, "0x80004005"),
|
||||
EventLogHelpers.ConfigurationLoadError(deploymentResult, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file.")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<string, (string, Action<XElement>)> InvalidConfigTransformations = InitInvalidConfigTransformations();
|
||||
public static IEnumerable<object[]> InvalidConfigTransformationsScenarios => InvalidConfigTransformations.ToTheoryData();
|
||||
|
||||
|
|
@ -177,7 +235,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode);
|
||||
|
||||
StopServer();
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Configuration load error. " + expectedError);
|
||||
EventLogHelpers.VerifyEventLogEvents(deploymentResult,
|
||||
EventLogHelpers.FailedToStartApplication(deploymentResult, "0x80004005"),
|
||||
EventLogHelpers.ConfigurationLoadError(deploymentResult, expectedError)
|
||||
);
|
||||
}
|
||||
|
||||
public static Dictionary<string, (string, Action<XElement>)> InitInvalidConfigTransformations()
|
||||
|
|
@ -223,42 +284,42 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
dictionary.Add("App in bin subdirectory full path to dll using exec and quotes",
|
||||
parameters => {
|
||||
MoveApplication(parameters, "bin");
|
||||
TransformArguments(parameters, (arguments, root) => "exec " + Path.Combine(root, "bin", arguments));
|
||||
parameters.TransformArguments((arguments, root) => "exec " + Path.Combine(root, "bin", arguments));
|
||||
return "";
|
||||
});
|
||||
|
||||
dictionary.Add("App in subdirectory with space",
|
||||
parameters => {
|
||||
MoveApplication(parameters, pathWithSpace);
|
||||
TransformArguments(parameters, (arguments, root) => Path.Combine(pathWithSpace, arguments));
|
||||
parameters.TransformArguments((arguments, root) => Path.Combine(pathWithSpace, arguments));
|
||||
return "";
|
||||
});
|
||||
|
||||
dictionary.Add("App in subdirectory with space and full path to dll",
|
||||
parameters => {
|
||||
MoveApplication(parameters, pathWithSpace);
|
||||
TransformArguments(parameters, (arguments, root) => Path.Combine(root, pathWithSpace, arguments));
|
||||
parameters.TransformArguments((arguments, root) => Path.Combine(root, pathWithSpace, arguments));
|
||||
return "";
|
||||
});
|
||||
|
||||
dictionary.Add("App in bin subdirectory with space full path to dll using exec and quotes",
|
||||
parameters => {
|
||||
MoveApplication(parameters, pathWithSpace);
|
||||
TransformArguments(parameters, (arguments, root) => "exec \"" + Path.Combine(root, pathWithSpace, arguments) + "\" extra arguments");
|
||||
parameters.TransformArguments((arguments, root) => "exec \"" + Path.Combine(root, pathWithSpace, arguments) + "\" extra arguments");
|
||||
return "extra|arguments";
|
||||
});
|
||||
|
||||
dictionary.Add("App in bin subdirectory and quoted argument",
|
||||
parameters => {
|
||||
MoveApplication(parameters, "bin");
|
||||
TransformArguments(parameters, (arguments, root) => Path.Combine("bin", arguments) + " \"extra argument\"");
|
||||
parameters.TransformArguments((arguments, root) => Path.Combine("bin", arguments) + " \"extra argument\"");
|
||||
return "extra argument";
|
||||
});
|
||||
|
||||
dictionary.Add("App in bin subdirectory full path to dll",
|
||||
parameters => {
|
||||
MoveApplication(parameters, "bin");
|
||||
TransformArguments(parameters, (arguments, root) => Path.Combine(root, "bin", arguments) + " extra arguments");
|
||||
parameters.TransformArguments((arguments, root) => Path.Combine(root, "bin", arguments) + " extra arguments");
|
||||
return "extra|arguments";
|
||||
});
|
||||
return dictionary;
|
||||
|
|
@ -288,16 +349,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
dictionary.Add("App in subdirectory",
|
||||
parameters => {
|
||||
MoveApplication(parameters, pathWithSpace);
|
||||
TransformPath(parameters, (path, root) => Path.Combine(pathWithSpace, path));
|
||||
TransformArguments(parameters, (arguments, root) => "\"additional argument\"");
|
||||
parameters.TransformPath((path, root) => Path.Combine(pathWithSpace, path));
|
||||
parameters.TransformArguments((arguments, root) => "\"additional argument\"");
|
||||
return "additional argument";
|
||||
});
|
||||
|
||||
dictionary.Add("App in bin subdirectory full path",
|
||||
parameters => {
|
||||
MoveApplication(parameters, pathWithSpace);
|
||||
TransformPath(parameters, (path, root) => Path.Combine(root, pathWithSpace, path));
|
||||
TransformArguments(parameters, (arguments, root) => "additional arguments");
|
||||
parameters.TransformPath((path, root) => Path.Combine(root, pathWithSpace, path));
|
||||
parameters.TransformArguments((arguments, root) => "additional arguments");
|
||||
return "additional|arguments";
|
||||
});
|
||||
|
||||
|
|
@ -322,26 +383,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void TransformPath(IISDeploymentParameters parameters, Func<string, string, string> transformation)
|
||||
{
|
||||
parameters.WebConfigActionList.Add(
|
||||
(config, contentRoot) =>
|
||||
{
|
||||
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
|
||||
aspNetCoreElement.SetAttributeValue("processPath", transformation((string)aspNetCoreElement.Attribute("processPath"), contentRoot));
|
||||
});
|
||||
}
|
||||
|
||||
private static void TransformArguments(IISDeploymentParameters parameters, Func<string, string, string> transformation)
|
||||
{
|
||||
parameters.WebConfigActionList.Add(
|
||||
(config, contentRoot) =>
|
||||
{
|
||||
var aspNetCoreElement = config.Descendants("aspNetCore").Single();
|
||||
aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,54 +2,75 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace IISTestSite
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var envVariable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_STARTUP_VALUE");
|
||||
var randomNumber = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_RANDOM_VALUE");
|
||||
var mode = args.FirstOrDefault();
|
||||
|
||||
// Semicolons are appended to env variables; removing them.
|
||||
if (envVariable == "CheckLargeStdOutWrites")
|
||||
switch (mode)
|
||||
{
|
||||
Console.WriteLine(new string('a', 4096));
|
||||
}
|
||||
else if (envVariable == "CheckLargeStdErrWrites")
|
||||
{
|
||||
Console.Error.WriteLine(new string('a', 4096));
|
||||
Console.Error.Flush();
|
||||
}
|
||||
else if (envVariable == "CheckLogFile")
|
||||
{
|
||||
Console.WriteLine($"Random number: {randomNumber}");
|
||||
}
|
||||
else if (envVariable == "CheckErrLogFile")
|
||||
{
|
||||
Console.Error.WriteLine($"Random number: {randomNumber}");
|
||||
Console.Error.Flush();
|
||||
}
|
||||
else if (envVariable == "CheckOversizedStdErrWrites")
|
||||
{
|
||||
Console.WriteLine(new string('a', 5000));
|
||||
// Semicolons are appended to env variables; removing them.
|
||||
case "CheckLargeStdOutWrites":
|
||||
Console.WriteLine(new string('a', 4096));
|
||||
break;
|
||||
case "CheckLargeStdErrWrites":
|
||||
Console.Error.WriteLine(new string('a', 4096));
|
||||
Console.Error.Flush();
|
||||
break;
|
||||
case "CheckLogFile":
|
||||
Console.WriteLine($"Random number: {args[1]}");
|
||||
break;
|
||||
case "CheckErrLogFile":
|
||||
Console.Error.WriteLine($"Random number: {args[1]}");
|
||||
Console.Error.Flush();
|
||||
break;
|
||||
case "CheckOversizedStdErrWrites":
|
||||
Console.WriteLine(new string('a', 5000));
|
||||
break;
|
||||
case "CheckOversizedStdOutWrites":
|
||||
Console.Error.WriteLine(new string('a', 4096));
|
||||
Console.Error.Flush();
|
||||
break;
|
||||
case "Hang":
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
break;
|
||||
case "HangOnStop":
|
||||
|
||||
var host = new WebHostBuilder()
|
||||
.UseIIS()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
host.Run();
|
||||
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
break;
|
||||
case "CheckConsoleFunctions":
|
||||
// Call a bunch of console functions and make sure none return invalid handle.
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.Title = "Test";
|
||||
Console.WriteLine($"Is Console redirection: {Console.IsOutputRedirected}");
|
||||
Console.BackgroundColor = ConsoleColor.Blue;
|
||||
break;
|
||||
}
|
||||
else if (envVariable == "CheckOversizedStdOutWrites")
|
||||
|
||||
return 12;
|
||||
}
|
||||
|
||||
public partial class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
Console.Error.WriteLine(new string('a', 4096));
|
||||
Console.Error.Flush();
|
||||
}
|
||||
else if (envVariable == "CheckConsoleFunctions")
|
||||
{
|
||||
// Call a bunch of console functions and make sure none return invalid handle.
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.Title = "Test";
|
||||
Console.WriteLine($"Is Console redirection: {Console.IsOutputRedirected}");
|
||||
Console.BackgroundColor = ConsoleColor.Blue;
|
||||
app.Run(async context => await context.Response.WriteAsync("OK"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue