diff --git a/.gitignore b/.gitignore index 74f65a6ecf..4e21547710 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ project.lock.json *.tlog *.CppClean.log *msbuild.log +gtest.log src/*/*/Debug/ src/*/*/x64/Debug/ diff --git a/IISIntegration.sln b/IISIntegration.sln index 96aa2b6a92..5246def8ed 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -115,6 +115,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InProcessRequestHandler", " EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OutOfProcessRequestHandler", "src\AspNetCoreModuleV2\OutOfProcessRequestHandler\OutOfProcessRequestHandler.vcxproj", "{7F87406C-A3C8-4139-A68D-E4C344294A67}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartupExceptionWebSite", "test\WebSites\StartupExceptionWebSite\StartupExceptionWebSite.csproj", "{340C59FC-C682-4CBA-81F8-791821EC8EDE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -359,6 +361,16 @@ Global {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.Build.0 = Release|x64 {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.ActiveCfg = Release|Win32 {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.Build.0 = Release|Win32 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Debug|Any CPU.ActiveCfg = Debug|x86 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Debug|x64.ActiveCfg = Debug|x64 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Debug|x64.Build.0 = Debug|x64 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Debug|x86.ActiveCfg = Debug|x86 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Debug|x86.Build.0 = Debug|x86 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Release|Any CPU.ActiveCfg = Release|x86 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Release|x64.ActiveCfg = Release|x64 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Release|x64.Build.0 = Release|x64 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Release|x86.ActiveCfg = Release|x86 + {340C59FC-C682-4CBA-81F8-791821EC8EDE}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -388,6 +400,7 @@ Global {48F46909-E76A-4788-BCE1-E543C0E140FE} = {622D35C9-627B-466E-8D15-752968CC79AF} {D57EA297-6DC2-4BC0-8C91-334863327863} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} {7F87406C-A3C8-4139-A68D-E4C344294A67} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {340C59FC-C682-4CBA-81F8-791821EC8EDE} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 32ec0aaedb..42079abe33 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -1,238 +1,247 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {55494E58-E061-4C4C-A0A8-837008E72F85} - Win32Proj - NewCommon - 10.0.15063.0 - - - - StaticLibrary - true - v141 - Unicode - - - StaticLibrary - false - v141 - true - Unicode - - - StaticLibrary - true - v141 - Unicode - - - StaticLibrary - false - v141 - false - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - C:\AspNetCoreModule\src\IISLib;$(IncludePath) - - - - Use - Level4 - true - Disabled - false - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - true - MultiThreadedDebug - false - ProgramDatabase - ..\iislib; - true - stdcpp17 - - - Windows - true - - - - - Use - Level4 - true - Disabled - false - _DEBUG;_LIB;%(PreprocessorDefinitions) - true - ProgramDatabase - false - MultiThreadedDebug - false - ..\iislib; - true - stdcpp17 - - - Windows - true - - - - - Use - Level4 - true - MaxSpeed - true - true - false - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - true - MultiThreaded - false - ..\iislib; - true - stdcpp17 - - - Windows - true - true - true - - - - - Use - Level4 - true - MaxSpeed - true - true - false - NDEBUG;_LIB;%(PreprocessorDefinitions) - true - ..\iislib; - - - MultiThreaded - false - true - stdcpp17 - - - Windows - true - true - true - - - ..\iislib - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - - - - {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} - - - - - Document - mc %(FullPath) - Compiling Event Messages ... - %(Filename).rc;%(Filename).h;MSG0409.bin - mc %(FullPath) - Compiling Event Messages ... - %(Filename).rc;%(Filename).h;MSG0409.bin - mc %(FullPath) - Compiling Event Messages ... - %(Filename).rc;%(Filename).h;MSG0409.bin - mc %(FullPath) - Compiling Event Messages ... - %(Filename).rc;%(Filename).h;MSG0409.bin - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {55494E58-E061-4C4C-A0A8-837008E72F85} + Win32Proj + NewCommon + 10.0.15063.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + false + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + C:\AspNetCoreModule\src\IISLib;$(IncludePath) + + + + Use + Level4 + true + Disabled + false + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreadedDebug + false + ProgramDatabase + ..\iislib; + true + stdcpp17 + + + Windows + true + + + + + Use + Level4 + true + Disabled + false + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + false + MultiThreadedDebug + false + ..\iislib; + true + stdcpp17 + + + Windows + true + + + + + Use + Level4 + true + MaxSpeed + true + true + false + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + false + ..\iislib; + true + stdcpp17 + + + Windows + true + true + true + + + + + Use + Level4 + true + MaxSpeed + true + true + false + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + ..\iislib; + true + + + MultiThreaded + false + stdcpp17 + + + Windows + true + true + true + + + ..\iislib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + Document + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + + + + + \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp b/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp new file mode 100644 index 0000000000..9f9ec1e75c --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp @@ -0,0 +1,184 @@ +// 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" + + +FileOutputManager::FileOutputManager() + : m_pStdOutFile(NULL) +{ +} + +FileOutputManager::~FileOutputManager() +{ + HANDLE handle = NULL; + WIN32_FIND_DATA fileData; + + if (m_pStdOutFile != NULL) + { + m_Timer.CancelTimer(); + _close(_fileno(m_pStdOutFile)); + m_pStdOutFile = NULL; + } + + // delete empty log file + handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData); + if (handle != INVALID_HANDLE_VALUE && + fileData.nFileSizeHigh == 0 && + fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh + { + FindClose(handle); + // no need to check whether the deletion succeeds + // as nothing can be done + DeleteFile(m_struLogFilePath.QueryStr()); + } + + _dup2(m_fdStdOut, _fileno(stdout)); + _dup2(m_fdStdErr, _fileno(stderr)); +} + +HRESULT +FileOutputManager::Initialize(PCWSTR pwzStdOutLogFileName, PCWSTR pwzApplicationPath) +{ + HRESULT hr = S_OK; + + if (SUCCEEDED(hr = m_wsApplicationPath.Copy(pwzApplicationPath))) + { + hr = m_wsStdOutLogFileName.Copy(pwzStdOutLogFileName); + } + + return hr; +} + +void FileOutputManager::NotifyStartupComplete() +{ +} + +bool FileOutputManager::GetStdOutContent(STRA* struStdOutput) +{ + // + // Ungraceful shutdown, try to log an error message. + // This will be a common place for errors as it means the hostfxr_main returned + // or there was an exception. + // + CHAR pzFileContents[4096] = { 0 }; + DWORD dwNumBytesRead; + LARGE_INTEGER li = { 0 }; + BOOL fLogged = FALSE; + DWORD dwFilePointer = 0; + + if (m_hLogFileHandle != INVALID_HANDLE_VALUE) + { + if (GetFileSizeEx(m_hLogFileHandle, &li) && li.LowPart > 0 && li.HighPart == 0) + { + if (li.LowPart > 4096) + { + dwFilePointer = SetFilePointer(m_hLogFileHandle, -4096, NULL, FILE_END); + } + else + { + dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN); + } + + if (dwFilePointer != INVALID_SET_FILE_POINTER) + { + // Read file fails. + if (ReadFile(m_hLogFileHandle, pzFileContents, 4096, &dwNumBytesRead, NULL)) + { + if (SUCCEEDED(struStdOutput->Copy(pzFileContents, dwNumBytesRead))) + { + fLogged = TRUE; + } + } + } + } + } + + return fLogged; +} + +HRESULT +FileOutputManager::Start() +{ + HRESULT hr; + SYSTEMTIME systemTime; + SECURITY_ATTRIBUTES saAttr = { 0 }; + STRU struPath; + + hr = UTILITY::ConvertPathToFullPath( + m_wsStdOutLogFileName.QueryStr(), + m_wsApplicationPath.QueryStr(), + &struPath); + + if (FAILED(hr)) + { + goto Finished; + } + + hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); + if (FAILED(hr)) + { + goto Finished; + } + + m_fdStdOut = _dup(_fileno(stdout)); + if (m_fdStdOut == -1) + { + hr = E_HANDLE; + goto Finished; + } + + m_fdStdErr = _dup(_fileno(stderr)); + if (m_fdStdErr == -1) + { + hr = E_HANDLE; + goto Finished; + } + + GetSystemTime(&systemTime); + hr = m_struLogFilePath.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", + struPath.QueryStr(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond, + GetCurrentProcessId()); + if (FAILED(hr)) + { + goto Finished; + } + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (_wfreopen_s(&m_pStdOutFile, m_struLogFilePath.QueryStr(), L"w+", stdout) == 0) + { + setvbuf(m_pStdOutFile, NULL, _IONBF, 0); + if (_dup2(_fileno(m_pStdOutFile), _fileno(stdout)) == -1) + { + hr = E_HANDLE; + goto Finished; + } + if (_dup2(_fileno(m_pStdOutFile), _fileno(stderr)) == -1) + { + hr = E_HANDLE; + goto Finished; + } + } + + m_hLogFileHandle = (HANDLE)_get_osfhandle(_fileno(m_pStdOutFile)); + if (m_hLogFileHandle == INVALID_HANDLE_VALUE) + { + hr = E_HANDLE; + goto Finished; + } + + // Periodically flush the log content to file + m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struLogFilePath, FILE_FLUSH_TIMEOUT, FILE_FLUSH_TIMEOUT); + +Finished: + + return hr; +} diff --git a/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.h b/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.h new file mode 100644 index 0000000000..c4f087161c --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.h @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + + +class FileOutputManager : public IOutputManager +{ + #define FILE_FLUSH_TIMEOUT 3000 +public: + FileOutputManager(); + ~FileOutputManager(); + + HRESULT + Initialize(PCWSTR pwzStdOutLogFileName, PCWSTR pwzApplciationpath); + + virtual bool GetStdOutContent(STRA* struStdOutput) override; + virtual HRESULT Start() override; + virtual void NotifyStartupComplete() override; + +private: + HANDLE m_hLogFileHandle; + STTIMER m_Timer; + STRU m_wsStdOutLogFileName; + STRU m_wsApplicationPath; + STRU m_struLogFilePath; + int m_fdStdOut; + int m_fdStdErr; + FILE* m_pStdOutFile; +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/IOutputManager.h b/src/AspNetCoreModuleV2/CommonLib/IOutputManager.h new file mode 100644 index 0000000000..386e6d1a4f --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/IOutputManager.h @@ -0,0 +1,25 @@ +// 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" + +#pragma once +class IOutputManager +{ +public: + virtual + HRESULT + Start() = 0; + + virtual + ~IOutputManager() {}; + + virtual + void + NotifyStartupComplete() = 0; + + virtual + bool + GetStdOutContent(STRA* struStdOutput) = 0; +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp b/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp new file mode 100644 index 0000000000..dbb6f71474 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp @@ -0,0 +1,35 @@ +// 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" + +HRESULT +LoggingHelpers::CreateLoggingProvider( + bool fIsLoggingEnabled, + bool fEnablePipe, + PCWSTR pwzStdOutFileName, + PCWSTR pwzApplicationPath, + _Out_ IOutputManager** outputManager +) +{ + HRESULT hr = S_OK; + + DBG_ASSERT(outputManager != NULL); + + if (fIsLoggingEnabled) + { + FileOutputManager* manager = new FileOutputManager; + hr = manager->Initialize(pwzStdOutFileName, pwzApplicationPath); + *outputManager = manager; + } + else if (fEnablePipe) + { + *outputManager = new PipeOutputManager; + } + else + { + *outputManager = new NullOutputManager; + } + + return hr; +} diff --git a/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h b/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h new file mode 100644 index 0000000000..fb3f55954f --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class LoggingHelpers +{ +public: + + static + HRESULT + CreateLoggingProvider( + bool fLoggingEnabled, + bool fEnablePipe, + PCWSTR pwzStdOutFileName, + PCWSTR pwzApplicationPath, + _Out_ IOutputManager** outputManager + ); +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/NullOutputManager.h b/src/AspNetCoreModuleV2/CommonLib/NullOutputManager.h new file mode 100644 index 0000000000..e2cec68179 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/NullOutputManager.h @@ -0,0 +1,31 @@ +// 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 IOutputManager +{ +public: + + NullOutputManager() = default; + + ~NullOutputManager() = default; + + HRESULT Start() + { + // The process has console, e.g., IIS Express scenario + return S_OK; + } + + void NotifyStartupComplete() + { + } + + bool GetStdOutContent(STRA*) + { + return false; + } +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp b/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp new file mode 100644 index 0000000000..f5e6acc782 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp @@ -0,0 +1,196 @@ +// 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" + +PipeOutputManager::PipeOutputManager() : + m_dwStdErrReadTotal(0), + m_hErrReadPipe(INVALID_HANDLE_VALUE), + m_hErrWritePipe(INVALID_HANDLE_VALUE), + m_hErrThread(INVALID_HANDLE_VALUE), + m_fDisposed(FALSE) +{ +} + +PipeOutputManager::~PipeOutputManager() +{ + StopOutputRedirection(); +} + +VOID +PipeOutputManager::StopOutputRedirection() +{ + DWORD dwThreadStatus = 0; + STRA straStdOutput; + + if (m_fDisposed) + { + return; + } + m_fDisposed = true; + + fflush(stdout); + fflush(stderr); + + if (m_hErrWritePipe != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hErrWritePipe); + m_hErrWritePipe = INVALID_HANDLE_VALUE; + } + + if (m_hErrThread != NULL && + GetExitCodeThread(m_hErrThread, &dwThreadStatus) != 0 && + dwThreadStatus == STILL_ACTIVE) + { + // wait for gracefullshut down, i.e., the exit of the background thread or timeout + if (WaitForSingleObject(m_hErrThread, PIPE_OUTPUT_THREAD_TIMEOUT) != WAIT_OBJECT_0) + { + // if the thread is still running, we need kill it first before exit to avoid AV + if (GetExitCodeThread(m_hErrThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE) + { + TerminateThread(m_hErrThread, STATUS_CONTROL_C_EXIT); + } + } + } + + CloseHandle(m_hErrThread); + m_hErrThread = NULL; + + if (m_hErrReadPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hErrReadPipe); + m_hErrReadPipe = INVALID_HANDLE_VALUE; + } + + // Restore the original stdout and stderr handles of the process, + // as the application has either finished startup or has exited. + if (_dup2(m_fdStdOut, _fileno(stdout)) == -1) + { + return; + } + + if (_dup2(m_fdStdErr, _fileno(stderr)) == -1) + { + return; + } + + if (GetStdOutContent(&straStdOutput)) + { + printf(straStdOutput.QueryStr()); + // Need to flush contents. + _flushall(); + } +} + +HRESULT PipeOutputManager::Start() +{ + HRESULT hr = S_OK; + SECURITY_ATTRIBUTES saAttr = { 0 }; + HANDLE hStdErrReadPipe; + HANDLE hStdErrWritePipe; + + m_fdStdOut = _dup(_fileno(stdout)); + if (m_fdStdOut == -1) + { + hr = E_HANDLE; + goto Finished; + } + m_fdStdErr = _dup(_fileno(stderr)); + if (m_fdStdErr == -1) + { + hr = E_HANDLE; + goto Finished; + } + + if (!CreatePipe(&hStdErrReadPipe, &hStdErrWritePipe, &saAttr, 0 /*nSize*/)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // TODO this still doesn't redirect calls in native, like wprintf + if (!SetStdHandle(STD_ERROR_HANDLE, hStdErrWritePipe)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!SetStdHandle(STD_OUTPUT_HANDLE, hStdErrWritePipe)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_hErrReadPipe = hStdErrReadPipe; + m_hErrWritePipe = hStdErrWritePipe; + + // Read the stderr handle on a separate thread until we get 4096 bytes. + m_hErrThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)ReadStdErrHandle, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (m_hErrThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + return hr; +} + +VOID +PipeOutputManager::ReadStdErrHandle( + LPVOID pContext +) +{ + PipeOutputManager *pLoggingProvider = (PipeOutputManager*)pContext; + DBG_ASSERT(pLoggingProvider != NULL); + pLoggingProvider->ReadStdErrHandleInternal(); +} + +bool PipeOutputManager::GetStdOutContent(STRA* struStdOutput) +{ + bool fLogged = false; + if (m_dwStdErrReadTotal > 0) + { + if (SUCCEEDED(struStdOutput->Copy(m_pzFileContents, m_dwStdErrReadTotal))) + { + fLogged = TRUE; + } + } + return fLogged; +} + +VOID +PipeOutputManager::ReadStdErrHandleInternal( + VOID +) +{ + DWORD dwNumBytesRead = 0; + while (true) + { + if (ReadFile(m_hErrReadPipe, &m_pzFileContents[m_dwStdErrReadTotal], MAX_PIPE_READ_SIZE - m_dwStdErrReadTotal, &dwNumBytesRead, NULL)) + { + m_dwStdErrReadTotal += dwNumBytesRead; + if (m_dwStdErrReadTotal >= MAX_PIPE_READ_SIZE) + { + break; + } + } + else if (GetLastError() == ERROR_BROKEN_PIPE) + { + break; + } + } +} + +VOID +PipeOutputManager::NotifyStartupComplete() +{ + StopOutputRedirection(); +} diff --git a/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h b/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h new file mode 100644 index 0000000000..fb4e19a449 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class PipeOutputManager : public IOutputManager +{ + #define PIPE_OUTPUT_THREAD_TIMEOUT 2000 + #define MAX_PIPE_READ_SIZE 4096 +public: + PipeOutputManager(); + ~PipeOutputManager(); + + // Inherited via ILoggerProvider + virtual HRESULT Start() override; + virtual void NotifyStartupComplete() override; + + // Inherited via IOutputManager + virtual bool GetStdOutContent(STRA* struStdOutput) override; + + VOID ReadStdErrHandleInternal(VOID); + + static + VOID ReadStdErrHandle(LPVOID pContext); + + VOID StopOutputRedirection(); +private: + HANDLE m_hErrReadPipe; + HANDLE m_hErrWritePipe; + STRU m_struLogFilePath; + HANDLE m_hErrThread; + CHAR m_pzFileContents[MAX_PIPE_READ_SIZE] = { 0 }; + BOOL m_fDisposed; + DWORD m_dwStdErrReadTotal; + int m_fdStdOut; + int m_fdStdErr; +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp b/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp index 3fd0be51f4..52ecf57d07 100644 --- a/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp @@ -1,3 +1,6 @@ +// 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 "SRWLockWrapper.h" diff --git a/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h b/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h index 2ae57cb2f8..e8c112a2d5 100644 --- a/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h +++ b/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h @@ -1,4 +1,8 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + #pragma once + class SRWLockWrapper { public: diff --git a/src/AspNetCoreModuleV2/CommonLib/stdafx.h b/src/AspNetCoreModuleV2/CommonLib/stdafx.h index 3191f9376b..8e5563da25 100644 --- a/src/AspNetCoreModuleV2/CommonLib/stdafx.h +++ b/src/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -15,6 +15,7 @@ #include #include #include "Shlwapi.h" +#include #include "hashtable.h" #include "stringu.h" #include "stringa.h" @@ -23,6 +24,7 @@ #include "ahutil.h" #include "hashfn.h" #include "irequesthandler.h" +#include "sttimer.h" #include "requesthandler.h" #include "iapplication.h" #include "application.h" @@ -34,3 +36,8 @@ #include "fx_ver.h" #include "hostfxr_utility.h" #include "hostfxroptions.h" +#include "IOutputManager.h" +#include "FileOutputManager.h" +#include "PipeOutputManager.h" +#include "NullOutputManager.h" +#include "LoggingHelpers.h" diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj index 8de752cea8..db22833452 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj @@ -214,7 +214,6 @@ - diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.cpp index 4551c177b3..81ec85fab3 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.cpp @@ -10,11 +10,6 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( PCWSTR pDotnetExeLocation) : m_pHttpServer(pHttpServer), m_ProcessExitCode(0), - m_hLogFileHandle(INVALID_HANDLE_VALUE), - m_hErrReadPipe(INVALID_HANDLE_VALUE), - m_hErrWritePipe(INVALID_HANDLE_VALUE), - m_dwStdErrReadTotal(0), - m_fDoneStdRedirect(FALSE), m_fBlockCallbacksIntoManaged(FALSE), m_fInitialized(FALSE), m_fShutdownCalledFromNative(FALSE), @@ -35,12 +30,6 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() { - if (m_hLogFileHandle != INVALID_HANDLE_VALUE) - { - m_Timer.CancelTimer(); - CloseHandle(m_hLogFileHandle); - m_hLogFileHandle = INVALID_HANDLE_VALUE; - } if (m_pConfig != NULL) { @@ -106,6 +95,12 @@ IN_PROCESS_APPLICATION::ShutDown( } } + if (m_pLoggerProvider != NULL) + { + delete m_pLoggerProvider; + m_pLoggerProvider = NULL; + } + Finished: if (FAILED(hr)) @@ -131,8 +126,6 @@ IN_PROCESS_APPLICATION::ShutDownInternal() { DWORD dwThreadStatus = 0; DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); - HANDLE handle = NULL; - WIN32_FIND_DATA fileData; if (IsDebuggerPresent()) { @@ -201,34 +194,6 @@ IN_PROCESS_APPLICATION::ShutDownInternal() CloseHandle(m_hThread); m_hThread = NULL; s_Application = NULL; - - CloseStdErrHandles(); - - if (m_pStdFile != NULL) - { - fflush(stdout); - fflush(stderr); - fclose(m_pStdFile); - } - - if (m_hLogFileHandle != INVALID_HANDLE_VALUE) - { - m_Timer.CancelTimer(); - CloseHandle(m_hLogFileHandle); - m_hLogFileHandle = INVALID_HANDLE_VALUE; - } - - // delete empty log file - handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData); - if (handle != INVALID_HANDLE_VALUE && - fileData.nFileSizeHigh == 0 && - fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh - { - FindClose(handle); - // no need to check whether the deletion succeeds - // as nothing can be done - DeleteFile(m_struLogFilePath.QueryStr()); - } } __override @@ -371,263 +336,13 @@ IN_PROCESS_APPLICATION::SetCallbackHandles( m_ShutdownHandlerContext = pvShutdownHandlerContext; m_AsyncCompletionHandler = async_completion_handler; - CloseStdErrHandles(); + m_pLoggerProvider->NotifyStartupComplete(); // Can't check the std err handle as it isn't a critical error - SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); // Initialization complete SetEvent(m_pInitalizeEvent); m_fInitialized = TRUE; } -VOID -IN_PROCESS_APPLICATION::SetStdOut( - VOID -) -{ - HRESULT hr = S_OK; - STRU struPath; - SYSTEMTIME systemTime; - SECURITY_ATTRIBUTES saAttr = { 0 }; - HANDLE hStdErrReadPipe; - HANDLE hStdErrWritePipe; - - if (!m_fDoneStdRedirect) - { - // Have not set stdout yet, redirect stdout to log file - SRWLockWrapper lock(m_srwLock); - if (!m_fDoneStdRedirect) - { - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - // - // best effort - // no need to capture the error code as nothing we can do here - // in case mamanged layer exits abnormally, may not be able to capture the log content as it is buffered. - // - if (!GetConsoleWindow()) - { - // Full IIS scenario. - - // - // SetStdHandle works as w3wp does not have Console - // Current process does not have a console - // - if (m_pConfig->QueryStdoutLogEnabled()) - { - hr = UTILITY::ConvertPathToFullPath( - m_pConfig->QueryStdoutLogFile()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - &struPath); - if (FAILED(hr)) - { - goto Finished; - } - - hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); - if (FAILED(hr)) - { - goto Finished; - } - - GetSystemTime(&systemTime); - hr = m_struLogFilePath.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", - struPath.QueryStr(), - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond, - GetCurrentProcessId()); - if (FAILED(hr)) - { - goto Finished; - } - - m_hLogFileHandle = CreateFileW(m_struLogFilePath.QueryStr(), - FILE_READ_DATA | FILE_WRITE_DATA, - FILE_SHARE_READ, - &saAttr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (m_hLogFileHandle == INVALID_HANDLE_VALUE) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (!SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (!SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - // not work - // AllocConsole() does not help - // *stdout = *m_pStdFile; - // *stderr = *m_pStdFile; - // _dup2(_fileno(m_pStdFile), _fileno(stdout)); - // _dup2(_fileno(m_pStdFile), _fileno(stderr)); - // this one cannot capture the process start failure - // _wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout); - - // Periodically flush the log content to file - m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struLogFilePath, 3000, 3000); - } - else - { - // - // CreatePipe for outputting stderr to the windows event log. - // Ignore failures - // - if (!CreatePipe(&hStdErrReadPipe, &hStdErrWritePipe, &saAttr, 0 /*nSize*/)) - { - goto Finished; - } - - if (!SetStdHandle(STD_ERROR_HANDLE, hStdErrWritePipe)) - { - goto Finished; - } - - m_hErrReadPipe = hStdErrReadPipe; - m_hErrWritePipe = hStdErrWritePipe; - - // Read the stderr handle on a separate thread until we get 4096 bytes. - m_hErrThread = CreateThread( - NULL, // default security attributes - 0, // default stack size - (LPTHREAD_START_ROUTINE)ReadStdErrHandle, - this, // thread function arguments - 0, // default creation flags - NULL); // receive thread identifier - - if (m_hErrThread == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - } - } - else - { - // The process has console, e.g., IIS Express scenario - - if (_wfopen_s(&m_pStdFile, m_struLogFilePath.QueryStr(), L"w") == 0) - { - // known issue: error info may not be capture when process crashes during buffering - // even we disabled FILE buffering - setvbuf(m_pStdFile, NULL, _IONBF, 0); - _dup2(_fileno(m_pStdFile), _fileno(stdout)); - _dup2(_fileno(m_pStdFile), _fileno(stderr)); - } - // These don't work for console scenario - // close and AllocConsole does not help - //_wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout); - // SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle); - // SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle); - //*stdout = *m_pStdFile; - //*stderr = *m_pStdFile; - } - } - } - -Finished: - m_fDoneStdRedirect = TRUE; - if (FAILED(hr) && m_pConfig->QueryStdoutLogEnabled()) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_WARNING_TYPE, - ASPNETCORE_EVENT_CONFIG_ERROR, - ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, - m_struLogFilePath.QueryStr(), - hr); - } -} - -VOID -IN_PROCESS_APPLICATION::ReadStdErrHandle( - LPVOID pContext -) -{ - IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext; - DBG_ASSERT(pApplication != NULL); - pApplication->ReadStdErrHandleInternal(); -} - -VOID -IN_PROCESS_APPLICATION::ReadStdErrHandleInternal( - VOID -) -{ - DWORD dwNumBytesRead = 0; - while (true) - { - if (ReadFile(m_hErrReadPipe, &m_pzFileContents[m_dwStdErrReadTotal], 4096 - m_dwStdErrReadTotal, &dwNumBytesRead, NULL)) - { - m_dwStdErrReadTotal += dwNumBytesRead; - if (m_dwStdErrReadTotal >= 4096) - { - break; - } - } - else if (GetLastError() == ERROR_BROKEN_PIPE) - { - break; - } - } -} - -VOID -IN_PROCESS_APPLICATION::CloseStdErrHandles -( - VOID -) -{ - DWORD dwThreadStatus = 0; - DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); - // Close Handles for stderr as we only care about capturing startup errors - if (m_hErrWritePipe != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hErrWritePipe); - m_hErrWritePipe = INVALID_HANDLE_VALUE; - } - - if (m_hErrThread != NULL && - GetExitCodeThread(m_hErrThread, &dwThreadStatus) != 0 && - dwThreadStatus == STILL_ACTIVE) - { - // wait for gracefullshut down, i.e., the exit of the background thread or timeout - if (WaitForSingleObject(m_hErrThread, dwTimeout) != WAIT_OBJECT_0) - { - // if the thread is still running, we need kill it first before exit to avoid AV - if (GetExitCodeThread(m_hErrThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE) - { - TerminateThread(m_hErrThread, STATUS_CONTROL_C_EXIT); - } - } - } - - CloseHandle(m_hErrThread); - m_hErrThread = NULL; - - if (m_hErrReadPipe != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hErrReadPipe); - m_hErrReadPipe = INVALID_HANDLE_VALUE; - } -} - // Will be called by the inprocesshandler HRESULT IN_PROCESS_APPLICATION::LoadManagedApplication @@ -657,11 +372,27 @@ IN_PROCESS_APPLICATION::LoadManagedApplication goto Finished; } - // Set up stdout redirect - SetStdOut(); - { + // Set up stdout redirect + SRWLockWrapper lock(m_srwLock); + + if (m_pLoggerProvider == NULL) + { + hr = LoggingHelpers::CreateLoggingProvider( + m_pConfig->QueryStdoutLogEnabled(), + !GetConsoleWindow(), + m_pConfig->QueryStdoutLogFile()->QueryStr(), + m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + &m_pLoggerProvider); + if (FAILED(hr)) + { + goto Finished; + } + + m_pLoggerProvider->Start(); + } + if (m_status != APPLICATION_STATUS::STARTING) { if (m_status == APPLICATION_STATUS::FAIL) @@ -887,6 +618,8 @@ Finished: if (!m_fShutdownCalledFromNative) { + m_pLoggerProvider->NotifyStartupComplete(); + LogErrorsOnMainExit(hr); if (m_fInitialized) { @@ -910,72 +643,20 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit( // This will be a common place for errors as it means the hostfxr_main returned // or there was an exception. // - CHAR pzFileContents[4096] = { 0 }; - DWORD dwNumBytesRead; - STRU struStdErrLog; - LARGE_INTEGER li = { 0 }; - BOOL fLogged = FALSE; - DWORD dwFilePointer = 0; - - if (m_pConfig->QueryStdoutLogEnabled()) + STRA struStdErrOutput; + if (m_pLoggerProvider->GetStdOutContent(&struStdErrOutput)) { - // Put stdout/stderr logs into - if (m_hLogFileHandle != INVALID_HANDLE_VALUE) - { - if (GetFileSizeEx(m_hLogFileHandle, &li) && li.LowPart > 0 && li.HighPart == 0) - { - if (li.LowPart > 4096) - { - dwFilePointer = SetFilePointer(m_hLogFileHandle, -4096, NULL, FILE_END); - } - else - { - dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN); - } - if (dwFilePointer != INVALID_SET_FILE_POINTER) - { - if (ReadFile(m_hLogFileHandle, pzFileContents, 4096, &dwNumBytesRead, NULL)) - { - if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal))) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - hr, - struStdErrLog.QueryStr()); - fLogged = TRUE; - - } - } - } - } - } + UTILITY::LogEventF(g_hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG, + m_pConfig->QueryApplicationPath()->QueryStr(), + m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + hr, + struStdErrOutput.QueryStr()); } else { - if (m_dwStdErrReadTotal > 0) - { - if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal))) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDERR_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - hr, - struStdErrLog.QueryStr()); - fLogged = TRUE; - } - } - } - - if (!fLogged) - { - // If we didn't log, log the generic message. UTILITY::LogEventF(g_hEventLog, EVENTLOG_ERROR_TYPE, ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, @@ -983,41 +664,34 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit( m_pConfig->QueryApplicationPath()->QueryStr(), m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), hr); - fLogged = TRUE; } } // // Calls hostfxr_main with the hostfxr and application as arguments. -// Method should be called with only -// Need to have __try / __except in methods that require unwinding. -// Note, this will not // HRESULT IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hostfxr_main_fn pProc) { HRESULT hr = S_OK; + __try { m_ProcessExitCode = pProc(argc, argv); + if (m_ProcessExitCode != 0) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } } - __except (FilterException(GetExceptionCode(), GetExceptionInformation())) + __except(GetExceptionCode() != 0) { - // TODO Log error message here. - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; + hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } // static -INT -IN_PROCESS_APPLICATION::FilterException(unsigned int, struct _EXCEPTION_POINTERS*) -{ - // We assume that any exception is a failure as the applicaiton didn't start or there was a startup error. - // TODO, log error based on exception code. - return EXCEPTION_EXECUTE_HANDLER; -} REQUESTHANDLER_CONFIG* IN_PROCESS_APPLICATION::QueryConfig() const diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.h index e72d921e1d..c9d0285369 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocess/inprocessapplication.h @@ -55,16 +55,6 @@ public: VOID ); - VOID - ReadStdErrHandleInternal( - VOID - ); - - VOID - CloseStdErrHandles( - VOID - ); - HRESULT LoadManagedApplication( VOID @@ -130,17 +120,6 @@ public: } private: - static - DWORD - DoShutDown( - LPVOID lpParam - ); - - VOID - ShutDownInternal( - VOID - ); - IHttpServer* const m_pHttpServer; // Thread executing the .NET Core process @@ -159,26 +138,18 @@ private: // The event that gets triggered when managed initialization is complete HANDLE m_pInitalizeEvent; - // The std log file handle - HANDLE m_hLogFileHandle; - HANDLE m_hErrReadPipe; - HANDLE m_hErrWritePipe; - STRU m_struLogFilePath; STRU m_struExeLocation; // The exit code of the .NET Core process INT m_ProcessExitCode; BOOL m_fIsWebSocketsConnection; - BOOL m_fDoneStdRedirect; volatile BOOL m_fBlockCallbacksIntoManaged; volatile BOOL m_fShutdownCalledFromNative; volatile BOOL m_fShutdownCalledFromManaged; BOOL m_fRecycleCalled; BOOL m_fInitialized; - FILE* m_pStdFile; - STTIMER m_Timer; SRWLOCK m_srwLock; // Thread for capturing startup stderr logs when logging is disabled @@ -188,6 +159,7 @@ private: PCWSTR m_pstrDotnetExeLocation; static IN_PROCESS_APPLICATION* s_Application; + IOutputManager* m_pLoggerProvider; REQUESTHANDLER_CONFIG* m_pConfig; // Allows to override call to hostfxr_main with custome callback @@ -205,26 +177,26 @@ private: _In_ LPVOID pContext ); - static - VOID - ReadStdErrHandle - ( - _In_ LPVOID pContext - ); - HRESULT SetEnvironementVariablesOnWorkerProcess( VOID ); - static - INT - FilterException(unsigned int code, struct _EXCEPTION_POINTERS *ep); - HRESULT RunDotnetApplication( DWORD argc, CONST PCWSTR* argv, hostfxr_main_fn pProc ); + + static + DWORD + DoShutDown( + LPVOID lpParam + ); + + VOID + ShutDownInternal( + VOID + ); }; diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj index 9881e6d4a8..08cd979af8 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj @@ -217,7 +217,6 @@ - diff --git a/test/CommonLibTests/CommonLibTests.vcxproj b/test/CommonLibTests/CommonLibTests.vcxproj index 184b27b4ae..ffa2e2314c 100644 --- a/test/CommonLibTests/CommonLibTests.vcxproj +++ b/test/CommonLibTests/CommonLibTests.vcxproj @@ -32,12 +32,28 @@ + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + + + Create Create @@ -141,7 +157,7 @@ ProgramDatabase $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest-1.8.0\include;..\..\src\AspNetCoreModuleV2\AspNetCore\Inc; /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" - stdcpp17 + stdcpp14 true diff --git a/test/CommonLibTests/FileOutputManagerTests.cpp b/test/CommonLibTests/FileOutputManagerTests.cpp new file mode 100644 index 0000000000..e3d261097a --- /dev/null +++ b/test/CommonLibTests/FileOutputManagerTests.cpp @@ -0,0 +1,142 @@ +// 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" + +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"; + + std::wstring tempDirectory = Helpers::CreateRandomTempDirectory(); + FileOutputManager* pManager = new FileOutputManager; + pManager->Initialize(fileNamePrefix.c_str(), tempDirectory.c_str()); + { + FileManagerWrapper wrapper(pManager); + + wprintf(expected, out); + } + + // std::filesystem is available on c++17, however gtest fails to build when using it + // c++14 has filesystem as experimental. + for (auto & p : std::experimental::filesystem::directory_iterator(tempDirectory)) + { + std::wstring filename(p.path().filename()); + ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix); + + std::wstring content = Helpers::ReadFileContent(std::wstring(p.path())); + ASSERT_EQ(content.length(), DWORD(4)); + ASSERT_STREQ(content.c_str(), expected); + } + + Helpers::DeleteDirectory(tempDirectory); + } + }; + + 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, StdErr) + { + PCSTR expected = "test"; + + std::wstring tempDirectory = Helpers::CreateRandomTempDirectory(); + + FileOutputManager* pManager = new FileOutputManager; + pManager->Initialize(L"", tempDirectory.c_str()); + { + FileManagerWrapper wrapper(pManager); + + printf(expected, stderr); + STRA straContent; + ASSERT_TRUE(pManager->GetStdOutContent(&straContent)); + + ASSERT_STREQ(straContent.QueryStr(), expected); + } + + Helpers::DeleteDirectory(tempDirectory); + } + + TEST(FileOutManagerOutputTest, CheckFileOutput) + { + PCSTR expected = "test"; + + std::wstring tempDirectory = Helpers::CreateRandomTempDirectory(); + + FileOutputManager* pManager = new FileOutputManager; + pManager->Initialize(L"", tempDirectory.c_str()); + { + FileManagerWrapper wrapper(pManager); + + printf(expected); + STRA straContent; + ASSERT_TRUE(pManager->GetStdOutContent(&straContent)); + + ASSERT_STREQ(straContent.QueryStr(), expected); + } + + Helpers::DeleteDirectory(tempDirectory); + } + + TEST(FileOutManagerOutputTest, CapAt4KB) + { + PCSTR expected = "test"; + + std::wstring tempDirectory = Helpers::CreateRandomTempDirectory(); + + FileOutputManager* pManager = new FileOutputManager; + pManager->Initialize(L"", tempDirectory.c_str()); + { + FileManagerWrapper wrapper(pManager); + + for (int i = 0; i < 1200; i++) + { + printf(expected); + } + + STRA straContent; + ASSERT_TRUE(pManager->GetStdOutContent(&straContent)); + + ASSERT_EQ(straContent.QueryCCH(), 4096); + } + + Helpers::DeleteDirectory(tempDirectory); + } +} + diff --git a/test/CommonLibTests/Helpers.cpp b/test/CommonLibTests/Helpers.cpp new file mode 100644 index 0000000000..9ad7560c94 --- /dev/null +++ b/test/CommonLibTests/Helpers.cpp @@ -0,0 +1,48 @@ +// 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" + +std::wstring +Helpers::CreateRandomValue() +{ + int randomValue = rand(); + return std::to_wstring(randomValue); +} + +std::wstring +Helpers::CreateRandomTempDirectory() +{ + PWSTR tempPath = new WCHAR[256]; + GetTempPath(256, tempPath); + std::wstring wstringPath(tempPath); + + return wstringPath.append(Helpers::CreateRandomValue()).append(L"\\"); +} + +void +Helpers::DeleteDirectory(std::wstring directory) +{ + std::experimental::filesystem::remove_all(directory); +} + +std::wstring +Helpers::ReadFileContent(std::wstring file) +{ + std::wcout << file << std::endl; + + std::fstream t(file); + std::stringstream buffer; + buffer << t.rdbuf(); + + int nChars = MultiByteToWideChar(CP_ACP, 0, buffer.str().c_str(), -1, NULL, 0); + + LPWSTR pwzName = new WCHAR[nChars]; + MultiByteToWideChar(CP_UTF8, 0, buffer.str().c_str(), -1, pwzName, nChars); + + std::wstring retVal(pwzName); + + delete pwzName; + + return retVal; +} diff --git a/test/CommonLibTests/Helpers.h b/test/CommonLibTests/Helpers.h new file mode 100644 index 0000000000..f9e06bbca4 --- /dev/null +++ b/test/CommonLibTests/Helpers.h @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +class Helpers +{ +public: + + static + std::wstring + CreateRandomValue(); + + static + std::wstring + CreateRandomTempDirectory(); + + static + void + DeleteDirectory(std::wstring directory); + + static + std::wstring + ReadFileContent(std::wstring file); +}; + diff --git a/test/CommonLibTests/PipeOutputManagerTests.cpp b/test/CommonLibTests/PipeOutputManagerTests.cpp new file mode 100644 index 0000000000..1b16f470d1 --- /dev/null +++ b/test/CommonLibTests/PipeOutputManagerTests.cpp @@ -0,0 +1,39 @@ +// 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" + +class FileManagerWrapper +{ +public: + PipeOutputManager * manager; + FileManagerWrapper(PipeOutputManager* m) + : manager(m) + { + manager->Start(); + } + + ~FileManagerWrapper() + { + delete manager; + } +}; + +namespace PipeOutputManagerTests +{ + TEST(PipeManagerOutputTest, NotifyStartupCompleteCallsDispose) + { + PCWSTR expected = L"test"; + + PipeOutputManager* pManager = new PipeOutputManager(); + ASSERT_EQ(S_OK, pManager->Start()); + + pManager->NotifyStartupComplete(); + + // 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. + } +} + diff --git a/test/CommonLibTests/main.cpp b/test/CommonLibTests/main.cpp index 49f150778f..47c991b3e9 100644 --- a/test/CommonLibTests/main.cpp +++ b/test/CommonLibTests/main.cpp @@ -9,6 +9,7 @@ int wmain(int argc, wchar_t* argv[]) int main(int argc, char* argv[]) #endif { + std::srand((unsigned int)std::time(0)); ::testing::InitGoogleTest(&argc, argv); RUN_ALL_TESTS(); return 0; diff --git a/test/CommonLibTests/stdafx.h b/test/CommonLibTests/stdafx.h index 3ada2d330d..e7c8024eda 100644 --- a/test/CommonLibTests/stdafx.h +++ b/test/CommonLibTests/stdafx.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -48,6 +50,7 @@ #include "requesthandler.h" #include "resources.h" #include "aspnetcore_msg.h" +#include "Helpers.h" #undef assert // Macro redefinition in IISLib. #include "gtest\gtest.h" diff --git a/test/IISIntegration.FunctionalTests/Inprocess/LoggingTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/LoggingTests.cs new file mode 100644 index 0000000000..e6215f34bc --- /dev/null +++ b/test/IISIntegration.FunctionalTests/Inprocess/LoggingTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using IISIntegration.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class LoggingTests : IISFunctionalTestBase + { + [Theory] + [InlineData("CheckErrLogFile")] + [InlineData("CheckLogFile")] + public async Task CheckStdoutLogging(string path) + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters(); + deploymentParameters.PublishApplicationBeforeDeployment = true; + deploymentParameters.PreservePublishedApplicationForDebugging = true; // workaround for keeping + + var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogEnabled", "true"); + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogFile", @".\logs\stdout"); + + var response = await deploymentResult.RetryingHttpClient.GetAsync(path); + + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal("Hello World", responseText); + + Dispose(); + + var folderPath = Path.Combine(deploymentResult.DeploymentResult.ContentRoot, @"logs"); + + var fileInDirectory = Directory.GetFiles(folderPath).Single(); + Assert.NotNull(fileInDirectory); + + string contents = null; + for (var i = 0; i < 20; i++) + { + try + { + contents = await File.ReadAllTextAsync(fileInDirectory); + break; + } + catch (IOException) + { + // File in use by IISExpress, delay a bit before rereading. + } + await Task.Delay(20); + } + + Assert.NotNull(contents); + + // Open the log file and see if there are any contents. + Assert.Contains("TEST MESSAGE", contents); + + RetryHelper.RetryOperation( + () => Directory.Delete(deploymentParameters.PublishedApplicationRootPath, true), + e => Logger.LogWarning($"Failed to delete directory : {e.Message}"), + retryCount: 3, + retryDelayMilliseconds: 100); + } + } +} diff --git a/test/IISIntegration.FunctionalTests/Inprocess/StartupExceptionTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/StartupExceptionTests.cs new file mode 100644 index 0000000000..6ca6a200cc --- /dev/null +++ b/test/IISIntegration.FunctionalTests/Inprocess/StartupExceptionTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using IISIntegration.FunctionalTests.Utilities; +using Xunit; +using System.Net; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class StartupExceptionTests : IISFunctionalTestBase + { + + [Theory] + [InlineData("CheckLogFile")] + [InlineData("CheckErrLogFile")] + public async Task CheckStdoutWithRandomNumber(string path) + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite"); + deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path; + var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString(); + deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.RetryingHttpClient.GetAsync(path); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + Assert.Contains(TestSink.Writes, context => context.Message.Contains($"Random number: {randomNumberString}")); + } + + [Theory] + [InlineData("CheckLargeStdErrWrites")] + [InlineData("CheckLargeStdOutWrites")] + [InlineData("CheckOversizedStdErrWrites")] + [InlineData("CheckOversizedStdOutWrites")] + public async Task CheckStdoutWithLargeWrites(string path) + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite"); + deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path; + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.RetryingHttpClient.GetAsync(path); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + Assert.Contains(TestSink.Writes, context => context.Message.Contains(new string('a', 4096))); + } + } +} diff --git a/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs index f679afb235..19c59a9161 100644 --- a/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var deploymentResult = await DeployAsync(deploymentParameters); - ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var deploymentResult = await DeployAsync(deploymentParameters); - ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); // Request to base address and check if various parts of the body are rendered & measure the cold startup time. var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); @@ -60,7 +60,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } - public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress) .WithTfms(Tfm.NetCoreApp22) @@ -94,7 +93,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Contains(TestSink.Writes, context => context.Message.Contains("Application is running inside IIS process but is not configured to use IIS server")); } - private DeploymentParameters GetBaseDeploymentParameters(string site = "InProcessWebSite") + // Defaults to inprocess specific deployment parameters + public static DeploymentParameters GetBaseDeploymentParameters(string site = "InProcessWebSite") { return new DeploymentParameters(Helpers.GetTestWebSitePath(site), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { @@ -105,16 +105,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests PublishApplicationBeforeDeployment = site == "InProcessWebSite", }; } - - private static void ModifyAspNetCoreSectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value) - { - // modify the web.config after publish - var root = deploymentResult.DeploymentResult.ContentRoot; - var webConfigFile = $"{root}/web.config"; - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("aspNetCore").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } } } diff --git a/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs index 7690a49208..4d3e0e65dc 100644 --- a/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests @@ -16,5 +19,32 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public static string GetInProcessTestSitesPath() => GetTestWebSitePath("InProcessWebSite"); public static string GetOutOfProcessTestSitesPath() => GetTestWebSitePath("OutOfProcessWebSite"); + + public static void ModifyAspNetCoreSectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value) + => ModifySectionInWebConfig(deploymentResult, key, value, section: "aspNetCore", 0); + + public static void ModifySectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value, string section, int index) + { + // modify the web.config after publish + var root = deploymentResult.DeploymentResult.ContentRoot; + var webConfigFile = $"{root}/web.config"; + var config = XDocument.Load(webConfigFile); + var element = config.Descendants(section).ToList()[index]; + element.SetAttributeValue(key, value); + config.Save(webConfigFile); + } + + // Defaults to inprocess specific deployment parameters + public static DeploymentParameters GetBaseDeploymentParameters(string site = "InProcessWebSite") + { + return new DeploymentParameters(Helpers.GetTestWebSitePath(site), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + { + TargetFramework = Tfm.NetCoreApp22, + ApplicationType = ApplicationType.Portable, + AncmVersion = AncmVersion.AspNetCoreModuleV2, + HostingModel = HostingModel.InProcess, + PublishApplicationBeforeDeployment = site == "InProcessWebSite", + }; + } } } diff --git a/test/WebSites/InProcessWebSite/Startup.cs b/test/WebSites/InProcessWebSite/Startup.cs index b6fd2ae9ca..8a190d47f0 100644 --- a/test/WebSites/InProcessWebSite/Startup.cs +++ b/test/WebSites/InProcessWebSite/Startup.cs @@ -657,7 +657,6 @@ namespace IISTestSite { app.Run(async context => { - if (context.Request.Path.StartsWithSegments("/NullBuffer")) { await context.Response.Body.WriteAsync(null, 0, 0); @@ -704,5 +703,27 @@ namespace IISTestSite { app.Run(async ctx => { await ctx.Response.WriteAsync(AppDomain.CurrentDomain.BaseDirectory); }); } + + private void CheckLogFile(IApplicationBuilder app) + { + app.Run(async ctx => + { + Console.WriteLine("TEST MESSAGE"); + + await ctx.Response.WriteAsync("Hello World"); + }); + } + + private void CheckErrLogFile(IApplicationBuilder app) + { + app.Run(async ctx => + { + Console.Error.WriteLine("TEST MESSAGE"); + Console.Error.Flush(); + + await ctx.Response.WriteAsync("Hello World"); + }); + } + } } diff --git a/test/WebSites/InProcessWebSite/web.config b/test/WebSites/InProcessWebSite/web.config index 532ccefdd9..3e1be0c169 100644 --- a/test/WebSites/InProcessWebSite/web.config +++ b/test/WebSites/InProcessWebSite/web.config @@ -1,16 +1,16 @@ - + - + - + - + \ No newline at end of file diff --git a/test/WebSites/StartupExceptionWebSite/Program.cs b/test/WebSites/StartupExceptionWebSite/Program.cs new file mode 100644 index 0000000000..283a0136ce --- /dev/null +++ b/test/WebSites/StartupExceptionWebSite/Program.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace IISTestSite +{ + public static class Program + { + public static void Main(string[] args) + { + var envVariable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_STARTUP_VALUE"); + var randomNumber = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_RANDOM_VALUE"); + + // Semicolons are appended to env variables; removing them. + if (envVariable == "CheckLargeStdOutWrites") + { + Console.WriteLine(new string('a', 4096)); + } + else if (envVariable == "CheckLargeStdErrWrites") + { + Console.Error.WriteLine(new string('a', 4096)); + Console.Error.Flush(); + } + else if (envVariable == "CheckLogFile") + { + Console.WriteLine($"Random number: {randomNumber}"); + } + else if (envVariable == "CheckErrLogFile") + { + Console.Error.WriteLine($"Random number: {randomNumber}"); + Console.Error.Flush(); + } + else if (envVariable == "CheckOversizedStdErrWrites") + { + Console.WriteLine(new string('a', 5000)); + + } + else if (envVariable == "CheckOversizedStdOutWrites") + { + Console.Error.WriteLine(new string('a', 4096)); + Console.Error.Flush(); + } + } + } +} diff --git a/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json b/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json new file mode 100644 index 0000000000..aa0472a2dd --- /dev/null +++ b/test/WebSites/StartupExceptionWebSite/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "iisSettings": { + "windowsAuthentication": true, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5762/", + "sslPort": 0 + } + }, + "profiles": { + "ANCM IIS Express": { + "commandName": "Executable", + "executablePath": "$(IISExpressPath)", + "commandLineArgs": "$(IISExpressArguments)", + "nativeDebugging": true, + "environmentVariables": { + "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", + "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "LAUNCHER_ARGS": "$(TargetPath)", + "ASPNETCORE_ENVIRONMENT": "Development", + "LAUNCHER_PATH": "$(DotNetPath)" + } + }, + "ANCM IIS": { + "commandName": "Executable", + "executablePath": "$(IISPath)", + "commandLineArgs": "$(IISArguments)", + "environmentVariables": { + "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", + "ANCM_PATH": "$(TargetDir)$(AncmV2Path)", + "LAUNCHER_ARGS": "$(TargetPath)", + "ASPNETCORE_ENVIRONMENT": "Development", + "LAUNCHER_PATH": "$(DotNetPath)" + } + } + } +} diff --git a/test/WebSites/StartupExceptionWebSite/StartupExceptionWebSite.csproj b/test/WebSites/StartupExceptionWebSite/StartupExceptionWebSite.csproj new file mode 100644 index 0000000000..2b9db3ce4a --- /dev/null +++ b/test/WebSites/StartupExceptionWebSite/StartupExceptionWebSite.csproj @@ -0,0 +1,17 @@ + + + + + + netcoreapp2.2 + + + + + + + + + + +