Use hostfxr error callback support (#6043)

This commit is contained in:
Pavel Krymets 2019-01-10 14:07:47 -08:00 committed by GitHub
parent 90511e6039
commit 5299eff616
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1106 additions and 991 deletions

View File

@ -6,17 +6,15 @@
#include "SRWExclusiveLock.h"
#include "applicationinfo.h"
#include "EventLog.h"
#include "hostfxr_utility.h"
#include "GlobalVersionUtility.h"
#include "HandleWrapper.h"
#include "file_utility.h"
#include "LoggingHelpers.h"
#include "resources.h"
#include "ConfigurationLoadException.h"
#include "WebConfigConfigurationSource.h"
#include "ModuleHelpers.h"
#include "BaseOutputManager.h"
#include "Environment.h"
#include "HostFxr.h"
#include "RedirectionOutput.h"
const PCWSTR HandlerResolver::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll";
const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll";
@ -55,10 +53,9 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
{
if (pConfiguration.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS)
{
std::unique_ptr<HOSTFXR_OPTIONS> options;
std::unique_ptr<BaseOutputManager> outputManager;
std::unique_ptr<HostFxrResolutionResult> options;
RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create(
RETURN_IF_FAILED(HostFxrResolutionResult::Create(
L"",
pConfiguration.QueryProcessPath(),
pApplication.GetApplicationPhysicalPath(),
@ -67,19 +64,13 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
location = options->GetDotnetExeLocation();
RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
pConfiguration.QueryStdoutLogEnabled(),
!m_pServer.IsCommandLineLaunch(),
pConfiguration.QueryStdoutLogFile().c_str(),
pApplication.GetApplicationPhysicalPath(),
outputManager));
auto redirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
hr = FindNativeAssemblyFromHostfxr(*options.get(), pstrHandlerDllName, handlerDllPath, outputManager.get());
hr = FindNativeAssemblyFromHostfxr(*options, pstrHandlerDllName, handlerDllPath, pApplication, pConfiguration, redirectionOutput);
if (FAILED_LOG(hr))
{
auto output = outputManager->GetStdOutContent();
auto output = redirectionOutput->GetOutput();
EventLog::Error(
ASPNETCORE_EVENT_GENERAL_ERROR,
@ -208,83 +199,76 @@ HandlerResolver::FindNativeAssemblyFromGlobalLocation(
//
HRESULT
HandlerResolver::FindNativeAssemblyFromHostfxr(
const HOSTFXR_OPTIONS& hostfxrOptions,
const HostFxrResolutionResult& hostfxrOptions,
PCWSTR libraryName,
std::wstring& handlerDllPath,
BaseOutputManager* outputManager
const IHttpApplication &pApplication,
const ShimOptions& pConfiguration,
std::shared_ptr<RedirectionOutput> stringRedirectionOutput
)
try
{
std::wstring struNativeSearchPaths;
size_t intIndex = 0;
size_t intPrevIndex = 0;
DWORD dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize;
DWORD dwRequiredBufferSize = 0;
hostfxr_get_native_search_directories_fn pFnHostFxrSearchDirectories = nullptr;
RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation().c_str()));
try
auto const hostFxr = HostFxr::CreateFromLoadedModule();
{
pFnHostFxrSearchDirectories = ModuleHelpers::GetKnownProcAddress<hostfxr_get_native_search_directories_fn>(m_hHostFxrDll, "hostfxr_get_native_search_directories");
}
catch (...)
{
EventLog::Error(
ASPNETCORE_EVENT_GENERAL_ERROR,
ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
hostfxrOptions.GetHostFxrLocation().c_str()
);
return OBSERVE_CAUGHT_EXCEPTION();
}
RETURN_LAST_ERROR_IF_NULL(pFnHostFxrSearchDirectories);
struNativeSearchPaths.resize(dwBufferSize);
outputManager->TryStartRedirection();
while (TRUE)
{
DWORD hostfxrArgc;
std::unique_ptr<PCWSTR[]> hostfxrArgv;
hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
const auto intHostFxrExitCode = pFnHostFxrSearchDirectories(
hostfxrArgc,
hostfxrArgv.get(),
struNativeSearchPaths.data(),
dwBufferSize,
&dwRequiredBufferSize
);
if (intHostFxrExitCode == 0)
{
break;
}
else if (dwRequiredBufferSize > dwBufferSize)
{
dwBufferSize = dwRequiredBufferSize + 1; // for null terminator
struNativeSearchPaths.resize(dwBufferSize);
}
else
{
// Stop redirecting before logging to event log to avoid logging debug logs
// twice.
outputManager->TryStopRedirection();
// If hostfxr didn't set the required buffer size, something in the app is misconfigured
// Ex: Framework not found.
EventLog::Error(
ASPNETCORE_EVENT_GENERAL_ERROR,
ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG
auto redirectionOutput = LoggingHelpers::CreateOutputs(
pConfiguration.QueryStdoutLogEnabled(),
pConfiguration.QueryStdoutLogFile(),
pApplication.GetApplicationPhysicalPath(),
std::move(stringRedirectionOutput)
);
return E_UNEXPECTED;
StandardStreamRedirection stdOutRedirection(*redirectionOutput.get(), m_pServer.IsCommandLineLaunch());
auto hostFxrErrorRedirection = hostFxr.RedirectOutput(redirectionOutput.get());
struNativeSearchPaths.resize(dwBufferSize);
while (TRUE)
{
DWORD hostfxrArgc;
std::unique_ptr<PCWSTR[]> hostfxrArgv;
hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
const auto intHostFxrExitCode = hostFxr.GetNativeSearchDirectories(
hostfxrArgc,
hostfxrArgv.get(),
struNativeSearchPaths.data(),
dwBufferSize,
&dwRequiredBufferSize
);
if (intHostFxrExitCode == 0)
{
break;
}
else if (dwRequiredBufferSize > dwBufferSize)
{
dwBufferSize = dwRequiredBufferSize + 1; // for null terminator
struNativeSearchPaths.resize(dwBufferSize);
}
else
{
// If hostfxr didn't set the required buffer size, something in the app is misconfigured
// Ex: Framework not found.
EventLog::Error(
ASPNETCORE_EVENT_GENERAL_ERROR,
ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG
);
return E_UNEXPECTED;
}
}
}
outputManager->TryStopRedirection();
struNativeSearchPaths.resize(struNativeSearchPaths.find(L'\0'));
auto fFound = FALSE;
@ -323,4 +307,4 @@ HandlerResolver::FindNativeAssemblyFromHostfxr(
return S_OK;
}
CATCH_RETURN()

View File

@ -6,10 +6,10 @@
#include <memory>
#include <string>
#include "ShimOptions.h"
#include "hostfxroptions.h"
#include "HostFxrResolutionResult.h"
#include "HandleWrapper.h"
#include "ApplicationFactory.h"
#include "BaseOutputManager.h"
#include "RedirectionOutput.h"
class HandlerResolver
{
@ -21,7 +21,13 @@ public:
private:
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory);
HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath);
HRESULT FindNativeAssemblyFromHostfxr(const HOSTFXR_OPTIONS& hostfxrOptions, PCWSTR libraryName, std::wstring& handlerDllPath, BaseOutputManager* outputManager);
HRESULT FindNativeAssemblyFromHostfxr(
const HostFxrResolutionResult& hostfxrOptions,
PCWSTR libraryName,
std::wstring& handlerDllPath,
const IHttpApplication &pApplication,
const ShimOptions& pConfiguration,
std::shared_ptr<RedirectionOutput> stringRedirectionOutput);
HMODULE m_hModule;
const IHttpServer &m_pServer;

View File

@ -4,7 +4,7 @@
#include "applicationinfo.h"
#include "proxymodule.h"
#include "hostfxr_utility.h"
#include "HostFxrResolver.h"
#include "debugutil.h"
#include "resources.h"
#include "SRWExclusiveLock.h"

View File

@ -3,7 +3,7 @@
#pragma once
#include "hostfxroptions.h"
#include "HostFxrResolutionResult.h"
#include "iapplication.h"
#include "SRWSharedLock.h"
#include "HandlerResolver.h"

View File

@ -1,68 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include "IOutputManager.h"
#include "StdWrapper.h"
#include "EventLog.h"
#include "exceptions.h"
#include "StringHelpers.h"
#include "debugutil.h"
class BaseOutputManager :
public IOutputManager
{
public:
BaseOutputManager() : BaseOutputManager(/* fEnableNativeLogging */ true) {}
BaseOutputManager(bool enableNativeLogging) :
m_disposed(false),
stdoutWrapper(nullptr),
stderrWrapper(nullptr),
m_enableNativeRedirection(enableNativeLogging)
{
InitializeSRWLock(&m_srwLock);
}
~BaseOutputManager() {}
void
TryStartRedirection()
{
const auto startLambda = [&]() { this->Start(); };
TryOperation(startLambda, L"Could not start stdout redirection in %s. Exception message: %s.");
}
void
TryStopRedirection()
{
const auto stopLambda = [&]() { this->Stop(); };
TryOperation(stopLambda, L"Could not stop stdout redirection in %s. Exception message: %s.");
}
protected:
std::wstring m_stdOutContent;
bool m_disposed;
bool m_enableNativeRedirection;
SRWLOCK m_srwLock{};
std::unique_ptr<StdWrapper> stdoutWrapper;
std::unique_ptr<StdWrapper> stderrWrapper;
template<typename Functor>
void
TryOperation(Functor func,
std::wstring exceptionMessage)
{
try
{
func();
}
catch (const std::runtime_error& exception)
{
EventLog::Warn(ASPNETCORE_EVENT_GENERAL_WARNING, exceptionMessage.c_str(), GetModuleName().c_str(), to_wide_string(exception.what(), GetConsoleOutputCP()).c_str());
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
}
}
};

View File

@ -199,7 +199,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="application.h" />
<ClInclude Include="baseoutputmanager.h" />
<ClInclude Include="BindingInformation.h" />
<ClInclude Include="ConfigurationSection.h" />
<ClInclude Include="ConfigurationSource.h" />
@ -209,22 +208,21 @@
<ClInclude Include="EventTracing.h" />
<ClInclude Include="exceptions.h" />
<ClInclude Include="file_utility.h" />
<ClInclude Include="FileOutputManager.h" />
<ClInclude Include="GlobalVersionUtility.h" />
<ClInclude Include="fx_ver.h" />
<ClInclude Include="HandleWrapper.h" />
<ClInclude Include="hostfxroptions.h" />
<ClInclude Include="hostfxr_utility.h" />
<ClInclude Include="HostFxr.h" />
<ClInclude Include="HostFxrResolutionResult.h" />
<ClInclude Include="HostFxrResolver.h" />
<ClInclude Include="iapplication.h" />
<ClInclude Include="debugutil.h" />
<ClInclude Include="InvalidOperationException.h" />
<ClInclude Include="IOutputManager.h" />
<ClInclude Include="RedirectionOutput.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" />
<ClInclude Include="StandardStreamRedirection.h" />
<ClInclude Include="RegistryKey.h" />
<ClInclude Include="requesthandler.h" />
<ClInclude Include="resources.h" />
@ -246,14 +244,15 @@
<ClCompile Include="Environment.cpp" />
<ClCompile Include="EventLog.cpp" />
<ClCompile Include="file_utility.cpp" />
<ClCompile Include="FileOutputManager.cpp" />
<ClCompile Include="fx_ver.cpp" />
<ClCompile Include="GlobalVersionUtility.cpp" />
<ClCompile Include="HandleWrapper.cpp" />
<ClCompile Include="hostfxr_utility.cpp" />
<ClCompile Include="hostfxroptions.cpp" />
<ClCompile Include="HostFxr.cpp" />
<ClCompile Include="HostFxrResolver.cpp" />
<ClCompile Include="HostFxrResolutionResult.cpp" />
<ClCompile Include="LoggingHelpers.cpp" />
<ClCompile Include="PipeOutputManager.cpp" />
<ClCompile Include="StandardStreamRedirection.cpp" />
<ClCompile Include="RedirectionOutput.cpp" />
<ClCompile Include="RegistryKey.cpp" />
<ClCompile Include="StdWrapper.cpp" />
<ClCompile Include="SRWExclusiveLock.cpp" />

View File

@ -1,178 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "stdafx.h"
#include "FileOutputManager.h"
#include "sttimer.h"
#include "exceptions.h"
#include "debugutil.h"
#include "SRWExclusiveLock.h"
#include "file_utility.h"
#include "StdWrapper.h"
#include "StringHelpers.h"
FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring pwzApplicationPath) :
FileOutputManager(pwzStdOutLogFileName, pwzApplicationPath, /* fEnableNativeLogging */ true) { }
FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring pwzApplicationPath, bool fEnableNativeLogging) :
BaseOutputManager(fEnableNativeLogging),
m_hLogFileHandle(INVALID_HANDLE_VALUE),
m_applicationPath(pwzApplicationPath),
m_stdOutLogFileName(pwzStdOutLogFileName)
{
InitializeSRWLock(&m_srwLock);
}
FileOutputManager::~FileOutputManager()
{
FileOutputManager::Stop();
}
// Start redirecting stdout and stderr into the file handle.
// Uses sttimer to continuously flush output into the file.
void
FileOutputManager::Start()
{
SYSTEMTIME systemTime;
SECURITY_ATTRIBUTES saAttr = { 0 };
FILETIME processCreationTime;
FILETIME dummyFileTime;
// To make Console.* functions work, allocate a console
// in the current process.
if (!AllocConsole())
{
THROW_LAST_ERROR_IF(GetLastError() != ERROR_ACCESS_DENIED);
}
// Concatenate the log file name and application path
auto logPath = m_applicationPath / m_stdOutLogFileName;
create_directories(logPath.parent_path());
THROW_LAST_ERROR_IF(!GetProcessTimes(
GetCurrentProcess(),
&processCreationTime,
&dummyFileTime,
&dummyFileTime,
&dummyFileTime));
THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime));
m_logFilePath = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
logPath.c_str(),
systemTime.wYear,
systemTime.wMonth,
systemTime.wDay,
systemTime.wHour,
systemTime.wMinute,
systemTime.wSecond,
GetCurrentProcessId());
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create the file with both READ and WRITE.
m_hLogFileHandle = CreateFileW(m_logFilePath.c_str(),
FILE_READ_DATA | FILE_WRITE_DATA,
FILE_SHARE_READ,
&saAttr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
THROW_LAST_ERROR_IF(m_hLogFileHandle == INVALID_HANDLE_VALUE);
stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, m_hLogFileHandle, m_enableNativeRedirection);
stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, m_hLogFileHandle, m_enableNativeRedirection);
stdoutWrapper->StartRedirection();
stderrWrapper->StartRedirection();
}
void
FileOutputManager::Stop()
{
CHAR pzFileContents[MAX_FILE_READ_SIZE] = { 0 };
DWORD dwNumBytesRead;
LARGE_INTEGER li = { 0 };
DWORD dwFilePointer = 0;
HANDLE handle = NULL;
WIN32_FIND_DATA fileData;
if (m_disposed)
{
return;
}
SRWExclusiveLock lock(m_srwLock);
if (m_disposed)
{
return;
}
m_disposed = true;
if (m_hLogFileHandle == INVALID_HANDLE_VALUE)
{
THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
}
FlushFileBuffers(m_hLogFileHandle);
if (stdoutWrapper != nullptr)
{
THROW_IF_FAILED(stdoutWrapper->StopRedirection());
}
if (stderrWrapper != nullptr)
{
THROW_IF_FAILED(stderrWrapper->StopRedirection());
}
// delete empty log file
handle = FindFirstFile(m_logFilePath.c_str(), &fileData);
if (handle != INVALID_HANDLE_VALUE &&
handle != NULL &&
fileData.nFileSizeHigh == 0 &&
fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh
{
FindClose(handle);
LOG_LAST_ERROR_IF(!DeleteFile(m_logFilePath.c_str()));
return;
}
// Read the first 30Kb from the file and store it in a buffer.
// By doing this, we can close the handle to the file and be done with it.
THROW_LAST_ERROR_IF(!GetFileSizeEx(m_hLogFileHandle, &li));
if (li.HighPart > 0)
{
THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID));
}
dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN);
THROW_LAST_ERROR_IF(dwFilePointer == INVALID_SET_FILE_POINTER);
THROW_LAST_ERROR_IF(!ReadFile(m_hLogFileHandle, pzFileContents, MAX_FILE_READ_SIZE, &dwNumBytesRead, NULL));
m_stdOutContent = to_wide_string(std::string(pzFileContents, dwNumBytesRead), GetConsoleOutputCP());
auto content = GetStdOutContent();
if (!content.empty())
{
// printf will fail in in full IIS
if (wprintf(content.c_str()) != -1)
{
// Need to flush contents for the new stdout and stderr
_flushall();
}
}
}
std::wstring FileOutputManager::GetStdOutContent()
{
return m_stdOutContent;
}

