diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h index 8c9cece3e3..ef55c4ec53 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h @@ -30,6 +30,7 @@ #define CS_ENABLED L"enabled" #define CS_ASPNETCORE_HANDLER_CALL_STARTUP_HOOK L"callStartupHook" #define CS_ASPNETCORE_HANDLER_STACK_SIZE L"stackSize" +#define CS_ASPNETCORE_SUPPRESS_RECYCLE_ON_STARTUP_TIMEOUT L"suppressRecycleOnStartupTimeout" #define CS_ASPNETCORE_DETAILEDERRORS L"ASPNETCORE_DETAILEDERRORS" #define CS_ASPNETCORE_ENVIRONMENT L"ASPNETCORE_ENVIRONMENT" #define CS_DOTNET_ENVIRONMENT L"DOTNET_ENVIRONMENT" diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp index c2ff5e0a7d..157f0dda22 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp @@ -66,6 +66,7 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc m_fSetCurrentDirectory = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_HANDLER_SET_CURRENT_DIRECTORY).value_or(L"true"), L"true"); m_fCallStartupHook = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_HANDLER_CALL_STARTUP_HOOK).value_or(L"true"), L"true"); m_strStackSize = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_STACK_SIZE).value_or(L"1048576"); + m_fSuppressRecycleOnStartupTimeout = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_SUPPRESS_RECYCLE_ON_STARTUP_TIMEOUT).value_or(L"false"), L"true"); m_dwStartupTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT) * 1000; m_dwShutdownTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT) * 1000; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h index 61a79664b2..fa0e19e849 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h @@ -118,6 +118,12 @@ public: return m_strStackSize; } + bool + QuerySuppressRecycleOnStartupTimeout() const + { + return m_fSuppressRecycleOnStartupTimeout; + } + InProcessOptions(const ConfigurationSource &configurationSource, IHttpSite* pSite); static @@ -139,6 +145,7 @@ private: bool m_fWindowsAuthEnabled; bool m_fBasicAuthEnabled; bool m_fAnonymousAuthEnabled; + bool m_fSuppressRecycleOnStartupTimeout; DWORD m_dwStartupTimeLimitInMS; DWORD m_dwShutdownTimeLimitInMS; DWORD m_dwMaxRequestBodySize; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 4f86cde3b8..1dd8a42c4b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -169,7 +169,14 @@ IN_PROCESS_APPLICATION::LoadManagedApplication(ErrorContext& errorContext) errorContext.errorReason = format("ASP.NET Core app failed to start after %d milliseconds", m_pConfig->QueryStartupTimeLimitInMS()); m_waitForShutdown = false; - StopClr(); + if (m_pConfig->QuerySuppressRecycleOnStartupTimeout()) + { + StopClr(); + } + else + { + Stop(/* fServerInitiated */false); + } throw InvalidOperationException(format(L"Managed server didn't initialize after %u ms.", m_pConfig->QueryStartupTimeLimitInMS())); } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs index 220489dba6..01a7243f10 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -453,9 +453,10 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess } [ConditionalFact] + [RequiresNewHandler] public async Task StartupTimeoutIsApplied() { - // From what I can tell, this failure is due to ungraceful shutdown. + // From what we can tell, this failure is due to ungraceful shutdown. // The error could be the same as https://github.com/dotnet/core-setup/issues/4646 // But can't be certain without another repro. using (AppVerifier.Disable(DeployerSelector.ServerType, 0x300)) @@ -470,11 +471,45 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess var response = await deploymentResult.HttpClient.GetAsync("/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + // Startup timeout now recycles process. + deploymentResult.AssertWorkerProcessStop(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, + EventLogHelpers.InProcessFailedToStart(deploymentResult, "Managed server didn't initialize after 1000 ms."), + Logger); + + if (DeployerSelector.HasNewHandler) + { + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Contains("500.37", responseContent); + } + } + } + + [ConditionalFact] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H1, SkipReason = "Shutdown hangs https://github.com/dotnet/aspnetcore/issues/25107")] + public async Task StartupTimeoutIsApplied_DisableRecycleOnStartupTimeout() + { + // From what we can tell, this failure is due to ungraceful shutdown. + // The error could be the same as https://github.com/dotnet/core-setup/issues/4646 + // But can't be certain without another repro. + using (AppVerifier.Disable(DeployerSelector.ServerType, 0x300)) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite); + deploymentParameters.TransformArguments((a, _) => $"{a} Hang"); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("startupTimeLimit", "1")); + deploymentParameters.HandlerSettings["suppressRecycleOnStartupTimeout"] = "true"; + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + StopServer(gracefulShutdown: false); - EventLogHelpers.VerifyEventLogEvents(deploymentResult, - EventLogHelpers.InProcessFailedToStart(deploymentResult, "Managed server didn't initialize after 1000 ms.") - ); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, + EventLogHelpers.InProcessFailedToStart(deploymentResult, "Managed server didn't initialize after 1000 ms."), + Logger); if (DeployerSelector.HasNewHandler) {