From 5299eff6160d8f9c86299f80430cedf0fbe52ff5 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 10 Jan 2019 14:07:47 -0800 Subject: [PATCH] Use hostfxr error callback support (#6043) --- .../AspNetCore/HandlerResolver.cpp | 136 +++++----- .../AspNetCore/HandlerResolver.h | 12 +- .../AspNetCore/applicationinfo.cpp | 2 +- .../AspNetCore/applicationinfo.h | 2 +- .../CommonLib/BaseOutputManager.h | 68 ----- .../CommonLib/CommonLib.vcxproj | 21 +- .../CommonLib/FileOutputManager.cpp | 178 ------------- .../CommonLib/FileOutputManager.h | 30 --- .../AspNetCoreModuleV2/CommonLib/HostFxr.cpp | 70 +++++ .../AspNetCoreModuleV2/CommonLib/HostFxr.h | 57 ++++ .../CommonLib/HostFxrResolutionResult.cpp | 77 ++++++ .../CommonLib/HostFxrResolutionResult.h | 53 ++++ ...ostfxr_utility.cpp => HostFxrResolver.cpp} | 20 +- .../{hostfxr_utility.h => HostFxrResolver.h} | 6 +- .../CommonLib/IOutputManager.h | 27 -- .../CommonLib/LoggingHelpers.cpp | 56 +--- .../CommonLib/LoggingHelpers.h | 17 +- .../CommonLib/ModuleHelpers.h | 26 +- .../CommonLib/NullOutputManager.h | 29 -- .../CommonLib/PipeOutputManager.h | 38 --- .../CommonLib/RedirectionOutput.cpp | 131 +++++++++ .../CommonLib/RedirectionOutput.h | 96 +++++++ ...ager.cpp => StandardStreamRedirection.cpp} | 81 ++---- .../CommonLib/StandardStreamRedirection.h | 66 +++++ .../CommonLib/StringHelpers.cpp | 11 + .../CommonLib/StringHelpers.h | 3 + .../AspNetCoreModuleV2/CommonLib/exceptions.h | 35 +++ .../IIS/AspNetCoreModuleV2/CommonLib/stdafx.h | 2 + .../CommonLibTests/CommonLibTests.vcxproj | 3 +- .../CommonLibTests/FileOutputManagerTests.cpp | 152 ----------- .../CommonLibTests/PipeOutputManagerTests.cpp | 162 ----------- .../StandardOutputRedirectionTest.cpp | 252 ++++++++++++++++++ .../CommonLibTests/hostfxr_utility_tests.cpp | 14 +- .../CommonLibTests/stdafx.h | 2 +- .../InProcessApplicationBase.h | 2 - .../inprocessapplication.cpp | 114 ++++---- .../inprocessapplication.h | 18 +- .../inprocesshandler.cpp | 1 - .../Common.FunctionalTests/LogFileTests.cs | 4 +- .../Utilities/EventLogHelpers.cs | 18 +- .../Inprocess/StdOutRedirectionTests.cs | 2 - .../IIS/tools/SetupTestEnvironment.ps1 | 3 +- 42 files changed, 1106 insertions(+), 991 deletions(-) delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.h create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.cpp create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.cpp create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.h rename src/Servers/IIS/AspNetCoreModuleV2/CommonLib/{hostfxr_utility.cpp => HostFxrResolver.cpp} (97%) rename src/Servers/IIS/AspNetCoreModuleV2/CommonLib/{hostfxr_utility.h => HostFxrResolver.h} (88%) delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/IOutputManager.h delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/NullOutputManager.h delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.cpp create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.h rename src/Servers/IIS/AspNetCoreModuleV2/CommonLib/{PipeOutputManager.cpp => StandardStreamRedirection.cpp} (69%) create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.h delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/FileOutputManagerTests.cpp delete mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/PipeOutputManagerTests.cpp create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/StandardOutputRedirectionTest.cpp diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index 44210d8b68..83527fbbdc 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -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 options; - std::unique_ptr outputManager; + std::unique_ptr 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(); - - 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 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(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 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 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() diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h index 1cd9029347..92efd74390 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h @@ -6,10 +6,10 @@ #include #include #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& 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 stringRedirectionOutput); HMODULE m_hModule; const IHttpServer &m_pServer; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 4ce20f893d..6c9f5d8482 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -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" diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index b40e0a8ba9..252d4e0d2d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -3,7 +3,7 @@ #pragma once -#include "hostfxroptions.h" +#include "HostFxrResolutionResult.h" #include "iapplication.h" #include "SRWSharedLock.h" #include "HandlerResolver.h" diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h deleted file mode 100644 index 7e0834584b..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h +++ /dev/null @@ -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 stdoutWrapper; - std::unique_ptr stderrWrapper; - - template - 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(); - } - } -}; - diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index e6bb6ab200..77ad69a397 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -199,7 +199,6 @@ - @@ -209,22 +208,21 @@ - - - + + + - + - - + @@ -246,14 +244,15 @@ - - - + + + - + + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp deleted file mode 100644 index 1b3772bb4f..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp +++ /dev/null @@ -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(stdout, STD_OUTPUT_HANDLE, m_hLogFileHandle, m_enableNativeRedirection); - stderrWrapper = std::make_unique(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; -} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.h deleted file mode 100644 index 435e07d4ff..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.h +++ /dev/null @@ -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 m_hLogFileHandle; - std::wstring m_stdOutLogFileName; - std::filesystem::path m_applicationPath; - std::filesystem::path m_logFilePath; -}; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.cpp new file mode 100644 index 0000000000..2959f94f49 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.cpp @@ -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(hModule, "hostfxr_main"), + ModuleHelpers::GetKnownProcAddress(hModule, "hostfxr_get_native_search_directories"), + ModuleHelpers::GetKnownProcAddress(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; + } +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h new file mode 100644 index 0000000000..e8951741d0 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h @@ -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; +}; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.cpp new file mode 100644 index 0000000000..269aec7660 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.cpp @@ -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& hostfxrArgv) const +{ + hostfxrArgc = static_cast(m_arguments.size()); + hostfxrArgv = std::make_unique(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& ppWrapper) +{ + std::filesystem::path knownDotnetLocation; + + if (!pcwzDotnetExePath.empty()) + { + knownDotnetLocation = pcwzDotnetExePath; + } + + try + { + std::filesystem::path hostFxrDllPath; + std::vector 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(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; +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.h new file mode 100644 index 0000000000..25b502fc15 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.h @@ -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 +#include +#include +#include +#include +#include + +class HostFxrResolutionResult +{ +public: + HostFxrResolutionResult( + std::filesystem::path dotnetExeLocation, + std::filesystem::path hostFxrLocation, + std::vector arguments + ) noexcept + : m_dotnetExeLocation(std::move(dotnetExeLocation)), + m_hostFxrLocation(std::move(hostFxrLocation)), + m_arguments(std::move(arguments)) + {} + + void + GetArguments(DWORD& hostfxrArgc, std::unique_ptr& 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& ppWrapper); + +private: + const std::filesystem::path m_dotnetExeLocation; + const std::filesystem::path m_hostFxrLocation; + const std::vector m_arguments; +}; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp similarity index 97% rename from src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp rename to src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp index f0be6e855b..58ba32b43e 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp @@ -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 #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 &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 -HOSTFXR_UTILITY::InvokeWhereToFindDotnet() +HostFxrResolver::InvokeWhereToFindDotnet() { HRESULT hr = S_OK; // Arguments to call where.exe @@ -480,14 +480,14 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() } std::optional -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 & vFolders ) { @@ -505,7 +505,7 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion( } VOID -HOSTFXR_UTILITY::FindDotNetFolders( +HostFxrResolver::FindDotNetFolders( const std::filesystem::path &path, _Out_ std::vector &pvFolders ) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h similarity index 88% rename from src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h rename to src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h index c015fadc88..1ff9ac8d14 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h @@ -9,12 +9,9 @@ #include #include -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 InvokeWhereToFindDotnet(); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/IOutputManager.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/IOutputManager.h deleted file mode 100644 index 4f19ad983d..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/IOutputManager.h +++ /dev/null @@ -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; -}; - diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp index 0a33c4c145..fc7c4ef126 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp @@ -3,55 +3,21 @@ #include "stdafx.h" #include "LoggingHelpers.h" -#include "FileOutputManager.h" -#include "PipeOutputManager.h" -#include "NullOutputManager.h" -#include "debugutil.h" -#include -#include -#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& outputManager -) +std::shared_ptr LoggingHelpers::CreateOutputs( + bool enableFileLogging, + std::wstring outputFileName, + std::wstring applicationPath, + std::shared_ptr stringStreamOutput) { - HRESULT hr = S_OK; - - DBG_ASSERT(outputManager != NULL); - - try + auto stdOutOutput = std::make_shared(); + std::shared_ptr 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(pwzStdOutFileName, pwzApplicationPath, fEnableNativeLogging); - outputManager = std::move(manager); - } - else if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &dummy)) - { - outputManager = std::make_unique(fEnableNativeLogging); - } - else - { - outputManager = std::make_unique(); - } - } - catch (std::bad_alloc&) - { - hr = E_OUTOFMEMORY; + fileOutput = std::make_shared(applicationPath, outputFileName); } - return hr; + return std::make_shared(std::move(fileOutput), std::move(stdOutOutput), std::move(stringStreamOutput)); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h index eff25b47d1..8fb98d62bc 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h @@ -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& outputManager + static std::shared_ptr + CreateOutputs( + bool enableFileLogging, + std::wstring outputFileName, + std::wstring applicationPath, + std::shared_ptr stringStreamOutput ); }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h index cab86d76ea..956bbf4c2a 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h @@ -14,7 +14,7 @@ public: void IncrementCurrentModuleRefCount(HandleWrapper &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 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(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 ) + } }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/NullOutputManager.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/NullOutputManager.h deleted file mode 100644 index d9c63b682d..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/NullOutputManager.h +++ /dev/null @@ -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""; - } -}; - diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h deleted file mode 100644 index 3a1a74cd43..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h +++ /dev/null @@ -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; -}; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.cpp new file mode 100644 index 0000000000..a3897954fa --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.cpp @@ -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 +#include "exceptions.h" +#include "EventLog.h" + +AggregateRedirectionOutput::AggregateRedirectionOutput(std::shared_ptr outputA, std::shared_ptr outputB, std::shared_ptr 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(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; +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.h new file mode 100644 index 0000000000..e400d4e102 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.h @@ -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 + +class RedirectionOutput +{ +public: + virtual ~RedirectionOutput() = default; + virtual void Append(const std::wstring& text) = 0; +}; + +class AggregateRedirectionOutput: NonCopyable, public RedirectionOutput +{ +public: + AggregateRedirectionOutput(std::shared_ptr outputA, std::shared_ptr outputB, std::shared_ptr outputC) noexcept(true); + + void Append(const std::wstring& text) override; + +private: + std::shared_ptr m_outputA; + std::shared_ptr m_outputB; + std::shared_ptr 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 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{}; +}; + + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.cpp similarity index 69% rename from src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp rename to src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.cpp index 8a9fe0612e..6629ea0d6d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.cpp @@ -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(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, m_enableNativeRedirection); - stderrWrapper = std::make_unique(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, m_enableNativeRedirection); + stdoutWrapper = std::make_unique(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, !m_commandLineLaunch); + stderrWrapper = std::make_unique(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(pContext); + auto pLoggingProvider = static_cast(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; - } - } } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.h new file mode 100644 index 0000000000..40250933f6 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.h @@ -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 stdoutWrapper; + std::unique_ptr stderrWrapper; + RedirectionOutput& m_output; +}; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp index c6ffbbf171..bff1dedd6b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp @@ -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; +} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.h index 5fa59fee55..285c9dfdbe 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.h @@ -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 [[nodiscard]] std::wstring format(const std::wstring& format, Args ... args) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/exceptions.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/exceptions.h index 5d2b59b463..35b4000f75 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/exceptions.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/exceptions.h @@ -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) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/stdafx.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/stdafx.h index b17ae43485..df7215895f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/stdafx.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -15,3 +15,5 @@ #include #include #include +#include +#include diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj index ed36bf66c0..5152c2ab11 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj @@ -50,13 +50,12 @@ - - + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/FileOutputManagerTests.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/FileOutputManagerTests.cpp deleted file mode 100644 index 66a9ff7f0b..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/FileOutputManagerTests.cpp +++ /dev/null @@ -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); - } - } - } -} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/PipeOutputManagerTests.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/PipeOutputManagerTests.cpp deleted file mode 100644 index 385d6df9e0..0000000000 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/PipeOutputManagerTests.cpp +++ /dev/null @@ -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!"); - } -} - diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/StandardOutputRedirectionTest.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/StandardOutputRedirectionTest.cpp new file mode 100644 index 0000000000..069f76b173 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/StandardOutputRedirectionTest.cpp @@ -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); + } + } +} + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/hostfxr_utility_tests.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/hostfxr_utility_tests.cpp index 01c9541429..31ddd86dc4 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/hostfxr_utility_tests.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/hostfxr_utility_tests.cpp @@ -5,14 +5,14 @@ #include #include #include -#include "hostfxr_utility.h" +#include "HostFxrResolver.h" #include "Environment.h" TEST(ParseHostFxrArguments, BasicHostFxrArguments) { std::vector 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 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 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 diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/stdafx.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/stdafx.h index 4b9ac7cd27..9cf10dc7c6 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/stdafx.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/stdafx.h @@ -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" diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h index 5bc2dd41e1..d8f141648c 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -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: diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 227795918b..0aee6b887d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -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(); } IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() @@ -170,45 +173,27 @@ IN_PROCESS_APPLICATION::ExecuteApplication() { try { - std::unique_ptr hostFxrOptions; + std::unique_ptr hostFxrResolutionResult; auto context = std::make_shared(); - 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(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& 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& con VOID IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr &context) { + HandleWrapper moduleHandle; + // Keep aspnetcorev2_inprocess.dll loaded while this thread is running // this is required because thread might be abandoned - HandleWrapper 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) { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 1086c8182e..61eb626822 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -6,7 +6,7 @@ #include #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 m_argv; - hostfxr_main_fn m_pProc; + ~ExecuteClrContext() + { + m_redirectionOutput = nullptr; + } + DWORD m_argc; + std::unique_ptr 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 m_pLoggerProvider; + std::shared_ptr m_stringRedirectionOutput; inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation"; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp index 8fee377a30..ec9e8f4f44 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp @@ -3,7 +3,6 @@ #include "inprocesshandler.h" #include "inprocessapplication.h" -#include "IOutputManager.h" #include "ShuttingDownApplication.h" #include "ntassert.h" diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs index f5f7a3ba3a..24d59746e4 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs @@ -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); } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs index 78c77fd2ca..9745d412ac 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs @@ -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 entries) + private static EventLogEntry[] AssertEntry(string regexString, IEnumerable 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; diff --git a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs index 35f10b13ab..83397ea876 100644 --- a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs @@ -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 = diff --git a/src/Servers/IIS/tools/SetupTestEnvironment.ps1 b/src/Servers/IIS/tools/SetupTestEnvironment.ps1 index 36b98a8bf7..4e3dde66ee 100644 --- a/src/Servers/IIS/tools/SetupTestEnvironment.ps1 +++ b/src/Servers/IIS/tools/SetupTestEnvironment.ps1 @@ -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)) {