View File

@ -1,30 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include "sttimer.h"
#include "HandleWrapper.h"
#include "StdWrapper.h"
#include "stringa.h"
#include "stringu.h"
#include "BaseOutputManager.h"
class FileOutputManager : public BaseOutputManager
{
#define MAX_FILE_READ_SIZE 30000
public:
FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName);
FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName, bool fEnableNativeLogging);
~FileOutputManager();
virtual std::wstring GetStdOutContent() override;
void Start() override;
void Stop() override;
private:
HandleWrapper<InvalidHandleTraits> m_hLogFileHandle;
std::wstring m_stdOutLogFileName;
std::filesystem::path m_applicationPath;
std::filesystem::path m_logFilePath;
};

View File

@ -0,0 +1,70 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "HostFxr.h"
#include "ModuleHelpers.h"
#include "EventLog.h"
HostFxrErrorRedirector::HostFxrErrorRedirector(corehost_set_error_writer_fn setErrorWriterFn, RedirectionOutput* writeFunction) noexcept
: m_setErrorWriter(setErrorWriterFn)
{
if (m_setErrorWriter)
{
m_writeFunction = writeFunction;
m_setErrorWriter(HostFxrErrorRedirectorCallback);
}
}
HostFxrErrorRedirector::~HostFxrErrorRedirector()
{
if (m_setErrorWriter)
{
m_setErrorWriter(nullptr);
m_writeFunction = nullptr;
}
}
void HostFxrErrorRedirector::HostFxrErrorRedirectorCallback(const WCHAR* message)
{
m_writeFunction->Append(std::wstring(message) + L"\r\n");
}
int HostFxr::Main(DWORD argc, const PCWSTR* argv) const noexcept(false)
{
return m_hostfxr_main_fn(argc, argv);
}
int HostFxr::GetNativeSearchDirectories(INT argc, const PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size) const noexcept
{
return m_hostfxr_get_native_search_directories_fn(argc, argv, buffer, buffer_size, required_buffer_size);
}
HostFxrErrorRedirector HostFxr::RedirectOutput(RedirectionOutput* writer) const noexcept
{
return HostFxrErrorRedirector(m_corehost_set_error_writer_fn, writer);
}
HostFxr HostFxr::CreateFromLoadedModule()
{
HMODULE hModule;
THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
try
{
return HostFxr(
ModuleHelpers::GetKnownProcAddress<hostfxr_main_fn>(hModule, "hostfxr_main"),
ModuleHelpers::GetKnownProcAddress<hostfxr_get_native_search_directories_fn>(hModule, "hostfxr_get_native_search_directories"),
ModuleHelpers::GetKnownProcAddress<corehost_set_error_writer_fn>(hModule, "hostfxr_set_error_writer", /* optional */ true));
}
catch (...)
{
EventLog::Error(
ASPNETCORE_EVENT_GENERAL_ERROR,
ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
ModuleHelpers::GetModuleFileNameValue(hModule).c_str()
);
throw;
}
}

