diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h index ae4aeb0466..05fd514bea 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/config_utility.h @@ -14,6 +14,7 @@ class ConfigUtility #define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings" #define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" #define CS_ASPNETCORE_DEBUG_FILE L"debugFile" + #define CS_ASPNETCORE_ENABLE_OUT_OF_PROCESS_CONSOLE_REDIRECTION L"enableOutOfProcessConsoleRedirection" #define CS_ASPNETCORE_DEBUG_LEVEL L"debugLevel" #define CS_ASPNETCORE_HANDLER_SETTINGS_NAME L"name" #define CS_ASPNETCORE_HANDLER_SETTINGS_VALUE L"value" @@ -40,6 +41,13 @@ public: return FindKeyValuePair(pElement, CS_ASPNETCORE_DEBUG_LEVEL, strDebugFile); } + static + HRESULT + FindEnableOutOfProcessConsoleRedirection(IAppHostElement* pElement, STRU& strEnableOutOfProcessConsoleRedirection) + { + return FindKeyValuePair(pElement, CS_ASPNETCORE_ENABLE_OUT_OF_PROCESS_CONSOLE_REDIRECTION, strEnableOutOfProcessConsoleRedirection); + } + private: static HRESULT diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp index 80e70a187c..4837a94c72 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp @@ -158,6 +158,7 @@ PROCESS_MANAGER::GetProcess( pConfig->QueryAnonymousAuthEnabled(), pConfig->QueryEnvironmentVariables(), pConfig->QueryStdoutLogEnabled(), + pConfig->QueryEnableOutOfProcessConsoleRedirection(), fWebsocketSupported, pConfig->QueryStdoutLogFile(), pConfig->QueryApplicationPhysicalPath(), // physical path diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp index 5216e072d4..94e3279f75 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp @@ -25,6 +25,7 @@ SERVER_PROCESS::Initialize( BOOL fAnonymousAuthEnabled, std::map& pEnvironmentVariables, BOOL fStdoutLogEnabled, + BOOL fEnableOutOfProcessConsoleRedirection, BOOL fWebSocketSupported, STRU *pstruStdoutLogFile, STRU *pszAppPhysicalPath, @@ -43,6 +44,7 @@ SERVER_PROCESS::Initialize( m_fWindowsAuthEnabled = fWindowsAuthEnabled; m_fBasicAuthEnabled = fBasicAuthEnabled; m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; + m_fEnableOutOfProcessConsoleRedirection = fEnableOutOfProcessConsoleRedirection; m_pProcessManager->ReferenceProcessManager(); m_fDebuggerAttached = FALSE; @@ -1030,6 +1032,15 @@ SERVER_PROCESS::SetupStdHandles( saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; + if (!m_fEnableOutOfProcessConsoleRedirection) + { + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = INVALID_HANDLE_VALUE; + pStartupInfo->hStdOutput = INVALID_HANDLE_VALUE; + return hr; + } + if (!m_fStdoutLogEnabled) { CreatePipe(&m_hStdoutHandle, &m_hStdErrWritePipe, &saAttr, 0 /*nSize*/); @@ -1770,6 +1781,8 @@ SERVER_PROCESS::SERVER_PROCESS() : m_dwListeningProcessId(0), m_hListeningProcessHandle(NULL), m_hShutdownHandle(NULL), + m_hStdErrWritePipe(NULL), + m_hReadThread(nullptr), m_randomGenerator(std::random_device()()) { //InterlockedIncrement(&g_dwActiveServerProcesses); @@ -1866,13 +1879,15 @@ SERVER_PROCESS::~SERVER_PROCESS() m_pProcessManager = NULL; } - if (m_hStdoutHandle != NULL) + if (m_hStdErrWritePipe != NULL) { - if (m_hStdoutHandle != INVALID_HANDLE_VALUE) + if (m_hStdErrWritePipe != INVALID_HANDLE_VALUE) { - CloseHandle(m_hStdoutHandle); + FlushFileBuffers(m_hStdErrWritePipe); + CloseHandle(m_hStdErrWritePipe); } - m_hStdoutHandle = NULL; + + m_hStdErrWritePipe = NULL; } // Forces ReadFile to cancel, causing the read loop to complete. @@ -1907,6 +1922,15 @@ SERVER_PROCESS::~SERVER_PROCESS() m_hReadThread = nullptr; } + if (m_hStdoutHandle != NULL) + { + if (m_hStdoutHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hStdoutHandle); + } + m_hStdoutHandle = NULL; + } + if (m_fStdoutLogEnabled) { m_Timer.CancelTimer(); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h index 0d90dd47de..bfe3225dbd 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.h @@ -37,6 +37,7 @@ public: _In_ BOOL fAnonymousAuthEnabled, _In_ std::map& pEnvironmentVariables, _In_ BOOL fStdoutLogEnabled, + _In_ BOOL fDisableRedirection, _In_ BOOL fWebSocketSupported, _In_ STRU *pstruStdoutLogFile, _In_ STRU *pszAppPhysicalPath, @@ -253,6 +254,7 @@ private: BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; BOOL m_fDebuggerAttached; + BOOL m_fEnableOutOfProcessConsoleRedirection; STTIMER m_Timer; SOCKET m_socket; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp index f4507164df..d3a705e9a2 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp @@ -379,6 +379,12 @@ REQUESTHANDLER_CONFIG::Populate( goto Finished; } + hr = ConfigUtility::FindEnableOutOfProcessConsoleRedirection(pAspNetCoreElement, m_fEnableOutOfProcessConsoleRedirection); + if (FAILED(hr)) + { + goto Finished; + } + Finished: if (pAspNetCoreElement != NULL) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h index 90a2b5a365..5f06c12695 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h @@ -218,6 +218,12 @@ public: return &m_struConfigPath; } + BOOL + QueryEnableOutOfProcessConsoleRedirection() + { + return !m_fEnableOutOfProcessConsoleRedirection.Equals(L"false", 1); + } + protected: // @@ -255,6 +261,7 @@ protected: BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; + STRU m_fEnableOutOfProcessConsoleRedirection; APP_HOSTING_MODEL m_hostingModel; std::map m_pEnvironmentVariables; STRU m_struHostFxrLocation; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs index 699bd8b3ac..70f6a6b8b4 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs @@ -233,6 +233,22 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.OutOfProcessFailedToStart(deploymentResult, "Wow!"), Logger); } + [ConditionalFact] + [RequiresNewShim] + public async Task DisableRedirectionNoLogs() + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess); + deploymentParameters.HandlerSettings["enableOutOfProcessConsoleRedirection"] = "false"; + deploymentParameters.TransformArguments((a, _) => $"{a} ConsoleWriteSingle"); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("Test"); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.OutOfProcessFailedToStart(deploymentResult, ""), Logger); + } + [ConditionalFact] public async Task CaptureLogsForOutOfProcessWhenProcessFailsToStart30KbMax() { diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs index ddc981d7af..8557b89f94 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs @@ -84,6 +84,55 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode); } + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + [RequiresNewShim] + public async Task ShutdownMultipleTimesWorks(TestVariant variant) + { + // Must publish to set env vars in web.config + var deploymentParameters = Fixture.GetBaseDeploymentParameters(variant); + + var deploymentResult = await DeployAsync(deploymentParameters); + + // Shutdown once + var response = await deploymentResult.HttpClient.GetAsync("/Shutdown"); + + // Wait for server to start again. + int i; + for (i = 0; i < 10; i++) + { + // ANCM should eventually recover from being shutdown multiple times. + response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + if (response.IsSuccessStatusCode) + { + break; + } + } + + if (i == 10) + { + // Didn't restart after 10 retries + Assert.False(true); + } + + // Shutdown again + response = await deploymentResult.HttpClient.GetAsync("/Shutdown"); + + // return if server starts again. + for (i = 0; i < 10; i++) + { + // ANCM should eventually recover from being shutdown multiple times. + response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + if (response.IsSuccessStatusCode) + { + return; + } + } + + // Test failure if this happens. + Assert.False(true); + } + private static int GetUnusedRandomPort() { // Large number of retries to prevent test failures due to port collisions, but not infinite