From 54471a293030d3dd6d02caeeedaf5542436f3896 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 17 Jul 2018 20:26:57 -0700 Subject: [PATCH] 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)); } } }