View File

@ -0,0 +1,57 @@
// 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 "HostFxrResolver.h"
#include "exceptions.h"
#include "RedirectionOutput.h"
typedef INT(*hostfxr_get_native_search_directories_fn) (INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size);
typedef INT(*hostfxr_main_fn) (DWORD argc, CONST PCWSTR argv[]);
typedef void(*corehost_error_writer_fn) (const WCHAR* message);
typedef corehost_error_writer_fn(*corehost_set_error_writer_fn) (corehost_error_writer_fn error_writer);
class HostFxrErrorRedirector: NonCopyable
{
public:
HostFxrErrorRedirector(corehost_set_error_writer_fn setErrorWriterFn, RedirectionOutput* writeFunction) noexcept;
~HostFxrErrorRedirector();
static void HostFxrErrorRedirectorCallback(const WCHAR* message);
private:
corehost_set_error_writer_fn m_setErrorWriter;
static inline thread_local RedirectionOutput* m_writeFunction;
};
class HostFxr
{
public:
HostFxr(
hostfxr_main_fn hostfxr_main_fn,
hostfxr_get_native_search_directories_fn hostfxr_get_native_search_directories_fn,
corehost_set_error_writer_fn corehost_set_error_writer_fn) noexcept
: m_hostfxr_main_fn(hostfxr_main_fn),
m_hostfxr_get_native_search_directories_fn(hostfxr_get_native_search_directories_fn),
m_corehost_set_error_writer_fn(corehost_set_error_writer_fn)
{
}
~HostFxr() = default;
int Main(DWORD argc, CONST PCWSTR* argv) const noexcept(false);
int GetNativeSearchDirectories(INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size) const noexcept;
HostFxrErrorRedirector RedirectOutput(RedirectionOutput* writer) const noexcept;
static
HostFxr CreateFromLoadedModule();
private:
hostfxr_main_fn m_hostfxr_main_fn;
hostfxr_get_native_search_directories_fn m_hostfxr_get_native_search_directories_fn;
corehost_set_error_writer_fn m_corehost_set_error_writer_fn;
};

View File

@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "HostFxrResolutionResult.h"
#include "HostFxrResolver.h"
#include "debugutil.h"
#include "exceptions.h"
#include "EventLog.h"
void HostFxrResolutionResult::GetArguments(DWORD& hostfxrArgc, std::unique_ptr<PCWSTR[]>& hostfxrArgv) const
{
hostfxrArgc = static_cast<DWORD>(m_arguments.size());
hostfxrArgv = std::make_unique<PCWSTR[]>(hostfxrArgc);
for (DWORD i = 0; i < hostfxrArgc; ++i)
{
hostfxrArgv[i] = m_arguments[i].c_str();
}
}
HRESULT HostFxrResolutionResult::Create(
_In_ const std::wstring& pcwzDotnetExePath,
_In_ const std::wstring& pcwzProcessPath,
_In_ const std::wstring& pcwzApplicationPhysicalPath,
_In_ const std::wstring& pcwzArguments,
_Out_ std::unique_ptr<HostFxrResolutionResult>& ppWrapper)
{
std::filesystem::path knownDotnetLocation;
if (!pcwzDotnetExePath.empty())
{
knownDotnetLocation = pcwzDotnetExePath;
}
try
{
std::filesystem::path hostFxrDllPath;
std::vector<std::wstring> arguments;
HostFxrResolver::GetHostFxrParameters(
pcwzProcessPath,
pcwzApplicationPhysicalPath,
pcwzArguments,
hostFxrDllPath,
knownDotnetLocation,
arguments);
LOG_INFOF(L"Parsed hostfxr options: dotnet location: '%ls' hostfxr path: '%ls' arguments:", knownDotnetLocation.c_str(), hostFxrDllPath.c_str());
for (size_t i = 0; i < arguments.size(); i++)
{
LOG_INFOF(L"Argument[%d] = '%ls'", i, arguments[i].c_str());
}
ppWrapper = std::make_unique<HostFxrResolutionResult>(knownDotnetLocation, hostFxrDllPath, arguments);
}
catch (InvalidOperationException &ex)
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
pcwzApplicationPhysicalPath.c_str(),
ex.as_wstring().c_str());
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();
return S_OK;
}

View File

