From b359d6bed106ebead16a26ddc937e1f71f7f9b5c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 13 Jul 2018 11:35:52 -0700 Subject: [PATCH 1/7] Fix CI test failures (#1045) --- .../CommonLib/debugutil.cpp | 2 +- .../IISApplication.cs | 1 - .../IISDeployer.cs | 5 +- .../Inprocess/EventLogTests.cs | 2 +- .../Inprocess/LoggingTests.cs | 6 +- .../Inprocess/StartupExceptionTests.cs | 19 ++- .../Utilities/Helpers.cs | 58 ++++++++ .../Utilities/IISCapability.cs | 16 +++ .../Utilities/SkipIISAttribute.cs | 17 --- test/IIS.FunctionalTests/RequiresAttribute.cs | 73 ---------- .../RequiresIISAttribute.cs | 131 ++++++++++++++++++ .../RequiresIISAttribute.cs | 7 + 12 files changed, 238 insertions(+), 99 deletions(-) create mode 100644 test/Common.FunctionalTests/Utilities/IISCapability.cs delete mode 100644 test/IIS.FunctionalTests/RequiresAttribute.cs create mode 100644 test/IIS.FunctionalTests/RequiresIISAttribute.cs diff --git a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp index 09becc9010..28bc775dfa 100644 --- a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp @@ -91,7 +91,7 @@ DebugInitializeFromConfig(IHttpServer& pHttpServer, IHttpApplication& pHttpAppli STRU debugValue; RETURN_IF_FAILED(ConfigUtility::FindDebugLevel(pAspNetCoreElement, debugValue)); - SetDebugFlags(debugFile.QueryStr()); + SetDebugFlags(debugValue.QueryStr()); CreateDebugLogFile(debugFile.QueryStr()); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs index 3fafd7c8f9..afa28dd4e3 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs @@ -197,7 +197,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting () => File.Delete(_apphostConfigPath), e => _logger.LogWarning($"Failed to delete file : {e.Message}")); } - if (File.Exists(_apphostConfigBackupPath)) { RetryFileOperation( diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs index 6df58dd736..f1f4922e88 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs @@ -34,7 +34,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS } GetLogsFromFile($"{_application.WebSiteName}.txt"); - GetLogsFromFile("web.config"); CleanPublishedOutput(); InvokeUserApplicationCleanup(); @@ -88,9 +87,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS var arr = new string[0]; RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, file)), - (ex) => Logger.LogError("Could not read log file"), + (ex) => Logger.LogWarning("Could not read log file"), 5, 200); + + Logger.LogInformation($"Found debug log file: {file}"); foreach (var line in arr) { Logger.LogInformation(line); diff --git a/test/Common.FunctionalTests/Inprocess/EventLogTests.cs b/test/Common.FunctionalTests/Inprocess/EventLogTests.cs index 1ae8aa5d57..176661189c 100644 --- a/test/Common.FunctionalTests/Inprocess/EventLogTests.cs +++ b/test/Common.FunctionalTests/Inprocess/EventLogTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); var deploymentResult = await DeployAsync(deploymentParameters); - await Helpers.AssertStarts(deploymentResult, "/HelloWorld"); + await Helpers.AssertStarts(deploymentResult); StopServer(); diff --git a/test/Common.FunctionalTests/Inprocess/LoggingTests.cs b/test/Common.FunctionalTests/Inprocess/LoggingTests.cs index 6b4b2278a4..53009a969c 100644 --- a/test/Common.FunctionalTests/Inprocess/LoggingTests.cs +++ b/test/Common.FunctionalTests/Inprocess/LoggingTests.cs @@ -69,6 +69,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] public async Task StartupMessagesAreLoggedIntoDebugLogFile() { var tempFile = Path.GetTempFileName(); @@ -78,6 +79,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"] = tempFile; var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.AddDebugLogToWebConfig(deploymentResult.DeploymentResult.ContentRoot, tempFile); + var response = await deploymentResult.RetryingHttpClient.GetAsync("/"); StopServer(); @@ -93,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } [ConditionalFact] - [SkipIIS] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] public async Task StartupMessagesLogFileSwitchedWhenLogFilePresentInWebConfig() { var firstTempFile = Path.GetTempFileName(); diff --git a/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs b/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs index d39ed68c24..667c47327c 100644 --- a/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs +++ b/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; @@ -22,11 +23,17 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests // However, for IIS, we need a web.config file because the default on generated on publish // doesn't include V2. We can remove the publish flag once IIS supports non-publish running var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true); - 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 environmentVariablesInWebConfig = new Dictionary + { + ["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path, + ["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString + }; var deploymentResult = await DeployAsync(deploymentParameters); + Helpers.AddEnvironmentVariablesToWebConfig(deploymentResult.DeploymentResult.ContentRoot, environmentVariablesInWebConfig); var response = await deploymentResult.RetryingHttpClient.GetAsync(path); @@ -45,10 +52,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task CheckStdoutWithLargeWrites(string path) { var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true); - deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path; + + var environmentVariablesInWebConfig = new Dictionary + { + ["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path + }; var deploymentResult = await DeployAsync(deploymentParameters); + Helpers.AddEnvironmentVariablesToWebConfig(deploymentResult.DeploymentResult.ContentRoot, environmentVariablesInWebConfig); + var response = await deploymentResult.RetryingHttpClient.GetAsync(path); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index e512dd6d83..0480351ce1 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -54,6 +56,62 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests config.Save(webConfigFile); } + public static void AddDebugLogToWebConfig(string contentRoot, string filename) + { + var path = Path.Combine(contentRoot, "web.config"); + var webconfig = XDocument.Load(path); + var xElement = webconfig.Descendants("aspNetCore").Single(); + + var element = xElement.Descendants("handlerSettings").SingleOrDefault(); + if (element == null) + { + element = new XElement("handlerSettings"); + xElement.Add(element); + } + + CreateOrSetElement(element, "debugLevel", "4", "handlerSetting"); + + CreateOrSetElement(element, "debugFile", Path.Combine(contentRoot, filename), "handlerSetting"); + + webconfig.Save(path); + } + + public static void AddEnvironmentVariablesToWebConfig(string contentRoot, IDictionary environmentVariables) + { + var path = Path.Combine(contentRoot, "web.config"); + var webconfig = XDocument.Load(path); + var xElement = webconfig.Descendants("aspNetCore").Single(); + + var element = xElement.Descendants("environmentVariables").SingleOrDefault(); + if (element == null) + { + element = new XElement("environmentVariables"); + xElement.Add(element); + } + + foreach (var envVar in environmentVariables) + { + CreateOrSetElement(element, envVar.Key, envVar.Value, "environmentVariable"); + } + + webconfig.Save(path); + } + + public static void CreateOrSetElement(XElement rootElement, string name, string value, string elementName) + { + if (rootElement.Descendants() + .Attributes() + .Where(attribute => attribute.Value == name) + .Any()) + { + return; + } + var element = new XElement(elementName); + element.SetAttributeValue("name", name); + element.SetAttributeValue("value", value); + rootElement.Add(element); + } + // Defaults to inprocess specific deployment parameters public static DeploymentParameters GetBaseDeploymentParameters(string site = null, HostingModel hostingModel = HostingModel.InProcess, bool publish = false) { diff --git a/test/Common.FunctionalTests/Utilities/IISCapability.cs b/test/Common.FunctionalTests/Utilities/IISCapability.cs new file mode 100644 index 0000000000..e3a5bf6c4e --- /dev/null +++ b/test/Common.FunctionalTests/Utilities/IISCapability.cs @@ -0,0 +1,16 @@ +// 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 Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Flags] + public enum IISCapability + { + None = 0, + Websockets = 1, + WindowsAuthentication = 2, + PoolEnvironmentVariables = 4 + } +} diff --git a/test/Common.FunctionalTests/Utilities/SkipIISAttribute.cs b/test/Common.FunctionalTests/Utilities/SkipIISAttribute.cs index da90b513f8..e69de29bb2 100644 --- a/test/Common.FunctionalTests/Utilities/SkipIISAttribute.cs +++ b/test/Common.FunctionalTests/Utilities/SkipIISAttribute.cs @@ -1,17 +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. - -using System; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.AspNetCore.Testing.xunit; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] - public sealed class SkipIISAttribute : Attribute, ITestCondition - { - public bool IsMet => DeployerSelector.ServerType == ServerType.IIS; - - public string SkipReason => "Cannot run test on full IIS."; - } -} diff --git a/test/IIS.FunctionalTests/RequiresAttribute.cs b/test/IIS.FunctionalTests/RequiresAttribute.cs deleted file mode 100644 index 6602d1125c..0000000000 --- a/test/IIS.FunctionalTests/RequiresAttribute.cs +++ /dev/null @@ -1,73 +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. - -using System; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Principal; -using System.Xml.Linq; -using Microsoft.AspNetCore.Testing.xunit; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] - public sealed class RequiresIISAttribute : Attribute, ITestCondition - { - private static readonly bool _isMet; - public static readonly string _skipReason; - - public bool IsMet => _isMet; - public string SkipReason => _skipReason; - - static RequiresIISAttribute() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - _skipReason = "IIS tests can only be run on Windows"; - return; - } - - var identity = WindowsIdentity.GetCurrent(); - var principal = new WindowsPrincipal(identity); - if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) - { - _skipReason += "The current console is not running as admin."; - return; - } - - if (!File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "w3wp.exe"))) - { - _skipReason += "The machine does not have IIS installed."; - return; - } - - var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml"); - - if (!File.Exists(ancmConfigPath)) - { - _skipReason = "IIS Schema is not installed."; - return; - } - - XDocument ancmConfig; - - try - { - ancmConfig = XDocument.Load(ancmConfigPath); - } - catch - { - _skipReason = "Could not read ANCM schema configuration"; - return; - } - - _isMet = ancmConfig - .Root - .Descendants("attribute") - .Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal)); - - _skipReason = _isMet ? null : "IIS schema needs to be upgraded to support ANCM."; - } - } -} diff --git a/test/IIS.FunctionalTests/RequiresIISAttribute.cs b/test/IIS.FunctionalTests/RequiresIISAttribute.cs new file mode 100644 index 0000000000..f55f397cfa --- /dev/null +++ b/test/IIS.FunctionalTests/RequiresIISAttribute.cs @@ -0,0 +1,131 @@ +// 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.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Xml.Linq; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Win32; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class RequiresIISAttribute : Attribute, ITestCondition + { + private static readonly bool _isMetStatic; + private static readonly string _skipReasonStatic; + + private readonly bool _isMet; + private readonly string _skipReason; + + private static readonly bool _websocketsAvailable; + private static readonly bool _windowsAuthAvailable; + private static readonly bool _poolEnvironmentVariablesAvailable; + + static RequiresIISAttribute() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _skipReasonStatic = "IIS tests can only be run on Windows"; + return; + } + + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + _skipReasonStatic += "The current console is not running as admin."; + return; + } + + if (!File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "w3wp.exe"))) + { + _skipReasonStatic += "The machine does not have IIS installed."; + return; + } + + var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml"); + + if (!File.Exists(ancmConfigPath)) + { + _skipReasonStatic = "IIS Schema is not installed."; + return; + } + + XDocument ancmConfig; + + try + { + ancmConfig = XDocument.Load(ancmConfigPath); + } + catch + { + _skipReasonStatic = "Could not read ANCM schema configuration"; + return; + } + + _isMetStatic = ancmConfig + .Root + .Descendants("attribute") + .Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal)); + + _skipReasonStatic = _isMetStatic ? null : "IIS schema needs to be upgraded to support ANCM."; + + _websocketsAvailable = File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "iiswsock.dll")); + + _windowsAuthAvailable = File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "authsspi.dll")); + + var iisRegistryKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\InetStp", writable: false); + if (iisRegistryKey == null) + { + _poolEnvironmentVariablesAvailable = false; + } + else + { + var majorVersion = (int)iisRegistryKey.GetValue("MajorVersion", -1); + var minorVersion = (int)iisRegistryKey.GetValue("MinorVersion", -1); + var version = new Version(majorVersion, minorVersion); + _poolEnvironmentVariablesAvailable = version >= new Version(10, 0); + } + } + + public RequiresIISAttribute() { } + + public RequiresIISAttribute(IISCapability capabilities) + { + _isMet = _isMetStatic; + _skipReason = _skipReasonStatic; + if (capabilities.HasFlag(IISCapability.Websockets)) + { + _isMet &= _websocketsAvailable; + if (!_websocketsAvailable) + { + _skipReason += "The machine does not have IIS websockets installed."; + } + } + if (capabilities.HasFlag(IISCapability.WindowsAuthentication)) + { + _isMet &= _windowsAuthAvailable; + + if (!_windowsAuthAvailable) + { + _skipReason += "The machine does not have IIS windows authentication installed."; + } + } + if (capabilities.HasFlag(IISCapability.PoolEnvironmentVariables)) + { + _isMet &= _poolEnvironmentVariablesAvailable; + if (!_poolEnvironmentVariablesAvailable) + { + _skipReason += "The machine does allow for setting environment variables on application pools."; + } + } + } + + public bool IsMet => _isMet; + public string SkipReason => _skipReason; + } +} diff --git a/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs b/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs index 15bbb9c26c..24d656332d 100644 --- a/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs +++ b/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs @@ -13,5 +13,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public bool IsMet => IISExpressAncmSchema.SupportsInProcessHosting; public string SkipReason => IISExpressAncmSchema.SkipReason; + + public RequiresIISAttribute() { } + + public RequiresIISAttribute(IISCapability capabilities) + { + // IISCapabilities aren't pretinent to IISExpress + } } } From c657e31b942e8bc612d159231c1deae0e02b4fdc Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 13 Jul 2018 21:32:20 -0700 Subject: [PATCH 2/7] Fix Console invalid handle issues. (#1029) --- .../inprocessapplication.cpp | 5 +- .../managedexports.cpp | 4 +- .../RequestHandlerLib/FileOutputManager.cpp | 16 ++-- .../RequestHandlerLib/FileOutputManager.h | 2 +- .../RequestHandlerLib/PipeOutputManager.cpp | 8 +- .../Core/IISNativeApplication.cs | 35 +++++++- .../NativeMethods.cs | 18 +++- .../IISApplication.cs | 1 - .../Inprocess/LoggingTests.cs | 87 ++++++++++++++----- .../Utilities/Helpers.cs | 16 ++++ test/WebSites/InProcessWebSite/Program.cs | 8 ++ test/WebSites/InProcessWebSite/Startup.cs | 1 - 12 files changed, 155 insertions(+), 46 deletions(-) diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index bb56b01419..7e6f0ee433 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -277,10 +277,7 @@ IN_PROCESS_APPLICATION::LoadManagedApplication goto Finished; } - if (FAILED(hr = m_pLoggerProvider->Start())) - { - goto Finished; - } + LOG_IF_FAILED(m_pLoggerProvider->Start()); } if (m_status != APPLICATION_STATUS::STARTING) diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index 38c4cb6503..1db194bdbd 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -15,7 +15,8 @@ register_callbacks( _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, _In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler, _In_ VOID* pvRequstHandlerContext, - _In_ VOID* pvShutdownHandlerContext + _In_ VOID* pvShutdownHandlerContext, + _Out_ BOOL* pfResetStandardStreams ) { if (pInProcessApplication == NULL) @@ -30,6 +31,7 @@ register_callbacks( pvRequstHandlerContext, pvShutdownHandlerContext ); + *pfResetStandardStreams = !pInProcessApplication->QueryConfig()->QueryStdoutLogEnabled(); return S_OK; } diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.cpp index 3242f96227..358d0f79cd 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.cpp @@ -10,8 +10,8 @@ FileOutputManager::FileOutputManager() : m_hLogFileHandle(INVALID_HANDLE_VALUE), - m_fdPreviousStdOut(0), - m_fdPreviousStdErr(0) + m_fdPreviousStdOut(-1), + m_fdPreviousStdErr(-1) { } @@ -40,14 +40,14 @@ FileOutputManager::~FileOutputManager() if (m_fdPreviousStdOut >= 0) { - _dup2(m_fdPreviousStdOut, _fileno(stdout)); - LOG_INFOF("Restoring original stdout of stdout: %d", m_fdPreviousStdOut); + LOG_LAST_ERROR_IF(SetStdHandle(STD_OUTPUT_HANDLE, (HANDLE)_get_osfhandle(m_fdPreviousStdOut))); + LOG_INFOF("Restoring original stdout: %d", m_fdPreviousStdOut); } if (m_fdPreviousStdErr >= 0) { - _dup2(m_fdPreviousStdErr, _fileno(stderr)); - LOG_INFOF("Restoring original stdout of stdout: %d", m_fdPreviousStdOut); + LOG_LAST_ERROR_IF(SetStdHandle(STD_ERROR_HANDLE, (HANDLE)_get_osfhandle(m_fdPreviousStdErr))); + LOG_INFOF("Restoring original stderr: %d", m_fdPreviousStdOut); } } @@ -60,10 +60,6 @@ FileOutputManager::Initialize(PCWSTR pwzStdOutLogFileName, PCWSTR pwzApplication return S_OK; } -void FileOutputManager::NotifyStartupComplete() -{ -} - bool FileOutputManager::GetStdOutContent(STRA* struStdOutput) { // diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.h b/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.h index 7f44d92462..f9a617ccf9 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/FileOutputManager.h @@ -19,7 +19,7 @@ public: virtual bool GetStdOutContent(STRA* struStdOutput) override; virtual HRESULT Start() override; - virtual void NotifyStartupComplete() override; + virtual void NotifyStartupComplete() override {}; private: HandleWrapper m_hLogFileHandle; diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/PipeOutputManager.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/PipeOutputManager.cpp index e8b61a962b..ade993f37b 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/PipeOutputManager.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/PipeOutputManager.cpp @@ -14,7 +14,9 @@ PipeOutputManager::PipeOutputManager() : m_hErrReadPipe(INVALID_HANDLE_VALUE), m_hErrWritePipe(INVALID_HANDLE_VALUE), m_hErrThread(NULL), - m_fDisposed(FALSE) + m_fDisposed(FALSE), + m_fdPreviousStdOut(-1), + m_fdPreviousStdErr(-1) { InitializeSRWLock(&m_srwLock); } @@ -54,7 +56,7 @@ PipeOutputManager::StopOutputRedirection() if (m_fdPreviousStdOut >= 0) { - LOG_IF_DUPFAIL(_dup2(m_fdPreviousStdOut, _fileno(stdout))); + LOG_LAST_ERROR_IF(SetStdHandle(STD_OUTPUT_HANDLE, (HANDLE)_get_osfhandle(m_fdPreviousStdOut))); } else { @@ -63,7 +65,7 @@ PipeOutputManager::StopOutputRedirection() if (m_fdPreviousStdErr >= 0) { - LOG_IF_DUPFAIL(_dup2(m_fdPreviousStdErr, _fileno(stderr))); + LOG_LAST_ERROR_IF(SetStdHandle(STD_ERROR_HANDLE, (HANDLE)_get_osfhandle(m_fdPreviousStdErr))); } else { diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs index 6d574867b4..44f9b40bef 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; namespace Microsoft.AspNetCore.Server.IIS.Core { @@ -31,7 +32,39 @@ namespace Microsoft.AspNetCore.Server.IIS.Core IntPtr requestContext, IntPtr shutdownContext) { - NativeMethods.HttpRegisterCallbacks(_nativeApplication, requestHandler, shutdownHandler, onAsyncCompletion, requestContext, shutdownContext); + NativeMethods.HttpRegisterCallbacks( + _nativeApplication, + requestHandler, + shutdownHandler, + onAsyncCompletion, + requestContext, + shutdownContext, + out var resetStandardStreams); + + if (resetStandardStreams) + { + ResetStdOutHandles(); + } + } + + private static void ResetStdOutHandles() + { + // By using the PipeOutputRedirection, after calling RegisterCallbacks, + // stdout and stderr will be redirected to NULL. However, if something wrote + // to stdout before redirecting, (like a Console.WriteLine during startup), + // we need to call Console.Set* to pick up the modified consoles outputs. + Console.SetOut(CreateStreamWriter(Console.OpenStandardOutput())); + Console.SetError(CreateStreamWriter(Console.OpenStandardError())); + } + + private static StreamWriter CreateStreamWriter(Stream stdStream) + { + return new StreamWriter( + stdStream, + encoding: Console.OutputEncoding, + bufferSize: 256, + leaveOpen: true) + { AutoFlush = true }; } public void Dispose() diff --git a/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs b/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs index de7bcb0562..a21ddcbac5 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs @@ -53,7 +53,13 @@ namespace Microsoft.AspNetCore.Server.IIS private static extern void http_indicate_completion(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS notificationStatus); [DllImport(AspNetCoreModuleDll)] - private static extern int register_callbacks(IntPtr pInProcessApplication, PFN_REQUEST_HANDLER requestCallback, PFN_SHUTDOWN_HANDLER shutdownCallback, PFN_ASYNC_COMPLETION asyncCallback, IntPtr pvRequestContext, IntPtr pvShutdownContext); + private static extern int register_callbacks(IntPtr pInProcessApplication, + PFN_REQUEST_HANDLER requestCallback, + PFN_SHUTDOWN_HANDLER shutdownCallback, + PFN_ASYNC_COMPLETION asyncCallback, + IntPtr pvRequestContext, + IntPtr pvShutdownContext, + out bool resetStandardStreams); [DllImport(AspNetCoreModuleDll)] private static extern unsafe int http_write_response_bytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected); @@ -135,9 +141,15 @@ namespace Microsoft.AspNetCore.Server.IIS Validate(http_set_completion_status(pInProcessHandler, rquestNotificationStatus)); } - public static void HttpRegisterCallbacks(IntPtr pInProcessApplication, PFN_REQUEST_HANDLER requestCallback, PFN_SHUTDOWN_HANDLER shutdownCallback, PFN_ASYNC_COMPLETION asyncCallback, IntPtr pvRequestContext, IntPtr pvShutdownContext) + public static void HttpRegisterCallbacks(IntPtr pInProcessApplication, + PFN_REQUEST_HANDLER requestCallback, + PFN_SHUTDOWN_HANDLER shutdownCallback, + PFN_ASYNC_COMPLETION asyncCallback, + IntPtr pvRequestContext, + IntPtr pvShutdownContext, + out bool resetStandardStreams) { - Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, asyncCallback, pvRequestContext, pvShutdownContext)); + Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, asyncCallback, pvRequestContext, pvShutdownContext, out resetStandardStreams)); } public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected) diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs index afa28dd4e3..d0c0bfb153 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs @@ -231,7 +231,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting pool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem; pool.ManagedRuntimeVersion = string.Empty; pool.StartMode = StartMode.AlwaysRunning; - AddEnvironmentVariables(contentRoot, pool); _logger.LogInformation($"Configured AppPool {AppPoolName}"); diff --git a/test/Common.FunctionalTests/Inprocess/LoggingTests.cs b/test/Common.FunctionalTests/Inprocess/LoggingTests.cs index 53009a969c..6384fd508e 100644 --- a/test/Common.FunctionalTests/Inprocess/LoggingTests.cs +++ b/test/Common.FunctionalTests/Inprocess/LoggingTests.cs @@ -19,55 +19,58 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalTheory] [InlineData("CheckErrLogFile")] [InlineData("CheckLogFile")] - public async Task CheckStdoutLogging(string path) + public async Task CheckStdoutLoggingToFile(string path) { var deploymentParameters = Helpers.GetBaseDeploymentParameters(); deploymentParameters.PublishApplicationBeforeDeployment = true; - deploymentParameters.PreservePublishedApplicationForDebugging = true; // workaround for keeping var deploymentResult = await DeployAsync(deploymentParameters); + var pathToLogs = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); try { Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogEnabled", "true"); - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogFile", @".\logs\stdout"); + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogFile", Path.Combine(pathToLogs, "std")); - var response = await deploymentResult.RetryingHttpClient.GetAsync(path); - - var responseText = await response.Content.ReadAsStringAsync(); - - Assert.Equal("Hello World", responseText); + await Helpers.AssertStarts(deploymentResult, path); StopServer(); - var folderPath = Path.Combine(deploymentResult.DeploymentResult.ContentRoot, @"logs"); + var fileInDirectory = Directory.GetFiles(pathToLogs).Single(); - var fileInDirectory = Directory.GetFiles(folderPath).Single(); - Assert.NotNull(fileInDirectory); - - string contents = null; - - // RetryOperation doesn't support async lambdas, call synchronous ReadAllText. - RetryHelper.RetryOperation( - () => contents = File.ReadAllText(fileInDirectory), - e => Logger.LogError($"Failed to read file: {e.Message}"), - retryCount: 10, - retryDelayMilliseconds: 100); + var contents = File.ReadAllText(fileInDirectory); Assert.NotNull(contents); Assert.Contains("TEST MESSAGE", contents); + Assert.DoesNotContain(TestSink.Writes, context => context.Message.Contains("TEST MESSAGE")); + // TODO we should check that debug logs are restored during graceful shutdown. + // The IIS Express deployer doesn't support graceful shutdown. + //Assert.Contains(TestSink.Writes, context => context.Message.Contains("Restoring original stdout: ")); } finally { RetryHelper.RetryOperation( - () => Directory.Delete(deploymentParameters.PublishedApplicationRootPath, true), + () => Directory.Delete(pathToLogs, true), e => Logger.LogWarning($"Failed to delete directory : {e.Message}"), retryCount: 3, retryDelayMilliseconds: 100); } } + [ConditionalFact] + public async Task InvalidFilePathForLogs_ServerStillRuns() + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogEnabled", "true"); + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogFile", Path.Combine("Q:", "std")); + + await Helpers.AssertStarts(deploymentResult, "HelloWorld"); + } + [ConditionalFact] [RequiresIIS(IISCapability.PoolEnvironmentVariables)] public async Task StartupMessagesAreLoggedIntoDebugLogFile() @@ -96,6 +99,48 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } } + [ConditionalTheory] + [InlineData("CheckErrLogFile")] + [InlineData("CheckLogFile")] + public async Task CheckStdoutLoggingToPipe_DoesNotCrashProcess(string path) + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); + var deploymentResult = await DeployAsync(deploymentParameters); + + await Helpers.AssertStarts(deploymentResult, path); + + StopServer(); + + if (deploymentParameters.ServerType == ServerType.IISExpress) + { + Assert.Contains(TestSink.Writes, context => context.Message.Contains("TEST MESSAGE")); + } + } + + [ConditionalTheory] + [InlineData("CheckErrLogFile")] + [InlineData("CheckLogFile")] + public async Task CheckStdoutLoggingToPipeWithFirstWrite(string path) + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + var firstWriteString = path + path; + + Helpers.ModifyEnvironmentVariableCollectionInWebConfig(deploymentResult, "ASPNETCORE_INPROCESS_INITIAL_WRITE", firstWriteString); + + await Helpers.AssertStarts(deploymentResult, path); + + StopServer(); + + if (deploymentParameters.ServerType == ServerType.IISExpress) + { + // We can't read stdout logs from IIS as they aren't redirected. + Assert.Contains(TestSink.Writes, context => context.Message.Contains(firstWriteString)); + Assert.Contains(TestSink.Writes, context => context.Message.Contains("TEST MESSAGE")); + } + } + [ConditionalFact] [RequiresIIS(IISCapability.PoolEnvironmentVariables)] public async Task StartupMessagesLogFileSwitchedWhenLogFilePresentInWebConfig() diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index 0480351ce1..878ab06272 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -41,6 +41,22 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests config.Save(webConfigFile); } + public static void ModifyEnvironmentVariableCollectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value) + { + var webConfigFile = GetWebConfigFile(deploymentResult); + var config = XDocument.Load(webConfigFile); + + var envVarElement = new XElement("environmentVariable"); + envVarElement.SetAttributeValue("name", key); + envVarElement.SetAttributeValue("value", value); + + config.Descendants("aspNetCore").Single() + .Descendants("environmentVariables").Single() + .Add(envVarElement); + + config.Save(webConfigFile); + } + public static void ModifyHandlerSectionInWebConfig(IISDeploymentResult deploymentResult, string handlerVersionValue) { var webConfigFile = GetWebConfigFile(deploymentResult); diff --git a/test/WebSites/InProcessWebSite/Program.cs b/test/WebSites/InProcessWebSite/Program.cs index 2e5e8f5d6a..ec9ed04f87 100644 --- a/test/WebSites/InProcessWebSite/Program.cs +++ b/test/WebSites/InProcessWebSite/Program.cs @@ -1,6 +1,7 @@ // 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 Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; @@ -10,6 +11,13 @@ namespace IISTestSite { public static void Main(string[] args) { + var envVariable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_INITIAL_WRITE"); + if (!string.IsNullOrEmpty(envVariable)) + { + Console.WriteLine(envVariable); + Console.Error.WriteLine(envVariable); + } + var host = new WebHostBuilder() .ConfigureLogging((_, factory) => { diff --git a/test/WebSites/InProcessWebSite/Startup.cs b/test/WebSites/InProcessWebSite/Startup.cs index 2bf89384f8..f42e220751 100644 --- a/test/WebSites/InProcessWebSite/Startup.cs +++ b/test/WebSites/InProcessWebSite/Startup.cs @@ -753,6 +753,5 @@ namespace IISTestSite } await context.Response.WriteAsync("Response End"); } - } } From 5c2df180fcddbbc5c85aa6310931e86620c87afe Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 15 Jul 2018 12:16:08 -0700 Subject: [PATCH 3/7] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 70 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 9e7e524c41..fb4b95edd2 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,47 +5,47 @@ 0.10.13 2.2.0-preview1-17099 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 0.6.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 0.6.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 15.6.82 15.6.82 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.0.7 - 2.1.1 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.0.9 + 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34640 + 2.2.0-preview1-34694 15.6.1 11.1.0 2.0.3 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 + 4.5.0 + 4.5.0 + 4.5.0 + 4.5.1 + 4.5.1 + 4.5.0 + 4.5.1 + 4.5.0 9.0.1 2.3.1 2.4.0-rc.1.build4038 From 074264cd3a1a1558f9a88f3c580df915f086f516 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 08:56:18 -0700 Subject: [PATCH 4/7] Use less global variables (#1064) --- .../AspNetCore/applicationinfo.cpp | 69 +++++++++---------- .../AspNetCore/applicationinfo.h | 23 +++++-- .../AspNetCore/applicationmanager.cpp | 55 +++++---------- .../AspNetCore/applicationmanager.h | 33 +++++---- src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp | 24 +------ .../AspNetCore/globalmodule.cpp | 2 + .../AspNetCore/proxymodule.cpp | 4 +- .../AspNetCore/proxymodule.h | 2 - src/AspNetCoreModuleV2/AspNetCore/stdafx.h | 16 +---- src/AspNetCoreModuleV2/CommonLib/EventLog.h | 2 + 10 files changed, 100 insertions(+), 130 deletions(-) diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index 2072e33af6..edfc085f22 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -12,12 +12,21 @@ #include "SRWExclusiveLock.h" #include "GlobalVersionUtility.h" #include "exceptions.h" -#include "HandleWrapper.h" #include "PollingAppOfflineApplication.h" +#include "EventLog.h" + +extern HINSTANCE g_hModule; const PCWSTR APPLICATION_INFO::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll"; const PCWSTR APPLICATION_INFO::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll"; +SRWLOCK APPLICATION_INFO::s_requestHandlerLoadLock {}; +bool APPLICATION_INFO::s_fAspnetcoreRHAssemblyLoaded = false; +bool APPLICATION_INFO::s_fAspnetcoreRHLoadedError = false; +HMODULE APPLICATION_INFO::s_hAspnetCoreRH = nullptr; + +PFN_ASPNETCORE_CREATE_APPLICATION APPLICATION_INFO::s_pfnAspNetCoreCreateApplication = nullptr; + APPLICATION_INFO::~APPLICATION_INFO() { if (m_pApplication != NULL) @@ -39,23 +48,14 @@ APPLICATION_INFO::~APPLICATION_INFO() HRESULT APPLICATION_INFO::Initialize( - _In_ IHttpServer *pServer, - _In_ IHttpApplication *pApplication + _In_ IHttpApplication &pApplication ) { - HRESULT hr = S_OK; + m_pConfiguration = new ASPNETCORE_SHIM_CONFIG(); + RETURN_IF_FAILED(m_pConfiguration->Populate(&m_pServer, &pApplication)); + RETURN_IF_FAILED(m_struInfoKey.Copy(pApplication.GetApplicationId())); - DBG_ASSERT(pServer); - DBG_ASSERT(pApplication); - - // todo: make sure Initialize should be called only once - m_pServer = pServer; - FINISHED_IF_NULL_ALLOC(m_pConfiguration = new ASPNETCORE_SHIM_CONFIG()); - FINISHED_IF_FAILED(m_pConfiguration->Populate(m_pServer, pApplication)); - FINISHED_IF_FAILED(m_struInfoKey.Copy(pApplication->GetApplicationId())); - -Finished: - return hr; + return S_OK; } @@ -125,7 +125,7 @@ APPLICATION_INFO::EnsureApplicationCreated( }; LOG_INFO("Creating handler application"); FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication( - m_pServer, + &m_pServer, pHttpContext->GetApplication(), parameters.data(), static_cast(parameters.size()), @@ -157,19 +157,18 @@ APPLICATION_INFO::FindRequestHandlerAssembly(STRU& location) PCWSTR pstrHandlerDllName; STACK_STRU(struFileName, 256); - if (g_fAspnetcoreRHLoadedError) + if (s_fAspnetcoreRHLoadedError) { FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } - else if (!g_fAspnetcoreRHAssemblyLoaded) + else if (!s_fAspnetcoreRHAssemblyLoaded) { - SRWExclusiveLock lock(g_srwLock); - - if (g_fAspnetcoreRHLoadedError) + SRWExclusiveLock lock(s_requestHandlerLoadLock); + if (s_fAspnetcoreRHLoadedError) { FINISHED(E_APPLICATION_ACTIVATION_EXEC_FAILURE); } - if (g_fAspnetcoreRHAssemblyLoaded) + if (s_fAspnetcoreRHAssemblyLoaded) { FINISHED(S_OK); } @@ -184,9 +183,9 @@ APPLICATION_INFO::FindRequestHandlerAssembly(STRU& location) } // Try to see if RH is already loaded - g_hAspnetCoreRH = GetModuleHandle(pstrHandlerDllName); + s_hAspnetCoreRH = GetModuleHandle(pstrHandlerDllName); - if (g_hAspnetCoreRH == NULL) + if (s_hAspnetCoreRH == NULL) { if (m_pConfiguration->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) { @@ -229,22 +228,22 @@ APPLICATION_INFO::FindRequestHandlerAssembly(STRU& location) WLOG_INFOF(L"Loading request handler: %s", struFileName.QueryStr()); - g_hAspnetCoreRH = LoadLibraryW(struFileName.QueryStr()); + s_hAspnetCoreRH = LoadLibraryW(struFileName.QueryStr()); - if (g_hAspnetCoreRH == NULL) + if (s_hAspnetCoreRH == NULL) { FINISHED(HRESULT_FROM_WIN32(GetLastError())); } } - g_pfnAspNetCoreCreateApplication = (PFN_ASPNETCORE_CREATE_APPLICATION) - GetProcAddress(g_hAspnetCoreRH, "CreateApplication"); - if (g_pfnAspNetCoreCreateApplication == NULL) + s_pfnAspNetCoreCreateApplication = (PFN_ASPNETCORE_CREATE_APPLICATION) + GetProcAddress(s_hAspnetCoreRH, "CreateApplication"); + if (s_pfnAspNetCoreCreateApplication == NULL) { FINISHED(HRESULT_FROM_WIN32(GetLastError())); } - g_fAspnetcoreRHAssemblyLoaded = TRUE; + s_fAspnetcoreRHAssemblyLoaded = TRUE; } Finished: @@ -252,11 +251,11 @@ Finished: // Question: we remember the load failure so that we will not try again. // User needs to check whether the fuction pointer is NULL // - m_pfnAspNetCoreCreateApplication = g_pfnAspNetCoreCreateApplication; + m_pfnAspNetCoreCreateApplication = s_pfnAspNetCoreCreateApplication; - if (!g_fAspnetcoreRHLoadedError && FAILED(hr)) + if (!s_fAspnetcoreRHLoadedError && FAILED(hr)) { - g_fAspnetcoreRHLoadedError = TRUE; + s_fAspnetcoreRHLoadedError = TRUE; } return hr; } @@ -457,7 +456,7 @@ APPLICATION_INFO::RecycleApplication() if (m_pConfiguration->QueryHostingModel() == HOSTING_IN_PROCESS) { // In process application failed to start for whatever reason, need to recycle the work process - m_pServer->RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); + m_pServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); } } @@ -466,7 +465,7 @@ APPLICATION_INFO::RecycleApplication() if (!g_fRecycleProcessCalled) { g_fRecycleProcessCalled = TRUE; - g_pHttpServer->RecycleProcess(L"On Demand by AspNetCore Module for recycle application failure"); + m_pServer.RecycleProcess(L"On Demand by AspNetCore Module for recycle application failure"); } } else diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index 3d4b901755..49badd578a 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -23,14 +23,13 @@ HRESULT ); extern BOOL g_fRecycleProcessCalled; -extern PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication; class APPLICATION_INFO { public: - APPLICATION_INFO() : - m_pServer(NULL), + APPLICATION_INFO(_In_ IHttpServer &pServer) : + m_pServer(pServer), m_cRefs(1), m_fValid(FALSE), m_fAppCreationAttempted(FALSE), @@ -48,11 +47,17 @@ public: virtual ~APPLICATION_INFO(); + + static + void + StaticInitialize() + { + InitializeSRWLock(&s_requestHandlerLoadLock); + } HRESULT Initialize( - _In_ IHttpServer *pServer, - _In_ IHttpApplication *pApplication + _In_ IHttpApplication &pApplication ); VOID @@ -125,11 +130,17 @@ private: ASPNETCORE_SHIM_CONFIG *m_pConfiguration; IAPPLICATION *m_pApplication; SRWLOCK m_srwLock; - IHttpServer *m_pServer; + IHttpServer &m_pServer; PFN_ASPNETCORE_CREATE_APPLICATION m_pfnAspNetCoreCreateApplication; static const PCWSTR s_pwzAspnetcoreInProcessRequestHandlerName; static const PCWSTR s_pwzAspnetcoreOutOfProcessRequestHandlerName; + + static SRWLOCK s_requestHandlerLoadLock; + static bool s_fAspnetcoreRHAssemblyLoaded; + static bool s_fAspnetcoreRHLoadedError; + static HMODULE s_hAspnetCoreRH; + static PFN_ASPNETCORE_CREATE_APPLICATION s_pfnAspNetCoreCreateApplication; }; class APPLICATION_INFO_HASH : diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp index c65222b10e..a7f08e5d3e 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -8,6 +8,9 @@ #include "resources.h" #include "SRWExclusiveLock.h" #include "exceptions.h" +#include "EventLog.h" + +extern BOOL g_fInShutdown; // The application manager is a singleton across ANCM. APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; @@ -18,7 +21,6 @@ APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; // HRESULT APPLICATION_MANAGER::GetOrCreateApplicationInfo( - _In_ IHttpServer* pServer, _In_ IHttpContext* pHttpContext, _Out_ APPLICATION_INFO ** ppApplicationInfo ) @@ -32,56 +34,37 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( STACK_STRU ( strEventMsg, 256 ); - DBG_ASSERT(pServer); DBG_ASSERT(pHttpContext); DBG_ASSERT(ppApplicationInfo); *ppApplicationInfo = NULL; + IHttpApplication &pApplication = *pHttpContext->GetApplication(); - if (!m_fDebugInitialize) - { - SRWExclusiveLock lock(m_srwLock); - if (!m_fDebugInitialize) - { - DebugInitializeFromConfig(*pServer, *pHttpContext->GetApplication()); - m_fDebugInitialize = TRUE; - } - } // The configuration path is unique for each application and is used for the // key in the applicationInfoHash. - pszApplicationId = pHttpContext->GetApplication()->GetApplicationId(); + pszApplicationId = pApplication.GetApplicationId(); // When accessing the m_pApplicationInfoHash, we need to acquire the application manager // lock to avoid races on setting state. + SRWSharedLock lock(m_srwLock); + if (!m_fDebugInitialize) { - SRWSharedLock lock(m_srwLock); - if (g_fInShutdown) - { - FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); - } - m_pApplicationInfoHash->FindKey(pszApplicationId, ppApplicationInfo); + DebugInitializeFromConfig(m_pHttpServer, pApplication); + m_fDebugInitialize = TRUE; } + if (g_fInShutdown) + { + FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); + } + + m_pApplicationInfoHash->FindKey(pszApplicationId, ppApplicationInfo); + if (*ppApplicationInfo == NULL) { - pApplicationInfo = new APPLICATION_INFO(); + pApplicationInfo = new APPLICATION_INFO(m_pHttpServer); - FINISHED_IF_FAILED(pApplicationInfo->Initialize(pServer, pHttpContext->GetApplication())); - - SRWExclusiveLock lock(m_srwLock); - - if (g_fInShutdown) - { - // Already in shuting down. No need to create the application - FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); - } - m_pApplicationInfoHash->FindKey(pszApplicationId, ppApplicationInfo); - - if (*ppApplicationInfo != NULL) - { - // someone else created the application - FINISHED(S_OK); - } + FINISHED_IF_FAILED(pApplicationInfo->Initialize(pApplication)); hostingModel = pApplicationInfo->QueryConfig()->QueryHostingModel(); @@ -309,7 +292,7 @@ Finished: if (!g_fRecycleProcessCalled) { g_fRecycleProcessCalled = TRUE; - g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand Due Application Recycle Error"); + m_pHttpServer.RecycleProcess(L"AspNetCore Recycle Process on Demand Due Application Recycle Error"); } } diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h index cd565e2c01..f01df15844 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h @@ -27,15 +27,9 @@ public: static APPLICATION_MANAGER* - GetInstance( - VOID - ) + GetInstance() { - if ( sm_pApplicationManager == NULL ) - { - sm_pApplicationManager = new APPLICATION_MANAGER(); - } - + assert(sm_pApplicationManager); return sm_pApplicationManager; } @@ -68,7 +62,6 @@ public: HRESULT GetOrCreateApplicationInfo( - _In_ IHttpServer* pServer, _In_ IHttpContext* pHttpContext, _Out_ APPLICATION_INFO ** ppApplicationInfo ); @@ -92,6 +85,16 @@ public: } } + static HRESULT StaticInitialize(IHttpServer& pHttpServer) + { + assert(!sm_pApplicationManager); + sm_pApplicationManager = new APPLICATION_MANAGER(pHttpServer); + RETURN_IF_FAILED(sm_pApplicationManager->Initialize()); + + APPLICATION_INFO::StaticInitialize(); + return S_OK; + } + HRESULT Initialize() { if(m_pApplicationInfoHash == NULL) @@ -107,12 +110,11 @@ public: } private: - // - // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload - // - APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL), + APPLICATION_MANAGER(IHttpServer& pHttpServer) : + m_pApplicationInfoHash(NULL), m_hostingModel(HOSTING_UNKNOWN), - m_fDebugInitialize(FALSE) + m_fDebugInitialize(FALSE), + m_pHttpServer(pHttpServer) { InitializeSRWLock(&m_srwLock); } @@ -120,6 +122,7 @@ private: APPLICATION_INFO_HASH *m_pApplicationInfoHash; static APPLICATION_MANAGER *sm_pApplicationManager; SRWLOCK m_srwLock; - APP_HOSTING_MODEL m_hostingModel; + APP_HOSTING_MODEL m_hostingModel; BOOL m_fDebugInitialize; + IHttpServer &m_pHttpServer; }; diff --git a/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp b/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp index d185ad4be4..5369fbc527 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp @@ -13,20 +13,10 @@ DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2.dll"); -HTTP_MODULE_ID g_pModuleId = NULL; -IHttpServer * g_pHttpServer = NULL; HANDLE g_hEventLog = NULL; BOOL g_fRecycleProcessCalled = FALSE; -PCWSTR g_pszModuleName = NULL; HINSTANCE g_hModule; -HMODULE g_hAspnetCoreRH = NULL; -BOOL g_fAspnetcoreRHAssemblyLoaded = FALSE; -BOOL g_fAspnetcoreRHLoadedError = FALSE; BOOL g_fInShutdown = FALSE; -DWORD g_dwActiveServerProcesses = 0; -SRWLOCK g_srwLock; - -PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication; VOID StaticCleanup() @@ -100,17 +90,10 @@ HRESULT BOOL fDisableANCM = FALSE; ASPNET_CORE_PROXY_MODULE_FACTORY * pFactory = NULL; ASPNET_CORE_GLOBAL_MODULE * pGlobalModule = NULL; - APPLICATION_MANAGER * pApplicationManager = NULL; UNREFERENCED_PARAMETER(dwServerVersion); - InitializeSRWLock(&g_srwLock); - - g_pModuleId = pModuleInfo->GetId(); - g_pszModuleName = pModuleInfo->GetName(); - g_pHttpServer = pHttpServer; - - if (g_pHttpServer->IsCommandLineLaunch()) + if (pHttpServer->IsCommandLineLaunch()) { g_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); } @@ -175,13 +158,12 @@ HRESULT 0)); pFactory = NULL; - pApplicationManager = APPLICATION_MANAGER::GetInstance(); - FINISHED_IF_FAILED(pApplicationManager->Initialize()); + FINISHED_IF_FAILED(APPLICATION_MANAGER::StaticInitialize(*pHttpServer)); pGlobalModule = NULL; - pGlobalModule = new ASPNET_CORE_GLOBAL_MODULE(pApplicationManager); + pGlobalModule = new ASPNET_CORE_GLOBAL_MODULE(APPLICATION_MANAGER::GetInstance()); FINISHED_IF_FAILED(pModuleInfo->SetGlobalNotifications( pGlobalModule, diff --git a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp index 05dee36b15..0ecbba4afb 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp @@ -3,6 +3,8 @@ #include "globalmodule.h" +extern BOOL g_fInShutdown; + ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE( APPLICATION_MANAGER* pApplicationManager) { diff --git a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp index f27a1571c3..962d7202d1 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp @@ -7,6 +7,9 @@ #include "applicationinfo.h" #include "acache.h" #include "exceptions.h" + +extern BOOL g_fInShutdown; + __override HRESULT ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule( @@ -83,7 +86,6 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( pApplicationManager = APPLICATION_MANAGER::GetInstance(); FINISHED_IF_FAILED(pApplicationManager->GetOrCreateApplicationInfo( - g_pHttpServer, pHttpContext, &m_pApplicationInfo)); diff --git a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h index 5864dd0c6f..a66a0fd3e1 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h +++ b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h @@ -8,8 +8,6 @@ #include "irequesthandler.h" extern HTTP_MODULE_ID g_pModuleId; -extern IHttpServer *g_pHttpServer; -extern HMODULE g_hAspnetCoreRH; class ASPNET_CORE_PROXY_MODULE : public CHttpModule { diff --git a/src/AspNetCoreModuleV2/AspNetCore/stdafx.h b/src/AspNetCoreModuleV2/AspNetCore/stdafx.h index 1191022c01..29c485bc9c 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/stdafx.h +++ b/src/AspNetCoreModuleV2/AspNetCore/stdafx.h @@ -9,8 +9,8 @@ // #define WIN32_LEAN_AND_MEAN -#define NTDDI_VERSION 0x06010000 -#define WINVER 0x0601 +#define NTDDI_VERSION NTDDI_WIN7 +#define WINVER _WIN32_WINNT_WIN7 #define _WIN32_WINNT 0x0601 #include @@ -20,16 +20,4 @@ #include "stringu.h" #include "stringa.h" -extern PVOID g_pModuleId; -extern BOOL g_fAspnetcoreRHAssemblyLoaded; -extern BOOL g_fAspnetcoreRHLoadedError; -extern BOOL g_fInShutdown; -extern BOOL g_fEnableReferenceCountTracing; -extern DWORD g_dwActiveServerProcesses; -extern HINSTANCE g_hModule; -extern HMODULE g_hAspnetCoreRH; -extern SRWLOCK g_srwLock; -extern PCWSTR g_pwzAspnetcoreRequestHandlerName; -extern HANDLE g_hEventLog; - #pragma warning( error : 4091) diff --git a/src/AspNetCoreModuleV2/CommonLib/EventLog.h b/src/AspNetCoreModuleV2/CommonLib/EventLog.h index 8782eff6d4..3efbe38b35 100644 --- a/src/AspNetCoreModuleV2/CommonLib/EventLog.h +++ b/src/AspNetCoreModuleV2/CommonLib/EventLog.h @@ -6,4 +6,6 @@ #include "Utility.h" #include "resources.h" +extern HANDLE g_hEventLog; + #define EVENTLOG(log, name, ...) UTILITY::LogEventF(log, ASPNETCORE_EVENT_ ## name ## _LEVEL, ASPNETCORE_EVENT_ ## name, ASPNETCORE_EVENT_ ## name ## _MSG, __VA_ARGS__) From b741743537abb054bb30e104643c1d92bfed314d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 17 Jul 2018 13:03:04 -0700 Subject: [PATCH 5/7] Set IsMet and SkipReason with empty constructor (#1070) --- test/IIS.FunctionalTests/RequiresIISAttribute.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/IIS.FunctionalTests/RequiresIISAttribute.cs b/test/IIS.FunctionalTests/RequiresIISAttribute.cs index f55f397cfa..de0e4eb805 100644 --- a/test/IIS.FunctionalTests/RequiresIISAttribute.cs +++ b/test/IIS.FunctionalTests/RequiresIISAttribute.cs @@ -92,7 +92,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } } - public RequiresIISAttribute() { } + public RequiresIISAttribute() + : this (IISCapability.None) { } public RequiresIISAttribute(IISCapability capabilities) { From 54471a293030d3dd6d02caeeedaf5542436f3896 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 20:26:57 -0700 Subject: [PATCH 6/7] Add client abort tests (#1051) --- IISIntegration.NoV1.sln | 30 +++ IISIntegration.sln | 30 +++ .../IIS.Performance/IIS.Performance.csproj | 1 + .../AspNetCore/globalmodule.cpp | 4 + .../AspNetCore/globalmodule.h | 1 + .../CommonLib/debugutil.cpp | 6 +- .../inprocessapplication.cpp | 5 + .../RequestHandlerLib/filewatcher.cpp | 4 + .../Core/IISHttpContext.ReadWrite.cs | 9 +- .../Core/IISHttpContext.cs | 11 +- .../Core/IISHttpContextOfT.cs | 3 +- .../Core/IO/AsyncIOOperation.cs | 4 +- .../Core/OutputProducer.cs | 1 + .../NativeMethods.cs | 1 + .../Inprocess/ClientDisconnectTests.cs | 47 +++++ .../Utilities/Helpers.cs | 6 +- test/Common.Tests/Common.Tests.csproj | 23 +++ .../Utilities/LoggingHandler.cs | 0 .../Utilities/RetryHandler.cs | 0 .../Utilities/TestConnections.cs | 0 .../Utilities/TimeoutExtensions.cs | 21 +++ .../IIS.FunctionalTests.csproj | 1 + .../AppHostConfig/HostableWebCore.config | 5 +- test/IIS.Tests/ClientDisconnectTests.cs | 177 ++++++++++++++++++ test/IIS.Tests/IIS.Tests.csproj | 36 ++++ .../InProcess => IIS.Tests}/TestServerTest.cs | 15 +- ...pIfHostableWebCoreNotAvailibleAttribute.cs | 4 +- .../Utilities/TestServer.cs | 30 ++- .../IISExpress.FunctionalTests.csproj | 1 + .../InProcess/AppOfflineTests.cs | 2 +- .../InProcess/ShutdownTests.cs | 3 +- 31 files changed, 444 insertions(+), 37 deletions(-) create mode 100644 test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs create mode 100644 test/Common.Tests/Common.Tests.csproj rename test/{Common.FunctionalTests => Common.Tests}/Utilities/LoggingHandler.cs (100%) rename test/{Common.FunctionalTests => Common.Tests}/Utilities/RetryHandler.cs (100%) rename test/{Common.FunctionalTests => Common.Tests}/Utilities/TestConnections.cs (100%) create mode 100644 test/Common.Tests/Utilities/TimeoutExtensions.cs rename test/{Common.FunctionalTests => IIS.Tests}/AppHostConfig/HostableWebCore.config (97%) create mode 100644 test/IIS.Tests/ClientDisconnectTests.cs create mode 100644 test/IIS.Tests/IIS.Tests.csproj rename test/{IISExpress.FunctionalTests/InProcess => IIS.Tests}/TestServerTest.cs (72%) rename test/{Common.FunctionalTests => IIS.Tests}/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs (82%) rename test/{Common.FunctionalTests => IIS.Tests}/Utilities/TestServer.cs (80%) diff --git a/IISIntegration.NoV1.sln b/IISIntegration.NoV1.sln index b9b82faf5a..09ad7eac29 100644 --- a/IISIntegration.NoV1.sln +++ b/IISIntegration.NoV1.sln @@ -113,6 +113,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.FunctionalTests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IntegrationTesting.IIS", "src\Microsoft.AspNetCore.Server.IntegrationTesting.IIS\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj", "{34135ED7-313D-4E68-860C-D6B51AA28523}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.Tests", "test\IIS.Tests\IIS.Tests.csproj", "{C0310D84-BC2F-4B2E-870E-D35044DB3E3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Tests", "test\Common.Tests\Common.Tests.csproj", "{D17B7B35-5361-4A50-B499-E03E5C3CC095}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -381,6 +385,30 @@ Global {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x64.Build.0 = Release|Any CPU {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x86.ActiveCfg = Release|Any CPU {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x86.Build.0 = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x64.Build.0 = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x86.Build.0 = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|Any CPU.Build.0 = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x64.ActiveCfg = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x64.Build.0 = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x86.ActiveCfg = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x86.Build.0 = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x64.ActiveCfg = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x64.Build.0 = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x86.ActiveCfg = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x86.Build.0 = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|Any CPU.Build.0 = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x64.ActiveCfg = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x64.Build.0 = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x86.ActiveCfg = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -411,6 +439,8 @@ Global {1533E271-F61B-441B-8B74-59FB61DF0552} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} {D182103F-8405-4647-B158-C36F598657EF} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {34135ED7-313D-4E68-860C-D6B51AA28523} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {D17B7B35-5361-4A50-B499-E03E5C3CC095} = {EF30B533-D715-421A-92B7-92FEF460AC9C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/IISIntegration.sln b/IISIntegration.sln index c21a81482f..ba7d809f5f 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -120,6 +120,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.FunctionalTests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IntegrationTesting.IIS", "src\Microsoft.AspNetCore.Server.IntegrationTesting.IIS\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj", "{CE4FB142-91FB-4B34-BC96-A31120EF4009}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.Tests", "test\IIS.Tests\IIS.Tests.csproj", "{A091777D-66B3-42E1-B95C-85322DE40706}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Tests", "test\Common.Tests\Common.Tests.csproj", "{A641A208-2974-4E48-BCFF-54E3AAFA4FB9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -408,6 +412,30 @@ Global {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x64.Build.0 = Release|Any CPU {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x86.ActiveCfg = Release|Any CPU {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x86.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x64.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x64.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x86.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x86.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|Any CPU.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x64.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x64.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x86.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x86.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x64.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x64.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x86.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x86.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|Any CPU.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x64.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x64.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x86.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -441,6 +469,8 @@ Global {1533E271-F61B-441B-8B74-59FB61DF0552} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {CE4FB142-91FB-4B34-BC96-A31120EF4009} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {A091777D-66B3-42E1-B95C-85322DE40706} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9} = {EF30B533-D715-421A-92B7-92FEF460AC9C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/benchmarks/IIS.Performance/IIS.Performance.csproj b/benchmarks/IIS.Performance/IIS.Performance.csproj index 87102ca7ed..b7aa1dc3ac 100644 --- a/benchmarks/IIS.Performance/IIS.Performance.csproj +++ b/benchmarks/IIS.Performance/IIS.Performance.csproj @@ -25,6 +25,7 @@ + False diff --git a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp index 0ecbba4afb..0a9c0ccf02 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp @@ -22,6 +22,8 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening( { UNREFERENCED_PARAMETER(pProvider); + LOG_INFO("ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening"); + if (g_fInShutdown) { // Avoid receiving two shutudown notifications. @@ -54,6 +56,8 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange( // Retrieve the path that has changed. PCWSTR pwszChangePath = pProvider->GetChangePath(); + LOG_INFOF("ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange %S", pwszChangePath); + // Test for an error. if (NULL != pwszChangePath && _wcsicmp(pwszChangePath, L"MACHINE") != 0 && diff --git a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h index 0a7b38f0ff..b790ebb0f5 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h +++ b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h @@ -20,6 +20,7 @@ public: VOID Terminate() { + LOG_INFO("ASPNET_CORE_GLOBAL_MODULE::Terminate"); // Remove the class from memory. delete this; } diff --git a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp index 28bc775dfa..9d7ec40ebb 100644 --- a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp @@ -26,7 +26,6 @@ DebugInitialize() HKEY hKey; InitializeSRWLock(&g_logFileLock); - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", 0, @@ -71,6 +70,11 @@ DebugInitialize() { // ignore } + + if (IsDebuggerPresent()) + { + DEBUG_FLAGS_VAR |= DEBUG_FLAGS_INFO; + } } HRESULT diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 7e6f0ee433..baf73ec9c1 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -561,14 +561,19 @@ IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hos __try { + LOG_INFO("Starting managed application"); m_ProcessExitCode = pProc(argc, argv); if (m_ProcessExitCode != 0) { hr = HRESULT_FROM_WIN32(GetLastError()); } + + LOG_INFOF("Managed application exited with code %d", m_ProcessExitCode); } __except(GetExceptionCode() != 0) { + + LOG_INFOF("Managed threw an exception %d", GetExceptionCode()); hr = HRESULT_FROM_WIN32(GetLastError()); } diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index a35d8cb63c..37828b2623 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -3,6 +3,7 @@ #include "stdafx.h" #include "filewatcher.h" +#include "debugutil.h" FILE_WATCHER::FILE_WATCHER() : m_hCompletionPort(NULL), @@ -123,6 +124,7 @@ Win32 error DWORD dwErrorStatus; ULONG_PTR completionKey; + LOG_INFO("Starting file watcher thread"); pFileMonitor = (FILE_WATCHER*)pvArg; DBG_ASSERT(pFileMonitor != NULL); @@ -156,6 +158,8 @@ Win32 error } pFileMonitor->m_fThreadExit = TRUE; + + LOG_INFO("Stopping file watcher thread"); ExitThread(0); } diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.ReadWrite.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.ReadWrite.cs index 740c2a2d3e..26315971ef 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.ReadWrite.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.ReadWrite.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core while (true) { - var result = await _bodyInputPipe.Reader.ReadAsync(); + var result = await _bodyInputPipe.Reader.ReadAsync(cancellationToken); var readableBuffer = result.Buffer; try { @@ -158,9 +159,13 @@ namespace Microsoft.AspNetCore.Server.IIS.Core } } } + // We want to swallow IO exception and allow app to finish writing catch (Exception ex) { - _bodyOutput.Reader.Complete(ex); + if (!(ex is IOException)) + { + _bodyOutput.Reader.Complete(ex); + } } finally { diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs index 15c69432ed..ab977c4e82 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs @@ -217,7 +217,16 @@ namespace Microsoft.AspNetCore.Server.IIS.Core if (flushHeaders) { - await AsyncIO.FlushAsync(); + try + { + await AsyncIO.FlushAsync(); + } + // Client might be disconnected at this point + // don't leak the exception + catch (IOException) + { + // ignore + } } _writeBodyTask = WriteBody(!flushHeaders); diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs index f24c4fe93e..8fb2d75363 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs @@ -90,8 +90,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core await _writeBodyTask; } - // Cancell all remaining IO, thre might be reads pending if not entire request body was sent - // by client + // Cancel all remaining IO, there might be reads pending if not entire request body was sent by client AsyncIO.Dispose(); if (_readBodyTask != null) diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs index a7bea2e746..9d3752ef08 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks.Sources; @@ -101,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core.IO _result = bytes; if (hr != NativeMethods.HR_OK) { - _exception = new IOException("IO exception occurred", hr); + _exception = new IOException("Native IO operation failed", Marshal.GetExceptionForHR(hr)); } } else diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs index 4bf1816329..4b90abb006 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs @@ -121,6 +121,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core } catch (OperationCanceledException) { + _pipe.Writer.Complete(); _completed = true; throw; } diff --git a/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs b/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs index a21ddcbac5..8a6da21e8a 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs +++ b/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.IIS internal const int HR_OK = 0; internal const int ERROR_NOT_FOUND = unchecked((int)0x80070490); internal const int ERROR_OPERATION_ABORTED = unchecked((int)0x800703E3); + internal const int COR_E_IO = unchecked((int)0x80131620); private const string KERNEL32 = "kernel32.dll"; diff --git a/test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs b/test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs new file mode 100644 index 0000000000..fc5f60b26e --- /dev/null +++ b/test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs @@ -0,0 +1,47 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class ClientDisconnectTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public ClientDisconnectTests(IISTestSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ServerWorksAfterClientDisconnect() + { + using (var connection = _fixture.CreateTestConnection()) + { + var message = "Hello"; + await connection.Send( + "POST /ReadAndWriteSynchronously HTTP/1.1", + $"Content-Length: {100000}", + "Host: localhost", + "Connection: close", + "", + ""); + + await connection.Send(message); + + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + } + + var response = await _fixture.Client.GetAsync("HelloWorld"); + + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello World", responseText); + } + } +} diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index 878ab06272..1a905714b8 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -1,8 +1,6 @@ // 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.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,10 +12,8 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { - public class Helpers + public static class Helpers { - public static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(3); - public static string GetTestWebSitePath(string name) { return Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"),"test", "WebSites", name); diff --git a/test/Common.Tests/Common.Tests.csproj b/test/Common.Tests/Common.Tests.csproj new file mode 100644 index 0000000000..50b978f517 --- /dev/null +++ b/test/Common.Tests/Common.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + + + diff --git a/test/Common.FunctionalTests/Utilities/LoggingHandler.cs b/test/Common.Tests/Utilities/LoggingHandler.cs similarity index 100% rename from test/Common.FunctionalTests/Utilities/LoggingHandler.cs rename to test/Common.Tests/Utilities/LoggingHandler.cs diff --git a/test/Common.FunctionalTests/Utilities/RetryHandler.cs b/test/Common.Tests/Utilities/RetryHandler.cs similarity index 100% rename from test/Common.FunctionalTests/Utilities/RetryHandler.cs rename to test/Common.Tests/Utilities/RetryHandler.cs diff --git a/test/Common.FunctionalTests/Utilities/TestConnections.cs b/test/Common.Tests/Utilities/TestConnections.cs similarity index 100% rename from test/Common.FunctionalTests/Utilities/TestConnections.cs rename to test/Common.Tests/Utilities/TestConnections.cs diff --git a/test/Common.Tests/Utilities/TimeoutExtensions.cs b/test/Common.Tests/Utilities/TimeoutExtensions.cs new file mode 100644 index 0000000000..c2d5192dbe --- /dev/null +++ b/test/Common.Tests/Utilities/TimeoutExtensions.cs @@ -0,0 +1,21 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + + public static class TimeoutExtensions + { + public static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(300); + + public static Task TimeoutAfterDefault(this Task task, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = -1) + { + return task.TimeoutAfter(DefaultTimeout, filePath, lineNumber); + } + } +} diff --git a/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj b/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj index bbe5ffe049..319a3726b1 100644 --- a/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj +++ b/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj @@ -13,6 +13,7 @@ + False diff --git a/test/Common.FunctionalTests/AppHostConfig/HostableWebCore.config b/test/IIS.Tests/AppHostConfig/HostableWebCore.config similarity index 97% rename from test/Common.FunctionalTests/AppHostConfig/HostableWebCore.config rename to test/IIS.Tests/AppHostConfig/HostableWebCore.config index 1543ebb97c..5e994d2855 100644 --- a/test/Common.FunctionalTests/AppHostConfig/HostableWebCore.config +++ b/test/IIS.Tests/AppHostConfig/HostableWebCore.config @@ -177,8 +177,6 @@ --> - - @@ -193,8 +191,7 @@ - - + diff --git a/test/IIS.Tests/ClientDisconnectTests.cs b/test/IIS.Tests/ClientDisconnectTests.cs new file mode 100644 index 0000000000..3ede262e99 --- /dev/null +++ b/test/IIS.Tests/ClientDisconnectTests.cs @@ -0,0 +1,177 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class ClientDisconnectTests : LoggedTest + { + + [ConditionalFact] + public async Task WritesSucceedAfterClientDisconnect() + { + var requestStartedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var clientDisconnectedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestCompletedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var data = new byte[1024]; + using (var testServer = await TestServer.Create( + async ctx => + { + requestStartedCompletionSource.SetResult(true); + await clientDisconnectedCompletionSource.Task; + for (var i = 0; i < 1000; i++) + { + await ctx.Response.Body.WriteAsync(data); + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStartedCompletionSource.Task.TimeoutAfterDefault(); + } + clientDisconnectedCompletionSource.SetResult(true); + + await requestCompletedCompletionSource.Task.TimeoutAfterDefault(); + } + } + + [ConditionalFact] + public async Task ReadThrowsAfterClientDisconnect() + { + var requestStartedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestCompletedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + Exception exception = null; + + var data = new byte[1024]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + await ctx.Request.Body.ReadAsync(data); + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStartedCompletionSource.Task.TimeoutAfterDefault(); + } + + await requestCompletedCompletionSource.Task.TimeoutAfterDefault(); + } + + Assert.IsType(exception); + Assert.Equal("Native IO operation failed", exception.Message); + } + + [ConditionalFact] + public async Task WriterThrowsCancelledException() + { + var requestStartedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestCompletedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + Exception exception = null; + var cancellationTokenSource = new CancellationTokenSource(); + + var data = new byte[1024]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + while (true) + { + await ctx.Response.Body.WriteAsync(data, cancellationTokenSource.Token); + } + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + + await requestStartedCompletionSource.Task.TimeoutAfterDefault(); + cancellationTokenSource.Cancel(); + await requestCompletedCompletionSource.Task.TimeoutAfterDefault(); + } + + Assert.IsType(exception); + } + } + + [ConditionalFact] + public async Task ReaderThrowsCancelledException() + { + var requestStartedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestCompletedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + Exception exception = null; + var cancellationTokenSource = new CancellationTokenSource(); + + var data = new byte[1024]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + await ctx.Request.Body.ReadAsync(data, cancellationTokenSource.Token); + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStartedCompletionSource.Task.TimeoutAfterDefault(); + cancellationTokenSource.Cancel(); + await requestCompletedCompletionSource.Task.TimeoutAfterDefault(); + } + Assert.IsType(exception); + } + } + + private static async Task SendContentLength1Post(TestConnection connection) + { + await connection.Send( + "POST / HTTP/1.1", + "Content-Length: 1", + "Host: localhost", + "Connection: close", + "", + ""); + } + } +} diff --git a/test/IIS.Tests/IIS.Tests.csproj b/test/IIS.Tests/IIS.Tests.csproj new file mode 100644 index 0000000000..1037d93c25 --- /dev/null +++ b/test/IIS.Tests/IIS.Tests.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/IISExpress.FunctionalTests/InProcess/TestServerTest.cs b/test/IIS.Tests/TestServerTest.cs similarity index 72% rename from test/IISExpress.FunctionalTests/InProcess/TestServerTest.cs rename to test/IIS.Tests/TestServerTest.cs index a9a7b89fe7..c168af99d8 100644 --- a/test/IISExpress.FunctionalTests/InProcess/TestServerTest.cs +++ b/test/IIS.Tests/TestServerTest.cs @@ -2,31 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging.Testing; using Xunit; -using Xunit.Abstractions; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { - [SkipIfHostableWebCoreNotAvailible] - public class TestServerTest: LoggedTest + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class TestServerTest : LoggedTest { - public TestServerTest(ITestOutputHelper output = null) : base(output) - { - } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] public async Task SingleProcessTestServer_HelloWorld() { var helloWorld = "Hello World"; var expectedPath = "/Path"; string path = null; - using (var testServer = await TestServer.Create(ctx => { + using (var testServer = await TestServer.Create(ctx => + { path = ctx.Request.Path.ToString(); return ctx.Response.WriteAsync(helloWorld); }, LoggerFactory)) diff --git a/test/Common.FunctionalTests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs b/test/IIS.Tests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs similarity index 82% rename from test/Common.FunctionalTests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs rename to test/IIS.Tests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs index b21a087321..b63743f106 100644 --- a/test/Common.FunctionalTests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs +++ b/test/IIS.Tests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs @@ -8,10 +8,10 @@ using Microsoft.AspNetCore.Testing.xunit; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] - public sealed class SkipIfHostableWebCoreNotAvailibleAttribute : Attribute, ITestCondition + public sealed class SkipIfHostableWebCoreNotAvailableAttribute : Attribute, ITestCondition { public bool IsMet { get; } = File.Exists(TestServer.HostableWebCoreLocation); - public string SkipReason { get; } = $"Hostable Web Core not availible, {TestServer.HostableWebCoreLocation} not found."; + public string SkipReason { get; } = $"Hostable Web Core not available, {TestServer.HostableWebCoreLocation} not found."; } } diff --git a/test/Common.FunctionalTests/Utilities/TestServer.cs b/test/IIS.Tests/Utilities/TestServer.cs similarity index 80% rename from test/Common.FunctionalTests/Utilities/TestServer.cs rename to test/IIS.Tests/Utilities/TestServer.cs index 5eaaaf3440..c64fe4fbe9 100644 --- a/test/Common.FunctionalTests/Utilities/TestServer.cs +++ b/test/IIS.Tests/Utilities/TestServer.cs @@ -6,8 +6,11 @@ using System.IO; using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Loader; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; +using System.Xml.XPath; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -24,6 +27,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private const string HWebCoreDll = "hwebcore.dll"; internal static string HostableWebCoreLocation => Environment.ExpandEnvironmentVariables($@"%windir%\system32\inetsrv\{HWebCoreDll}"); + internal static string BasePath => Path.GetDirectoryName(new Uri(typeof(TestServer).Assembly.CodeBase).AbsolutePath); + internal static string InProcessHandlerLocation => Path.Combine(BasePath, InProcessHandlerDll); + + internal static string AspNetCoreModuleLocation => Path.Combine(BasePath, AspNetCoreModuleDll); private static readonly SemaphoreSlim WebCoreLock = new SemaphoreSlim(1, 1); @@ -35,14 +42,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private readonly Action _appBuilder; private readonly ILoggerFactory _loggerFactory; + private readonly hostfxr_main_fn _hostfxrMainFn; public HttpClient HttpClient { get; } public TestConnection CreateConnection() => new TestConnection(BasePort); private IWebHost _host; + private string _appHostConfigPath; + private TestServer(Action appBuilder, ILoggerFactory loggerFactory) { + _hostfxrMainFn = Main; _appBuilder = appBuilder; _loggerFactory = loggerFactory; @@ -57,7 +68,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests await WebCoreLock.WaitAsync(); var server = new TestServer(appBuilder, loggerFactory); server.Start(); - await server.HttpClient.GetAsync("/start"); + (await server.HttpClient.GetAsync("/start")).EnsureSuccessStatusCode(); await server._startedTaskCompletionSource.Task; return server; } @@ -70,11 +81,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private void Start() { LoadLibrary(HostableWebCoreLocation); - LoadLibrary(InProcessHandlerDll); - LoadLibrary(AspNetCoreModuleDll); + _appHostConfigPath = Path.GetTempFileName(); - set_main_handler(Main); - var startResult = WebCoreActivate(Path.GetFullPath("HostableWebCore.config"), null, "Instance"); + var webHostConfig = XDocument.Load(Path.GetFullPath("HostableWebCore.config")); + webHostConfig.XPathSelectElement("/configuration/system.webServer/globalModules/add[@name='AspNetCoreModuleV2']") + .SetAttributeValue("image", AspNetCoreModuleLocation); + webHostConfig.Save(_appHostConfigPath); + + set_main_handler(_hostfxrMainFn); + + var startResult = WebCoreActivate(_appHostConfigPath, null, "Instance"); if (startResult != 0) { throw new InvalidOperationException($"Error while running WebCoreActivate: {startResult}"); @@ -87,8 +103,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests .UseIIS() .ConfigureServices(services => { services.AddSingleton(this); - services.AddSingleton(_loggerFactory); - }) + services.AddSingleton(_loggerFactory); + }) .UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName) .Build(); diff --git a/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj b/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj index c2e7b815f9..cbd3d05b6b 100644 --- a/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj +++ b/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj @@ -12,6 +12,7 @@ + False diff --git a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs index 87741af201..b7e850cdb8 100644 --- a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess var hostShutdownToken = deploymentResult.DeploymentResult.HostShutdownToken; - Assert.True(hostShutdownToken.WaitHandle.WaitOne(Helpers.DefaultTimeout)); + Assert.True(hostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); Assert.True(hostShutdownToken.IsCancellationRequested); } diff --git a/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs b/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs index d43894a2c3..179c6bd198 100644 --- a/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -23,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var result = await DeployAsync(parameters); var response = await result.RetryingHttpClient.GetAsync("/Shutdown"); - Assert.True(result.DeploymentResult.HostShutdownToken.WaitHandle.WaitOne(Helpers.DefaultTimeout)); + Assert.True(result.DeploymentResult.HostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); } } } From 0634e1183e00350b86e3a3e364252098580dd970 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 21:06:03 -0700 Subject: [PATCH 7/7] React to hosting changes (#1072) --- build/dependencies.props | 2 +- .../DotNetCommands.cs | 73 ------------------- .../TestUriHelper.cs | 58 --------------- 3 files changed, 1 insertion(+), 132 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/DotNetCommands.cs diff --git a/build/dependencies.props b/build/dependencies.props index fb4b95edd2..37cc74caba 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -14,7 +14,7 @@ 2.2.0-preview1-34694 2.2.0-preview1-34694 2.2.0-preview1-34694 - 0.6.0-preview1-34694 + 0.6.0-a-preview1-pk-rem-iis-17083 2.2.0-preview1-34694 2.2.0-preview1-34694 2.2.0-preview1-34694 diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/DotNetCommands.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/DotNetCommands.cs deleted file mode 100644 index aaa7ade405..0000000000 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/DotNetCommands.cs +++ /dev/null @@ -1,73 +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. - -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Server.IntegrationTesting -{ - // Copied from hosting - // TODO: make public while removing IISExpressDeployer - public static class DotNetCommands - { - private const string _dotnetFolderName = ".dotnet"; - - internal static string DotNetHome { get; } = GetDotNetHome(); - - // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76 - private static string GetDotNetHome() - { - var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME"); - var userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); - var home = Environment.GetEnvironmentVariable("HOME"); - - var result = Path.Combine(Directory.GetCurrentDirectory(), _dotnetFolderName); - if (!string.IsNullOrEmpty(dotnetHome)) - { - result = dotnetHome; - } - else if (!string.IsNullOrEmpty(userProfile)) - { - result = Path.Combine(userProfile, _dotnetFolderName); - } - else if (!string.IsNullOrEmpty(home)) - { - result = home; - } - - return result; - } - - internal static string GetDotNetInstallDir(RuntimeArchitecture arch) - { - var dotnetDir = DotNetHome; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - dotnetDir = Path.Combine(dotnetDir, arch.ToString()); - } - - return dotnetDir; - } - - public static string GetDotNetExecutable(RuntimeArchitecture arch) - { - var dotnetDir = GetDotNetInstallDir(arch); - - var dotnetFile = "dotnet"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - dotnetFile += ".exe"; - } - - return Path.Combine(dotnetDir, dotnetFile); - } - - internal static bool IsRunningX86OnX64(RuntimeArchitecture arch) - { - return (RuntimeInformation.OSArchitecture == Architecture.X64 || RuntimeInformation.OSArchitecture == Architecture.Arm64) - && arch == RuntimeArchitecture.x86; - } - } -} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/TestUriHelper.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/TestUriHelper.cs index fe1b351ba4..e69de29bb2 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/TestUriHelper.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/TestUriHelper.cs @@ -1,58 +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. - -using System; - -namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common -{ - internal static class TestUriHelper - { - internal static Uri BuildTestUri(ServerType serverType, string hint) - { - // Assume status messages are enabled for Kestrel and disabled for all other servers. - var statusMessagesEnabled = (serverType == ServerType.Kestrel); - - return BuildTestUri(serverType, Uri.UriSchemeHttp, hint, statusMessagesEnabled); - } - - internal static Uri BuildTestUri(ServerType serverType, string scheme, string hint, bool statusMessagesEnabled) - { - if (string.IsNullOrEmpty(hint)) - { - if (serverType == ServerType.Kestrel && statusMessagesEnabled) - { - // Most functional tests use this codepath and should directly bind to dynamic port "0" and scrape - // the assigned port from the status message, which should be 100% reliable since the port is bound - // once and never released. Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not - // supported, so the port is only bound on "127.0.0.1" (IPv4). If a test explicitly requires IPv6, - // it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only). - return new UriBuilder(scheme, "127.0.0.1", 0).Uri; - } - else - { - // If the server type is not Kestrel, or status messages are disabled, there is no status message - // from which to scrape the assigned port, so the less reliable GetNextPort() must be used. The - // port is bound on "localhost" (both IPv4 and IPv6), since this is supported when using a specific - // (non-zero) port. - return new UriBuilder(scheme, "localhost", TestPortHelper.GetNextPort()).Uri; - } - } - else - { - var uriHint = new Uri(hint); - if (uriHint.Port == 0) - { - // Only a few tests use this codepath, so it's fine to use the less reliable GetNextPort() for simplicity. - // The tests using this codepath will be reviewed to see if they can be changed to directly bind to dynamic - // port "0" on "127.0.0.1" and scrape the assigned port from the status message (the default codepath). - return new UriBuilder(uriHint) { Port = TestPortHelper.GetNextPort() }.Uri; - } - else - { - // If the hint contains a specific port, return it unchanged. - return uriHint; - } - } - } - } -}