@ -0,0 +1,53 @@
// 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 <Windows.h>
#include <memory>
#include <filesystem>
#include <utility>
#include <vector>
#include <string>
class HostFxrResolutionResult
{
public:
HostFxrResolutionResult(
std::filesystem::path dotnetExeLocation,
std::filesystem::path hostFxrLocation,
std::vector<std::wstring> arguments
) noexcept
: m_dotnetExeLocation(std::move(dotnetExeLocation)),
m_hostFxrLocation(std::move(hostFxrLocation)),
m_arguments(std::move(arguments))
{}
void
GetArguments(DWORD& hostfxrArgc, std::unique_ptr<PCWSTR[]>& hostfxrArgv) const;
const std::filesystem::path&
GetHostFxrLocation() const noexcept
{
return m_hostFxrLocation;
}
const std::filesystem::path&
GetDotnetExeLocation() const noexcept
{
return m_dotnetExeLocation;
}
static
HRESULT Create(
_In_ const std::wstring& pcwzExeLocation,
_In_ const std::wstring& pcwzProcessPath,
_In_ const std::wstring& pcwzApplicationPhysicalPath,
_In_ const std::wstring& pcwzArguments,
_Out_ std::unique_ptr<HostFxrResolutionResult>& ppWrapper);
private:
const std::filesystem::path m_dotnetExeLocation;
const std::filesystem::path m_hostFxrLocation;
const std::vector<std::wstring> m_arguments;
};

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "hostfxr_utility.h"
#include "HostFxrResolver.h"
#include <atlcomcli.h>
#include "fx_ver.h"
@ -15,7 +15,7 @@
namespace fs = std::filesystem;
void
HOSTFXR_UTILITY::GetHostFxrParameters(
HostFxrResolver::GetHostFxrParameters(
const fs::path &processPath,
const fs::path &applicationPhysicalPath,
const std::wstring &applicationArguments,
@ -138,13 +138,13 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
}
BOOL
HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
HostFxrResolver::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
{
return ends_with(dotnetPath, L"dotnet.exe", true);
}
void
HOSTFXR_UTILITY::AppendArguments(
HostFxrResolver::AppendArguments(
const std::wstring &applicationArguments,
const fs::path &applicationPhysicalPath,
std::vector<std::wstring> &arguments,
@ -216,7 +216,7 @@ HOSTFXR_UTILITY::AppendArguments(
// like: C:\Program Files\dotnet\dotnet.exe, C:\Program Files\dotnet\dotnet, dotnet.exe, or dotnet.
// Get the absolute path to dotnet. If the path is already an absolute path, it will return that path
fs::path
HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
HostFxrResolver::GetAbsolutePathToDotnet(
const fs::path & applicationPath,
const fs::path & requestedPath
)
@ -296,7 +296,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
}
fs::path
HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
HostFxrResolver::GetAbsolutePathToHostFxr(
const fs::path & dotnetPath
)
{
@ -336,7 +336,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
// Returns true if a valid dotnet was found, else false.R
//
std::optional<fs::path>
HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
HostFxrResolver::InvokeWhereToFindDotnet()
{
HRESULT hr = S_OK;
// Arguments to call where.exe
@ -480,14 +480,14 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
}
std::optional<fs::path>
HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles()
HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles()
{
const auto programFilesDotnet = fs::path(Environment::ExpandEnvironmentVariables(L"%ProgramFiles%")) / "dotnet" / "dotnet.exe";
return is_regular_file(programFilesDotnet) ? std::make_optional(programFilesDotnet) : std::nullopt;
}
std::wstring
HOSTFXR_UTILITY::FindHighestDotNetVersion(
HostFxrResolver::FindHighestDotNetVersion(
_In_ std::vector<std::wstring> & vFolders
)
{
@ -505,7 +505,7 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion(
}
VOID
HOSTFXR_UTILITY::FindDotNetFolders(
HostFxrResolver::FindDotNetFolders(
const std::filesystem::path &path,
_Out_ std::vector<std::wstring> &pvFolders
)

View File

@ -9,12 +9,9 @@
#include <optional>
#include <string>
typedef INT(*hostfxr_get_native_search_directories_fn) (CONST INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size);
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]);
#define READ_BUFFER_SIZE 4096
class HOSTFXR_UTILITY
class HostFxrResolver
{
public:
@ -68,7 +65,6 @@ private:
const std::filesystem::path & dotnetPath
);
static
std::optional<std::filesystem::path>
InvokeWhereToFindDotnet();

View File

@ -1,27 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include "stdafx.h"
#include "stringa.h"
class IOutputManager
{
public:
virtual
void
Start() = 0;
virtual
~IOutputManager() = default;
virtual
std::wstring
GetStdOutContent() = 0;
virtual
void
Stop() = 0;
};

View File

@ -3,55 +3,21 @@
#include "stdafx.h"
#include "LoggingHelpers.h"
#include "FileOutputManager.h"
#include "PipeOutputManager.h"
#include "NullOutputManager.h"
#include "debugutil.h"
#include <Windows.h>
#include <io.h>
#include "ntassert.h"
#include "StandardStreamRedirection.h"
#include "exceptions.h"
#include "EventLog.h"
#include "BaseOutputManager.h"
HRESULT
LoggingHelpers::CreateLoggingProvider(
bool fIsLoggingEnabled,
bool fEnableNativeLogging,
PCWSTR pwzStdOutFileName,
PCWSTR pwzApplicationPath,
std::unique_ptr<BaseOutputManager>& outputManager
)
std::shared_ptr<RedirectionOutput> LoggingHelpers::CreateOutputs(
bool enableFileLogging,
std::wstring outputFileName,
std::wstring applicationPath,
std::shared_ptr<RedirectionOutput> stringStreamOutput)
{
HRESULT hr = S_OK;
DBG_ASSERT(outputManager != NULL);
try
auto stdOutOutput = std::make_shared<StandardOutputRedirectionOutput>();
std::shared_ptr<RedirectionOutput> fileOutput;
if (enableFileLogging)
{
// Check if there is an existing active console window before redirecting
// Window == IISExpress with active console window, don't redirect to a pipe
// if true.
CONSOLE_SCREEN_BUFFER_INFO dummy;
if (fIsLoggingEnabled)
{
auto manager = std::make_unique<FileOutputManager>(pwzStdOutFileName, pwzApplicationPath, fEnableNativeLogging);
outputManager = std::move(manager);
}
else if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &dummy))
{
outputManager = std::make_unique<PipeOutputManager>(fEnableNativeLogging);
}
else
{
outputManager = std::make_unique<NullOutputManager>();
}
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
fileOutput = std::make_shared<FileRedirectionOutput>(applicationPath, outputFileName);
}
return hr;
return std::make_shared<AggregateRedirectionOutput>(std::move(fileOutput), std::move(stdOutOutput), std::move(stringStreamOutput));
}

View File

@ -3,20 +3,19 @@
#pragma once
#include "BaseOutputManager.h"
#include "NonCopyable.h"
#include "StandardStreamRedirection.h"
class LoggingHelpers
{
public:
static
HRESULT
CreateLoggingProvider(
bool fLoggingEnabled,
bool fEnableNativeLogging,
PCWSTR pwzStdOutFileName,
PCWSTR pwzApplicationPath,
std::unique_ptr<BaseOutputManager>& outputManager
static std::shared_ptr<RedirectionOutput>
CreateOutputs(
bool enableFileLogging,
std::wstring outputFileName,
std::wstring applicationPath,
std::shared_ptr<RedirectionOutput> stringStreamOutput
);
};

View File

@ -14,7 +14,7 @@ public:
void IncrementCurrentModuleRefCount(HandleWrapper<ModuleHandleTraits> &handle)
{
WCHAR path[MAX_PATH];
#pragma warning( push )
#pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay
@ -26,14 +26,30 @@ public:
template<typename Func>
static
Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName) {
Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName, bool optional = false) {
#pragma warning( push )
#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast
#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast
auto proc = reinterpret_cast<Func>(GetProcAddress(hModule, lpProcName));
#pragma warning( pop )
THROW_LAST_ERROR_IF (!proc);
THROW_LAST_ERROR_IF (!optional && !proc);
return proc;
}
static
std::wstring
GetModuleFileNameValue(HMODULE hModule)
{
// this method is used for logging purposes for modules known to be under MAX_PATH
WCHAR path[MAX_PATH];
#pragma warning( push )
#pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay
THROW_LAST_ERROR_IF(!GetModuleFileName(hModule, path, MAX_PATH));
return path;
#pragma warning( pop )
}
};

View File

@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include "stdafx.h"
class NullOutputManager : public BaseOutputManager
{
public:
NullOutputManager() = default;
~NullOutputManager() = default;
void Start()
{
}
void Stop()
{
}
std::wstring GetStdOutContent()
{
return L"";
}
};

View File

@ -1,38 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include "StdWrapper.h"
#include "stringu.h"
#include "BaseOutputManager.h"
class PipeOutputManager : public BaseOutputManager
{
// Timeout to be used if a thread never exits
#define PIPE_OUTPUT_THREAD_TIMEOUT 2000
// Max event log message is ~32KB, limit pipe size just below that.
#define MAX_PIPE_READ_SIZE 30000
public:
PipeOutputManager();
PipeOutputManager(bool fEnableNativeLogging);
~PipeOutputManager();
void Start() override;
void Stop() override;
std::wstring GetStdOutContent() override;
// Thread functions
void ReadStdErrHandleInternal();
static void ReadStdErrHandle(LPVOID pContext);
private:
HANDLE m_hErrReadPipe;
HANDLE m_hErrWritePipe;
HANDLE m_hErrThread;
CHAR m_pipeContents[MAX_PIPE_READ_SIZE] = { 0 };
DWORD m_numBytesReadTotal;
};

View File

@ -0,0 +1,131 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "RedirectionOutput.h"
#include <filesystem>
#include "exceptions.h"
#include "EventLog.h"
AggregateRedirectionOutput::AggregateRedirectionOutput(std::shared_ptr<RedirectionOutput> outputA, std::shared_ptr<RedirectionOutput> outputB, std::shared_ptr<RedirectionOutput> outputC) noexcept(true):
m_outputA(std::move(outputA)), m_outputB(std::move(outputB)), m_outputC(std::move(outputC))
{
}
void AggregateRedirectionOutput::Append(const std::wstring& text)
{
if (m_outputA != nullptr)
{
m_outputA->Append(text);
}
if (m_outputB != nullptr)
{
m_outputB->Append(text);
}
if (m_outputC != nullptr)
{
m_outputC->Append(text);
}
}
FileRedirectionOutput::FileRedirectionOutput(const std::wstring& applicationPath, const std::wstring& fileName)
{
try
{
SYSTEMTIME systemTime{};
FILETIME processCreationTime;
FILETIME dummyFileTime;
// Concatenate the log file name and application path
auto logPath = std::filesystem::path(applicationPath) / fileName;
create_directories(logPath.parent_path());
THROW_LAST_ERROR_IF(!GetProcessTimes(
GetCurrentProcess(),
&processCreationTime,
&dummyFileTime,
&dummyFileTime,
&dummyFileTime));
THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime));
m_fileName = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
logPath.c_str(),
systemTime.wYear,
systemTime.wMonth,
systemTime.wDay,
systemTime.wHour,
systemTime.wMinute,
systemTime.wSecond,
GetCurrentProcessId());
m_file.exceptions(std::ifstream::failbit);
m_file.open(m_fileName, std::wofstream::out | std::wofstream::app);
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
EventLog::Warn(
ASPNETCORE_EVENT_GENERAL_WARNING,
L"Could not start stdout file redirection to '%s' with application base '%s'. %s.",
fileName.c_str(),
applicationPath.c_str(),
CaughtExceptionToString().c_str());
}
}
void FileRedirectionOutput::Append(const std::wstring& text)
{
if (m_file.is_open())
{
const auto multiByte = to_multi_byte_string(text, CP_UTF8);
m_file << multiByte;
}
}
FileRedirectionOutput::~FileRedirectionOutput()
{
if (m_file.is_open())
{
m_file.close();
std::error_code ec;
if (std::filesystem::file_size(m_fileName, ec) == 0 && SUCCEEDED_LOG(ec))
{
std::filesystem::remove(m_fileName, ec);
LOG_IF_FAILED(ec);
}
}
}
StandardOutputRedirectionOutput::StandardOutputRedirectionOutput(): m_handle(GetStdHandle(STD_OUTPUT_HANDLE))
{
HANDLE stdOutHandle;
DuplicateHandle(
/* hSourceProcessHandle*/ GetCurrentProcess(),
/* hSourceHandle */ GetStdHandle(STD_OUTPUT_HANDLE),
/* hTargetProcessHandle */ GetCurrentProcess(),
/* lpTargetHandle */&stdOutHandle,
/* dwDesiredAccess */ 0, // dwDesired is ignored if DUPLICATE_SAME_ACCESS is specified
/* bInheritHandle */ FALSE,
/* dwOptions */ DUPLICATE_SAME_ACCESS);
m_handle = stdOutHandle;
}
void StandardOutputRedirectionOutput::Append(const std::wstring& text)
{
DWORD nBytesWritten = 0;
auto encodedBytes = to_multi_byte_string(text, GetConsoleOutputCP());
WriteFile(m_handle, encodedBytes.data(), static_cast<DWORD>(encodedBytes.size()), &nBytesWritten, nullptr);
}
StringStreamRedirectionOutput::StringStreamRedirectionOutput()
{
InitializeSRWLock(&m_srwLock);
}
void StringStreamRedirectionOutput::Append(const std::wstring& text)
{
SRWExclusiveLock lock(m_srwLock);
auto const writeSize = min(m_charactersLeft, text.size());
m_output.write(text.c_str(), writeSize);
m_charactersLeft -= writeSize;
}

View File

@ -0,0 +1,96 @@
// 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 "SRWExclusiveLock.h"
#include "NonCopyable.h"
#include "HandleWrapper.h"
#include <fstream>
class RedirectionOutput
{
public:
virtual ~RedirectionOutput() = default;
virtual void Append(const std::wstring& text) = 0;
};
class AggregateRedirectionOutput: NonCopyable, public RedirectionOutput
{
public:
AggregateRedirectionOutput(std::shared_ptr<RedirectionOutput> outputA, std::shared_ptr<RedirectionOutput> outputB, std::shared_ptr<RedirectionOutput> outputC) noexcept(true);
void Append(const std::wstring& text) override;
private:
std::shared_ptr<RedirectionOutput> m_outputA;
std::shared_ptr<RedirectionOutput> m_outputB;
std::shared_ptr<RedirectionOutput> m_outputC;
};
class FileRedirectionOutput: NonCopyable, public RedirectionOutput
{
public:
FileRedirectionOutput(const std::wstring& applicationPath, const std::wstring& fileName);
void Append(const std::wstring& text) override;
~FileRedirectionOutput() override;
private:
std::wstring m_fileName;
std::ofstream m_file;
};
class StandardOutputRedirectionOutput: NonCopyable, public RedirectionOutput
{
public:
StandardOutputRedirectionOutput();
void Append(const std::wstring& text) override;
private:
HandleWrapper<InvalidHandleTraits> m_handle;
};
class ForwardingRedirectionOutput: NonCopyable, public RedirectionOutput
{
public:
ForwardingRedirectionOutput(RedirectionOutput ** target) noexcept
: m_target(target)
{
}
void Append(const std::wstring& text) override
{
auto const target = *m_target;
if (target)
{
target->Append(text);
}
}
private:
RedirectionOutput** m_target;
};
class StringStreamRedirectionOutput: NonCopyable, public RedirectionOutput
{
public:
StringStreamRedirectionOutput();
void Append(const std::wstring& text) override;
std::wstring GetOutput() const
{
return m_output.str();
}
private:
// Logs collected by this output are mostly used for Event Log messages where size limit is 32K
std::size_t m_charactersLeft = 30000;
std::wstringstream m_output;
SRWLOCK m_srwLock{};
};

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "PipeOutputManager.h"
#include "StandardStreamRedirection.h"
#include "stdafx.h"
#include "Exceptions.h"
@ -13,29 +13,27 @@
#define LOG_IF_DUPFAIL(err) do { if (err == -1) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0);
#define LOG_IF_ERRNO(err) do { if (err != 0) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0);
PipeOutputManager::PipeOutputManager()
: PipeOutputManager( /* fEnableNativeLogging */ true)
{
}
PipeOutputManager::PipeOutputManager(bool fEnableNativeLogging) :
BaseOutputManager(fEnableNativeLogging),
StandardStreamRedirection::StandardStreamRedirection(RedirectionOutput& output, bool commandLineLaunch) :
m_output(output),
m_hErrReadPipe(INVALID_HANDLE_VALUE),
m_hErrWritePipe(INVALID_HANDLE_VALUE),
m_hErrThread(nullptr),
m_numBytesReadTotal(0)
m_disposed(false),
m_commandLineLaunch(commandLineLaunch)
{
TryStartRedirection();
}
PipeOutputManager::~PipeOutputManager()
StandardStreamRedirection::~StandardStreamRedirection() noexcept(false)
{
PipeOutputManager::Stop();
TryStopRedirection();
}
// Start redirecting stdout and stderr into a pipe
// Continuously read the pipe on a background thread
// until Stop is called.
void PipeOutputManager::Start()
void StandardStreamRedirection::Start()
{
SECURITY_ATTRIBUTES saAttr = { 0 };
HANDLE hStdErrReadPipe;
@ -57,8 +55,8 @@ void PipeOutputManager::Start()
m_hErrReadPipe = hStdErrReadPipe;
m_hErrWritePipe = hStdErrWritePipe;
stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, m_enableNativeRedirection);
stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, m_enableNativeRedirection);
stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, !m_commandLineLaunch);
stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, !m_commandLineLaunch);
LOG_IF_FAILED(stdoutWrapper->StartRedirection());
LOG_IF_FAILED(stderrWrapper->StartRedirection());
@ -80,7 +78,7 @@ void PipeOutputManager::Start()
// and prints any output that was captured in the pipe.
// If more than 30Kb was written to the pipe, that output will
// be thrown away.
void PipeOutputManager::Stop()
void StandardStreamRedirection::Stop()
{
DWORD dwThreadStatus = 0;
@ -123,12 +121,13 @@ void PipeOutputManager::Stop()
// Don't check return value as IO may or may not be completed already.
if (m_hErrThread != nullptr)
{
LOG_INFO(L"Canceling standard stream pipe reader");
CancelSynchronousIo(m_hErrThread);
}
// GetExitCodeThread returns 0 on failure; thread status code is invalid.
if (m_hErrThread != nullptr &&
!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) &&
!LOG_LAST_ERROR_IF(!GetExitCodeThread(m_hErrThread, &dwThreadStatus)) &&
dwThreadStatus == STILL_ACTIVE)
{
// Wait for graceful shutdown, i.e., the exit of the background thread or timeout
@ -155,74 +154,40 @@ void PipeOutputManager::Stop()
CloseHandle(m_hErrReadPipe);
m_hErrReadPipe = INVALID_HANDLE_VALUE;
}
// If we captured any output, relog it to the original stdout
// Useful for the IIS Express scenario as it is running with stdout and stderr
m_stdOutContent = to_wide_string(std::string(m_pipeContents, m_numBytesReadTotal), GetConsoleOutputCP());
if (!m_stdOutContent.empty())
{
// printf will fail in in full IIS
if (wprintf(m_stdOutContent.c_str()) != -1)
{
// Need to flush contents for the new stdout and stderr
_flushall();
}
}
}
std::wstring PipeOutputManager::GetStdOutContent()
{
return m_stdOutContent;
}
void
PipeOutputManager::ReadStdErrHandle(
StandardStreamRedirection::ReadStdErrHandle(
LPVOID pContext
)
{
auto pLoggingProvider = static_cast<PipeOutputManager*>(pContext);
auto pLoggingProvider = static_cast<StandardStreamRedirection*>(pContext);
DBG_ASSERT(pLoggingProvider != NULL);
pLoggingProvider->ReadStdErrHandleInternal();
}
void
PipeOutputManager::ReadStdErrHandleInternal()
StandardStreamRedirection::ReadStdErrHandleInternal()
{
std::string tempBuffer;
tempBuffer.resize(PIPE_READ_SIZE);
// If ReadFile ever returns false, exit the thread
DWORD dwNumBytesRead = 0;
while (true)
{
// Fill a maximum of MAX_PIPE_READ_SIZE into a buffer.
if (ReadFile(m_hErrReadPipe,
&m_pipeContents[m_numBytesReadTotal],
MAX_PIPE_READ_SIZE - m_numBytesReadTotal,
tempBuffer.data(),
PIPE_READ_SIZE,
&dwNumBytesRead,
nullptr))
{
m_numBytesReadTotal += dwNumBytesRead;
if (m_numBytesReadTotal >= MAX_PIPE_READ_SIZE)
{
break;
}
m_output.Append(to_wide_string(tempBuffer.substr(0, dwNumBytesRead), GetConsoleOutputCP()));
}
else
{
return;
}
}
// Using std::string as a wrapper around new char[] so we don't need to call delete
// Also don't allocate on stack as stack size is 128KB by default.
std::string tempBuffer;
tempBuffer.resize(MAX_PIPE_READ_SIZE);
// After reading the maximum amount of data, keep reading in a loop until Stop is called on the output manager.
while (true)
{
if (!ReadFile(m_hErrReadPipe, tempBuffer.data(), MAX_PIPE_READ_SIZE, &dwNumBytesRead, nullptr))
{
return;
}
}
}

View File

@ -0,0 +1,66 @@
// 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 "RedirectionOutput.h"
#include "StdWrapper.h"
#include "ModuleHelpers.h"
class StandardStreamRedirection : NonCopyable
{
// Timeout to be used if a thread never exits
#define PIPE_OUTPUT_THREAD_TIMEOUT 2000
#define PIPE_READ_SIZE 4096
public:
StandardStreamRedirection(RedirectionOutput& output, bool commandLineLaunch);
~StandardStreamRedirection() noexcept(false);
private:
void Start();
void Stop();
void
TryStartRedirection()
{
try
{
Start();
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
}
}
void
TryStopRedirection()
{
try
{
Stop();
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
}
}
// Thread functions
void ReadStdErrHandleInternal();
static void ReadStdErrHandle(LPVOID pContext);
HANDLE m_hErrReadPipe;
HANDLE m_hErrWritePipe;
HANDLE m_hErrThread;
bool m_disposed;
bool m_commandLineLaunch;
SRWLOCK m_srwLock{};
std::unique_ptr<StdWrapper> stdoutWrapper;
std::unique_ptr<StdWrapper> stderrWrapper;
RedirectionOutput& m_output;
};

View File

@ -46,3 +46,14 @@ std::wstring to_wide_string(const std::string &source, const unsigned int codePa
return destination;
}
std::string to_multi_byte_string(const std::wstring& text, const unsigned int codePage)
{
auto const encodedByteCount = WideCharToMultiByte(codePage, 0, text.data(), -1, nullptr, 0, nullptr, nullptr);
std::string encodedBytes;
encodedBytes.resize(encodedByteCount);
WideCharToMultiByte(codePage, 0, text.data(), -1, encodedBytes.data(), encodedByteCount, nullptr, nullptr);
encodedBytes.resize(encodedByteCount - 1);
return encodedBytes;
}

View File

@ -17,6 +17,9 @@ int compare_ignore_case(const std::wstring& s1, const std::wstring& s2);
[[nodiscard]]
std::wstring to_wide_string(const std::string &source, const unsigned int codePage);
[[nodiscard]]
std::string to_multi_byte_string(const std::wstring& text, const unsigned int codePage);
template<typename ... Args>
[[nodiscard]]
std::wstring format(const std::wstring& format, Args ... args)

View File

@ -156,6 +156,17 @@ private:
return hr;
}
__declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS const std::error_code& error_code)
{
if (error_code)
{
TraceHRESULT(LOCATION_CALL error_code.value());
DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Failed error_code returned: 0x%x 0xs at " LOCATION_FORMAT, error_code.value(), error_code.message().c_str(), LOCATION_CALL_ONLY);
return E_FAIL;
}
return ERROR_SUCCESS;
}
__declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ONLY)
{
try
@ -188,6 +199,30 @@ __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ON
}
}
__declspec(noinline) inline std::wstring CaughtExceptionToString()
{
try
{
throw;
}
catch (const InvalidOperationException& exception)
{
return exception.as_wstring();
}
catch (const std::system_error& exception)
{
return to_wide_string(exception.what(), CP_ACP);
}
catch (const std::exception& exception)
{
return to_wide_string(exception.what(), CP_ACP);
}
catch (...)
{
return L"Unknown exception type";
}
}
[[noreturn]]
__declspec(noinline) inline void ThrowResultException(LOCATION_ARGUMENTS HRESULT hr)
{

View File

@ -15,3 +15,5 @@
#include <sstream>
#include <memory>
#include <filesystem>
#include <string>
#include <fstream>

View File

@ -50,13 +50,12 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="ConfigUtilityTests.cpp" />
<ClCompile Include="FileOutputManagerTests.cpp" />
<ClCompile Include="GlobalVersionTests.cpp" />
<ClCompile Include="Helpers.cpp" />
<ClCompile Include="hostfxr_utility_tests.cpp" />
<ClCompile Include="inprocess_application_tests.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="PipeOutputManagerTests.cpp" />
<ClCompile Include="StandardOutputRedirectionTest.cpp" />
<ClCompile Include="BindingInformationTest.cpp" />
<ClCompile Include="utility_tests.cpp" />
</ItemGroup>

View File

@ -1,152 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h"
#include "gtest/internal/gtest-port.h"
#include "FileOutputManager.h"
class FileManagerWrapper
{
public:
FileOutputManager* manager;
FileManagerWrapper(FileOutputManager* m)
: manager(m)
{
manager->Start();
}
~FileManagerWrapper()
{
delete manager;
}
};
namespace FileOutManagerStartupTests
{
using ::testing::Test;
class FileOutputManagerTest : public Test
{
protected:
void
Test(std::wstring fileNamePrefix, FILE* out)
{
PCWSTR expected = L"test";
auto tempDirectory = TempDirectory();
FileOutputManager* pManager = new FileOutputManager(fileNamePrefix, tempDirectory.path());
{
FileManagerWrapper wrapper(pManager);
wprintf(expected, out);
}
for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
{
std::wstring filename(p.path().filename());
ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);
std::wstring content = Helpers::ReadFileContent(std::wstring(p.path()));
}
}
};
TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWritten)
{
Test(L"", stdout);
Test(L"log", stdout);
}
TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWrittenErr)
{
Test(L"", stderr);
Test(L"log", stderr);
}
}
namespace FileOutManagerOutputTests
{
TEST(FileOutManagerOutputTest, StdOut)
{
PCWSTR expected = L"test";
auto tempDirectory = TempDirectory();
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
{
FileManagerWrapper wrapper(pManager);
fwprintf(stdout, expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_FALSE(output.empty());
ASSERT_STREQ(output.c_str(), expected);
}
}
TEST(FileOutManagerOutputTest, StdErr)
{
PCWSTR expected = L"test";
auto tempDirectory = TempDirectory();
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path().c_str());
{
FileManagerWrapper wrapper(pManager);
fwprintf(stderr, expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_FALSE(output.empty());
ASSERT_STREQ(output.c_str(), expected);
}
}
TEST(FileOutManagerOutputTest, CapAt30KB)
{
PCWSTR expected = L"hello world";
auto tempDirectory = TempDirectory();
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
{
FileManagerWrapper wrapper(pManager);
for (int i = 0; i < 3000; i++)
{
wprintf(expected);
}
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_FALSE(output.empty());
ASSERT_EQ(output.size(), 30000);
}
}
TEST(FileOutManagerOutputTest, StartStopRestoresCorrectly)
{
PCWSTR expected = L"test";
auto tempDirectory = TempDirectory();
for (int i = 0; i < 10; i++)
{
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
{
FileManagerWrapper wrapper(pManager);
wprintf(expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_FALSE(output.empty());
ASSERT_STREQ(output.c_str(), expected);
}
}
}
}

View File

@ -1,162 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h"
#include "gtest/internal/gtest-port.h"
#include "PipeOutputManager.h"
class FileManagerWrapper
{
public:
PipeOutputManager * manager;
FileManagerWrapper(PipeOutputManager* m)
: manager(m)
{
manager->Start();
}
~FileManagerWrapper()
{
delete manager;
}
};
namespace PipeOutputManagerTests
{
TEST(PipeManagerOutputTest, StdOut)
{
PCWSTR expected = L"test";
PipeOutputManager* pManager = new PipeOutputManager(true);
pManager->Start();
fwprintf(stdout, expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_STREQ(output.c_str(), expected);
delete pManager;
}
TEST(PipeManagerOutputTest, StdOutMultiToWide)
{
PipeOutputManager* pManager = new PipeOutputManager(true);
pManager->Start();
fprintf(stdout, "test");
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_STREQ(output.c_str(), L"test");
delete pManager;
}
TEST(PipeManagerOutputTest, StdErr)
{
PCWSTR expected = L"test";
PipeOutputManager* pManager = new PipeOutputManager();
pManager->Start();
fwprintf(stderr, expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_STREQ(output.c_str(), expected);
delete pManager;
}
TEST(PipeManagerOutputTest, CheckMaxPipeSize)
{
std::wstring test;
for (int i = 0; i < 3000; i++)
{
test.append(L"hello world");
}
PipeOutputManager* pManager = new PipeOutputManager();
pManager->Start();
wprintf(test.c_str());
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_EQ(output.size(), (DWORD)30000);
delete pManager;
}
TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut)
{
auto m_fdPreviousStdOut = _dup(_fileno(stdout));
auto m_fdPreviousStdErr = _dup(_fileno(stderr));
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
PCWSTR expected = L"test";
PipeOutputManager* pManager = new PipeOutputManager();
pManager->Start();
_dup2(m_fdPreviousStdOut, _fileno(stdout));
_dup2(m_fdPreviousStdErr, _fileno(stderr));
// Test will fail if we didn't redirect stdout back to a file descriptor.
// This is because gtest relies on console output to know if a test succeeded or failed.
// If the output still points to a file/pipe, the test (and all other tests after it) will fail.
delete pManager;
}
TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks)
{
for (int i = 0; i < 10; i++)
{
auto stdoutBefore = _fileno(stdout);
auto stderrBefore = _fileno(stderr);
PCWSTR expected = L"test";
PipeOutputManager* pManager = new PipeOutputManager();
pManager->Start();
fwprintf(stdout, expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_STREQ(output.c_str(), expected);
ASSERT_EQ(stdoutBefore, _fileno(stdout));
ASSERT_EQ(stderrBefore, _fileno(stderr));
delete pManager;
}
// When this returns, we get an AV from gtest.
}
TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr)
{
for (int i = 0; i < 10; i++)
{
auto stdoutBefore = _fileno(stdout);
auto stderrBefore = _fileno(stderr);
auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
PCWSTR expected = L"test";
PipeOutputManager* pManager = new PipeOutputManager();
pManager->Start();
fwprintf(stderr, expected);
pManager->Stop();
auto output = pManager->GetStdOutContent();
ASSERT_STREQ(output.c_str(), expected);
ASSERT_EQ(stdoutBefore, _fileno(stdout));
ASSERT_EQ(stderrBefore, _fileno(stderr));
delete pManager;
}
wprintf(L"Hello!");
}
}

View File

@ -0,0 +1,252 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h"
#include "gtest/internal/gtest-port.h"
#include "StandardStreamRedirection.h"
namespace FileRedirectionOutputTests
{
using ::testing::Test;
class FileRedirectionOutputTest : public Test
{
protected:
void
Test(std::wstring fileNamePrefix, FILE* out)
{
PCWSTR expected = L"test";
auto tempDirectory = TempDirectory();
{
FileRedirectionOutput redirectionOutput(tempDirectory.path(), fileNamePrefix);
StandardStreamRedirection pManager(redirectionOutput, false);
wprintf(expected, out);
}
for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
{
std::wstring filename(p.path().filename());
ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);
std::wstring content = Helpers::ReadFileContent(std::wstring(p.path()));
}
}
};
TEST_F(FileRedirectionOutputTest, WriteToFileCheckContentsWritten)
{
Test(L"", stdout);
Test(L"log", stdout);
}
TEST_F(FileRedirectionOutputTest, WriteToFileCheckContentsWrittenErr)
{
Test(L"", stderr);
Test(L"log", stderr);
}
}
namespace PipeOutputManagerTests
{
TEST(PipeManagerOutputTest, StdOut)
{
PCWSTR expected = L"test";
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fwprintf(stdout, expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_STREQ(output.c_str(), expected);
}
TEST(PipeManagerOutputTest, StdOutMultiToWide)
{
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fprintf(stdout, "test");
}
auto output = redirectionOutput.GetOutput();
ASSERT_STREQ(output.c_str(), L"test");
}
TEST(PipeManagerOutputTest, StdErr)
{
PCWSTR expected = L"test";
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fwprintf(stderr, expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_STREQ(output.c_str(), expected);
}
TEST(PipeManagerOutputTest, CheckMaxPipeSize)
{
std::wstring test;
for (int i = 0; i < 3000; i++)
{
test.append(L"hello world");
}
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
wprintf(test.c_str());
}
auto output = redirectionOutput.GetOutput();
ASSERT_EQ(output.size(), (DWORD)30000);
}
TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut)
{
auto m_fdPreviousStdOut = _dup(_fileno(stdout));
auto m_fdPreviousStdErr = _dup(_fileno(stderr));
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
_dup2(m_fdPreviousStdOut, _fileno(stdout));
_dup2(m_fdPreviousStdErr, _fileno(stderr));
// Test will fail if we didn't redirect stdout back to a file descriptor.
// This is because gtest relies on console output to know if a test succeeded or failed.
// If the output still points to a file/pipe, the test (and all other tests after it) will fail.
}
}
TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks)
{
for (int i = 0; i < 10; i++)
{
auto stdoutBefore = _fileno(stdout);
auto stderrBefore = _fileno(stderr);
PCWSTR expected = L"test";
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fwprintf(stdout, expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_STREQ(output.c_str(), expected);
ASSERT_EQ(stdoutBefore, _fileno(stdout));
ASSERT_EQ(stderrBefore, _fileno(stderr));
}
// When this returns, we get an AV from gtest.
}
TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr)
{
for (int i = 0; i < 10; i++)
{
auto stdoutBefore = _fileno(stdout);
auto stderrBefore = _fileno(stderr);
auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
PCWSTR expected = L"test";
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fwprintf(stderr, expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_STREQ(output.c_str(), expected);
ASSERT_EQ(stdoutBefore, _fileno(stdout));
ASSERT_EQ(stderrBefore, _fileno(stderr));
}
wprintf(L"Hello!");
}
TEST(StringStreamRedirectionOutputTest, StdOut)
{
PCWSTR expected = L"test";
{
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fwprintf(stdout, expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_FALSE(output.empty());
ASSERT_STREQ(output.c_str(), expected);
}
}
TEST(StringStreamRedirectionOutputTest, StdErr)
{
PCWSTR expected = L"test";
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
fwprintf(stderr, expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_FALSE(output.empty());
ASSERT_STREQ(output.c_str(), expected);
}
TEST(StringStreamRedirectionOutputTest, CapAt30KB)
{
PCWSTR expected = L"hello world";
auto tempDirectory = TempDirectory();
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
for (int i = 0; i < 3000; i++)
{
wprintf(expected);
}
}
auto output = redirectionOutput.GetOutput();
ASSERT_FALSE(output.empty());
ASSERT_EQ(output.size(), 30000);
}
TEST(StringStreamRedirectionOutputTest, StartStopRestoresCorrectly)
{
PCWSTR expected = L"test";
for (int i = 0; i < 10; i++)
{
StringStreamRedirectionOutput redirectionOutput;
{
StandardStreamRedirection pManager(redirectionOutput, false);
wprintf(expected);
}
auto output = redirectionOutput.GetOutput();
ASSERT_FALSE(output.empty());
ASSERT_STREQ(output.c_str(), expected);
}
}
}

View File

@ -5,14 +5,14 @@
#include <filesystem>
#include <vector>
#include <string>
#include "hostfxr_utility.h"
#include "HostFxrResolver.h"
#include "Environment.h"
TEST(ParseHostFxrArguments, BasicHostFxrArguments)
{
std::vector<std::wstring> bstrArray;
HOSTFXR_UTILITY::AppendArguments(
HostFxrResolver::AppendArguments(
L"exec \"test.dll\"", // args
L"invalid", // physical path to application
bstrArray); // args array.
@ -26,7 +26,7 @@ TEST(ParseHostFxrArguments, NoExecProvided)
{
std::vector<std::wstring> bstrArray;
HOSTFXR_UTILITY::AppendArguments(
HostFxrResolver::AppendArguments(
L"test.dll", // args
L"ignored", // physical path to application
bstrArray); // args array.
@ -40,7 +40,7 @@ TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath)
std::vector<std::wstring> bstrArray;
// we need to use existing dll so let's use ntdll that we know exists everywhere
auto system32 = Environment::ExpandEnvironmentVariables(L"%WINDIR%\\System32");
HOSTFXR_UTILITY::AppendArguments(
HostFxrResolver::AppendArguments(
L"exec \"ntdll.dll\"", // args
system32, // physical path to application
bstrArray, // args array.
@ -57,7 +57,7 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
std::filesystem::path struHostFxrDllLocation;
std::filesystem::path struExeLocation;
EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
EXPECT_THROW(HostFxrResolver::GetHostFxrParameters(
L"dotnet", // processPath
L"some\\path", // application physical path, ignored.
L"", //arguments
@ -94,7 +94,7 @@ TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
fDotnetInProgramFiles = std::filesystem::is_regular_file(L"C:/Program Files (x86)/dotnet/dotnet.exe");
}
auto dotnetPath = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles();
auto dotnetPath = HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles();
if (fDotnetInProgramFiles)
{
EXPECT_TRUE(dotnetPath.has_value());
@ -111,7 +111,7 @@ TEST(GetHostFxrArguments, InvalidParams)
std::filesystem::path struHostFxrDllLocation;
std::filesystem::path struExeLocation;
EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
EXPECT_THROW(HostFxrResolver::GetHostFxrParameters(
L"bogus", // processPath
L"", // application physical path, ignored.
L"ignored", //arguments

View File

@ -42,7 +42,7 @@
#include "hashfn.h"
#include "requesthandler_config.h"
#include "hostfxr_utility.h"
#include "HostFxrResolver.h"
#include "config_utility.h"
#include "environmentvariablehash.h"
#include "iapplication.h"

View File

@ -5,8 +5,6 @@
#include "AppOfflineTrackingApplication.h"
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs
class InProcessApplicationBase : public AppOfflineTrackingApplication
{
public:

View File

@ -3,7 +3,7 @@
#include "inprocessapplication.h"
#include "inprocesshandler.h"
#include "hostfxroptions.h"
#include "HostFxrResolutionResult.h"
#include "requesthandler_config.h"
#include "environmentvariablehelpers.h"
#include "exceptions.h"
@ -12,6 +12,7 @@
#include "EventLog.h"
#include "ModuleHelpers.h"
#include "Environment.h"
#include "HostFxr.h"
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
@ -34,6 +35,8 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
{
m_dotnetExeKnownLocation = knownLocation;
}
m_stringRedirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
}
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
@ -170,45 +173,27 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
{
try
{
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
std::unique_ptr<HostFxrResolutionResult> hostFxrResolutionResult;
auto context = std::make_shared<ExecuteClrContext>();
auto pProc = s_fMainCallback;
if (pProc == nullptr)
if (s_fMainCallback == nullptr)
{
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(
THROW_IF_FAILED(HostFxrResolutionResult::Create(
m_dotnetExeKnownLocation,
m_pConfig->QueryProcessPath(),
QueryApplicationPhysicalPath(),
m_pConfig->QueryArguments(),
hostFxrOptions
hostFxrResolutionResult
));
hostFxrOptions->GetArguments(context->m_argc, context->m_argv);
hostFxrResolutionResult->GetArguments(context->m_argc, context->m_argv);
THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
context->m_hostFxr = HostFxr::CreateFromLoadedModule();
}
context->m_pProc = pProc;
if (m_pLoggerProvider == nullptr)
else
{
THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
m_pConfig->QueryStdoutLogEnabled(),
!m_pHttpServer.IsCommandLineLaunch(),
m_pConfig->QueryStdoutLogFile().c_str(),
QueryApplicationPhysicalPath().c_str(),
m_pLoggerProvider));
m_pLoggerProvider->TryStartRedirection();
context->m_hostFxr = HostFxr(s_fMainCallback, nullptr, nullptr);
}
// There can only ever be a single instance of .NET Core
@ -238,36 +223,52 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
LOG_INFOF(L"Setting current directory to %s", this->QueryApplicationPhysicalPath().c_str());
}
//Start CLR thread
m_clrThread = std::thread(ClrThreadEntryPoint, context);
bool clrThreadExited;
// 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());
auto redirectionOutput = LoggingHelpers::CreateOutputs(
m_pConfig->QueryStdoutLogEnabled(),
m_pConfig->QueryStdoutLogFile(),
QueryApplicationPhysicalPath(),
m_stringRedirectionOutput
);
StandardStreamRedirection redirection(*redirectionOutput.get(), m_pHttpServer.IsCommandLineLaunch());
context->m_redirectionOutput = redirectionOutput.get();
//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);
// Disconnect output
context->m_redirectionOutput = nullptr;
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
}
LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
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();
m_pLoggerProvider->TryStopRedirection();
if (m_fStopCalled)
{
if (clrThreadExited)
@ -390,7 +391,7 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& con
{
__try
{
auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get());
auto const exitCode = context->m_hostFxr.Main(context->m_argc, context->m_argv.get());
LOG_INFOF(L"Managed application exited with code %d", exitCode);
@ -412,13 +413,22 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& con
VOID
IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context)
{
HandleWrapper<ModuleHandleTraits> moduleHandle;
// 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);
// Nested block is required here because FreeLibraryAndExitThread would prevent destructors from running
// so we need to do in in a nested scope
{
// We use forwarder here instead of context->m_errorWriter itself to be able to
// disconnect listener before CLR exits
ForwardingRedirectionOutput redirectionForwarder(&context->m_redirectionOutput);
const auto redirect = context->m_hostFxr.RedirectOutput(&redirectionForwarder);
ExecuteClr(context);
}
FreeLibraryAndExitThread(moduleHandle.release(), 0);
}
@ -446,7 +456,7 @@ IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
VOID
IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
{
auto content = m_pLoggerProvider->GetStdOutContent();
auto content = m_stringRedirectionOutput->GetOutput();
if (context.m_exceptionCode != 0)
{

View File

@ -6,7 +6,7 @@
#include <thread>
#include "InProcessApplicationBase.h"
#include "InProcessOptions.h"
#include "BaseOutputManager.h"
#include "HostFxr.h"
class IN_PROCESS_HANDLER;
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext);
@ -115,16 +115,22 @@ private:
{
ExecuteClrContext():
m_argc(0),
m_pProc(nullptr),
m_hostFxr(nullptr, nullptr, nullptr),
m_exitCode(0),
m_exceptionCode(0)
{
}
DWORD m_argc;
std::unique_ptr<PCWSTR[]> m_argv;
hostfxr_main_fn m_pProc;
~ExecuteClrContext()
{
m_redirectionOutput = nullptr;
}
DWORD m_argc;
std::unique_ptr<PCWSTR[]> m_argv;
HostFxr m_hostFxr;
RedirectionOutput* m_redirectionOutput;
int m_exitCode;
int m_exceptionCode;
};
@ -159,7 +165,7 @@ private:
static IN_PROCESS_APPLICATION* s_Application;
std::unique_ptr<BaseOutputManager> m_pLoggerProvider;
std::shared_ptr<StringStreamRedirectionOutput> m_stringRedirectionOutput;
inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation";

View File

@ -3,7 +3,6 @@
#include "inprocesshandler.h"
#include "inprocessapplication.h"
#include "IOutputManager.h"
#include "ShuttingDownApplication.h"
#include "ntassert.h"

View File

@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer();
if (variant.HostingModel == HostingModel.InProcess)
{
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not start stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070003");
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not stop stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070002");
// Error is getting logged twice, from shim and handler
EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.CouldNotStartStdoutFileRedirection("Q:\\std", deploymentResult), allowMultiple: true);
}
}

View File

@ -15,12 +15,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
public class EventLogHelpers
{
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString)
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, bool allowMultiple = false)
{
Assert.True(deploymentResult.HostProcess.HasExited);
var entries = GetEntries(deploymentResult);
AssertSingleEntry(expectedRegexMatchString, entries);
AssertEntry(expectedRegexMatchString, entries, allowMultiple);
}
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, ILogger logger)
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
var entries = GetEntries(deploymentResult);
try
{
AssertSingleEntry(expectedRegexMatchString, entries);
AssertEntry(expectedRegexMatchString, entries);
}
catch (Exception ex)
{
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
var entries = GetEntries(deploymentResult).ToList();
foreach (var regexString in expectedRegexMatchString)
{
var matchedEntries = AssertSingleEntry(regexString, entries);
var matchedEntries = AssertEntry(regexString, entries);
foreach (var matchedEntry in matchedEntries)
{
@ -60,12 +60,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.True(0 == entries.Count, $"Some entries were not matched by any regex {FormatEntries(entries)}");
}
private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable<EventLogEntry> entries)
private static EventLogEntry[] AssertEntry(string regexString, IEnumerable<EventLogEntry> entries, bool allowMultiple = false)
{
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)}");
Assert.True(allowMultiple || matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
return matchedEntries;
}
@ -197,6 +197,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
return "Could not find the assembly '(.*)' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application.";
}
public static string CouldNotStartStdoutFileRedirection(string file, IISDeploymentResult deploymentResult)
{
return
$"Could not start stdout file redirection to '{Regex.Escape(file)}' with application base '{EscapedContentRoot(deploymentResult)}'.";
}
private static string EscapedContentRoot(IISDeploymentResult deploymentResult)
{
var contentRoot = deploymentResult.ContentRoot;

View File

@ -25,7 +25,6 @@ namespace IIS.FunctionalTests.Inprocess
}
[ConditionalFact]
[SkipIfDebug]
public async Task FrameworkNotFoundExceptionLogged_Pipe()
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true);
@ -44,7 +43,6 @@ namespace IIS.FunctionalTests.Inprocess
}
[ConditionalFact]
[SkipIfDebug]
public async Task FrameworkNotFoundExceptionLogged_File()
{
var deploymentParameters =

View File

@ -6,10 +6,9 @@ param($Mode)
Remove-Item "HKLM:\SOFTWARE\Microsoft\IIS Extensions\IIS AspNetCore Module V2\Parameters" -ErrorAction Ignore;
$DumpFolder = "$env:ASPNETCORE_TEST_LOG_DIR\dumps"
if (!($DumpFolder))
{
$DumpFolder = "$PSScriptRoot\..\artifacts\dumps"
$DumpFolder = "$PSScriptRoot\..\..\..\..\artifacts\dumps"
}
if (!(Test-Path $DumpFolder))
{