From fdb4184dbfdbac6ee8b53a4d8d1fbfaa8f60c0c1 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 11 Oct 2017 15:14:35 -0700 Subject: [PATCH] Sockets transport (#2100) * Make Sockets the default transport * Create separate Libuv and Sockets functional test projects * Fix functional tests that fail with Sockets * Moved OneToTenThreads test to Kestrel.Transport.Libuv.Tests * Fix systemd activation tests to use libuv transport * Dispose Sockets PipeFactory * Improve Socket's server-side abort handling * Add explicit rebinding test --- .travis.yml | 2 +- KestrelHttpServer.sln | 55 ++++++--- samples/SampleApp/Startup.cs | 23 ++-- src/Kestrel.Core/Properties/AssemblyInfo.cs | 3 +- src/Kestrel.Https/Properties/AssemblyInfo.cs | 4 +- .../Internal/ISocketsTrace.cs | 23 ++++ .../Internal/SocketsTrace.cs | 93 ++++++++++++++ .../SocketConnection.cs | 67 +++++++--- .../SocketTransport.cs | 19 +-- .../SocketTransportFactory.cs | 24 +++- .../baseline.netcore.json | 4 + src/Kestrel/Kestrel.csproj | 2 + .../WebHostBuilderKestrelExtensions.cs | 8 +- .../AddressRegistrationTests.cs | 114 ++++++++++++++++-- .../HttpProtocolSelectionTests.cs | 4 +- test/Kestrel.FunctionalTests/HttpsTests.cs | 14 +-- .../LoggingConnectionAdapterTests.cs | 2 +- .../MaxRequestBufferSizeTests.cs | 2 +- test/Kestrel.FunctionalTests/RequestTests.cs | 28 +++-- test/Kestrel.FunctionalTests/ResponseTests.cs | 9 +- .../TestHelpers/TestServer.cs | 2 +- .../ThreadCountTests.cs | 72 ----------- ...el.Trasnport.Libuv.FunctionalTests.csproj} | 6 +- .../ListenHandleTests.cs | 0 .../TransportSelector.cs | 15 +++ .../LibuvTransportTests.cs | 50 ++++++++ ...l.Trasnport.Sockets.FunctionalTests.csproj | 36 ++++++ .../TransportSelector.cs | 15 +++ .../SystemdActivation/Dockerfile | 0 .../SystemdActivation/docker-entrypoint.sh | 0 .../SystemdActivation/docker.sh | 0 31 files changed, 525 insertions(+), 171 deletions(-) create mode 100644 src/Kestrel.Transport.Sockets/Internal/ISocketsTrace.cs create mode 100644 src/Kestrel.Transport.Sockets/Internal/SocketsTrace.cs delete mode 100644 test/Kestrel.FunctionalTests/ThreadCountTests.cs rename test/{Kestrel.FunctionalTests/Kestrel.FunctionalTests.csproj => Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Trasnport.Libuv.FunctionalTests.csproj} (76%) rename test/{Kestrel.FunctionalTests => Kestrel.Transport.Libuv.FunctionalTests}/ListenHandleTests.cs (100%) create mode 100644 test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs create mode 100644 test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Trasnport.Sockets.FunctionalTests.csproj create mode 100644 test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs rename test/{Kestrel.FunctionalTests => }/SystemdActivation/Dockerfile (100%) rename test/{Kestrel.FunctionalTests => }/SystemdActivation/docker-entrypoint.sh (100%) rename test/{Kestrel.FunctionalTests => }/SystemdActivation/docker.sh (100%) mode change 100755 => 100644 diff --git a/.travis.yml b/.travis.yml index 73fb758d8..61ec7b534 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,4 +28,4 @@ before_install: - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi script: - ./build.sh - - if test "$TRAVIS_OS_NAME" != "osx"; then bash test/Kestrel.FunctionalTests/SystemdActivation/docker.sh; fi + - if test "$TRAVIS_OS_NAME" != "osx"; then bash test/SystemdActivation/docker.sh; fi diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index 0baa31521..2f3786bd6 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26814.0 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" ProjectSection(SolutionItems) = preProject @@ -68,8 +68,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeGenerator", "tools\Code EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Https", "src\Kestrel.Https\Kestrel.Https.csproj", "{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.FunctionalTests", "test\Kestrel.FunctionalTests\Kestrel.FunctionalTests.csproj", "{9559A5F1-080C-4909-B6CF-7E4B3DC55748}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Performance", "benchmarks\Kestrel.Performance\Kestrel.Performance.csproj", "{EBFE9719-A44B-4978-A71F-D5C254E7F35A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestCertificates", "TestCertificates", "{2822C132-BFFB-4D53-AC5B-E7E47DD81A6E}" @@ -112,6 +110,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{C2910A13 build\repo.targets = build\repo.targets EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SystemdActivation", "SystemdActivation", "{B7B0EA74-528F-46B8-9BC4-909D9A67C194}" + ProjectSection(SolutionItems) = preProject + test\SystemdActivation\docker-entrypoint.sh = test\SystemdActivation\docker-entrypoint.sh + test\SystemdActivation\docker.sh = test\SystemdActivation\docker.sh + test\SystemdActivation\Dockerfile = test\SystemdActivation\Dockerfile + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.FunctionalTests", "test\Kestrel.Transport.Libuv.FunctionalTests\Kestrel.Trasnport.Libuv.FunctionalTests.csproj", "{74032D79-8EA7-4483-BD82-C38370420FFF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Sockets.FunctionalTests", "test\Kestrel.Transport.Sockets.FunctionalTests\Kestrel.Trasnport.Sockets.FunctionalTests.csproj", "{9C7B6B5F-088A-436E-834B-6373EA36DEEE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -194,18 +203,6 @@ Global {5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|x64.Build.0 = Release|Any CPU {5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|x86.ActiveCfg = Release|Any CPU {5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|x86.Build.0 = Release|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x64.ActiveCfg = Debug|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x64.Build.0 = Debug|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x86.ActiveCfg = Debug|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x86.Build.0 = Debug|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|Any CPU.Build.0 = Release|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x64.ActiveCfg = Release|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x64.Build.0 = Release|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x86.ActiveCfg = Release|Any CPU - {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x86.Build.0 = Release|Any CPU {EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -314,6 +311,30 @@ Global {924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x64.Build.0 = Release|Any CPU {924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x86.ActiveCfg = Release|Any CPU {924AE57C-1EBA-4A1D-A039-8C100B7507A5}.Release|x86.Build.0 = Release|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|x64.ActiveCfg = Debug|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|x64.Build.0 = Debug|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|x86.ActiveCfg = Debug|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Debug|x86.Build.0 = Debug|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Release|Any CPU.Build.0 = Release|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Release|x64.ActiveCfg = Release|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Release|x64.Build.0 = Release|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Release|x86.ActiveCfg = Release|Any CPU + {74032D79-8EA7-4483-BD82-C38370420FFF}.Release|x86.Build.0 = Release|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Debug|x64.Build.0 = Debug|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Debug|x86.Build.0 = Debug|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|Any CPU.Build.0 = Release|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x64.ActiveCfg = Release|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x64.Build.0 = Release|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x86.ActiveCfg = Release|Any CPU + {9C7B6B5F-088A-436E-834B-6373EA36DEEE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -325,7 +346,6 @@ Global {B35D4D31-E74C-4646-8A11-7A7A40F0021E} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE} {BD2D4D29-1BD9-40D0-BB31-337D5416B63C} = {327F7880-D9AF-46BD-B45C-3B7E34A01DFD} {5F64B3C3-0C2E-431A-B820-A81BBFC863DA} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} - {9559A5F1-080C-4909-B6CF-7E4B3DC55748} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {EBFE9719-A44B-4978-A71F-D5C254E7F35A} = {A95C3BE1-B850-4265-97A0-777ADCCD437F} {2822C132-BFFB-4D53-AC5B-E7E47DD81A6E} = {0EF2ACDF-012F-4472-A13A-4272419E2903} {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} @@ -336,6 +356,9 @@ Global {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {6956CF5C-3163-4398-8628-4ECA569245B5} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} {924AE57C-1EBA-4A1D-A039-8C100B7507A5} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} + {B7B0EA74-528F-46B8-9BC4-909D9A67C194} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} + {74032D79-8EA7-4483-BD82-C38370420FFF} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} + {9C7B6B5F-088A-436E-834B-6373EA36DEEE} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071} diff --git a/samples/SampleApp/Startup.cs b/samples/SampleApp/Startup.cs index e89e97790..562f3a020 100644 --- a/samples/SampleApp/Startup.cs +++ b/samples/SampleApp/Startup.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.Diagnostics; using System.Globalization; using System.IO; using System.Net; @@ -51,7 +52,7 @@ public static Task Main(string[] args) basePort = 5000; } - var host = new WebHostBuilder() + var hostBuilder = new WebHostBuilder() .ConfigureLogging((_, factory) => { factory.AddConsole(); @@ -80,16 +81,20 @@ public static Task Main(string[] args) // The following section should be used to demo sockets //options.ListenUnixSocket("/tmp/kestrel-test.sock"); }) - .UseLibuv(options => - { - // Uncomment the following line to change the default number of libuv threads for all endpoints. - // options.ThreadCount = 4; - }) .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .Build(); + .UseStartup(); - return host.RunAsync(); + if (string.Equals(Process.GetCurrentProcess().Id.ToString(), Environment.GetEnvironmentVariable("LISTEN_PID"))) + { + // Use libuv if activated by systemd, since that's currently the only transport that supports being passed a socket handle. + hostBuilder.UseLibuv(options => + { + // Uncomment the following line to change the default number of libuv threads for all endpoints. + // options.ThreadCount = 4; + }); + } + + return hostBuilder.Build().RunAsync(); } } } \ No newline at end of file diff --git a/src/Kestrel.Core/Properties/AssemblyInfo.cs b/src/Kestrel.Core/Properties/AssemblyInfo.cs index bd1d32689..e2d152d0d 100644 --- a/src/Kestrel.Core/Properties/AssemblyInfo.cs +++ b/src/Kestrel.Core/Properties/AssemblyInfo.cs @@ -3,7 +3,8 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Kestrel.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Kestrel.Https/Properties/AssemblyInfo.cs b/src/Kestrel.Https/Properties/AssemblyInfo.cs index 3bb5150c9..65c2045e2 100644 --- a/src/Kestrel.Https/Properties/AssemblyInfo.cs +++ b/src/Kestrel.Https/Properties/AssemblyInfo.cs @@ -3,4 +3,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + diff --git a/src/Kestrel.Transport.Sockets/Internal/ISocketsTrace.cs b/src/Kestrel.Transport.Sockets/Internal/ISocketsTrace.cs new file mode 100644 index 000000000..06c2c36f2 --- /dev/null +++ b/src/Kestrel.Transport.Sockets/Internal/ISocketsTrace.cs @@ -0,0 +1,23 @@ +// 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.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal +{ + public interface ISocketsTrace : ILogger + { + void ConnectionReadFin(string connectionId); + + void ConnectionWriteFin(string connectionId); + + void ConnectionError(string connectionId, Exception ex); + + void ConnectionReset(string connectionId); + + void ConnectionPause(string connectionId); + + void ConnectionResume(string connectionId); + } +} diff --git a/src/Kestrel.Transport.Sockets/Internal/SocketsTrace.cs b/src/Kestrel.Transport.Sockets/Internal/SocketsTrace.cs new file mode 100644 index 000000000..2757bcf4c --- /dev/null +++ b/src/Kestrel.Transport.Sockets/Internal/SocketsTrace.cs @@ -0,0 +1,93 @@ +// 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.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal +{ + public class SocketsTrace : ISocketsTrace + { + // ConnectionRead: Reserved: 3 + + private static readonly Action _connectionPause = + LoggerMessage.Define(LogLevel.Debug, 4, @"Connection id ""{ConnectionId}"" paused."); + + private static readonly Action _connectionResume = + LoggerMessage.Define(LogLevel.Debug, 5, @"Connection id ""{ConnectionId}"" resumed."); + + private static readonly Action _connectionReadFin = + LoggerMessage.Define(LogLevel.Debug, 6, @"Connection id ""{ConnectionId}"" received FIN."); + + private static readonly Action _connectionWriteFin = + LoggerMessage.Define(LogLevel.Debug, 7, @"Connection id ""{ConnectionId}"" sending FIN."); + + private static readonly Action _connectionError = + LoggerMessage.Define(LogLevel.Information, 14, @"Connection id ""{ConnectionId}"" communication error."); + + private static readonly Action _connectionReset = + LoggerMessage.Define(LogLevel.Debug, 19, @"Connection id ""{ConnectionId}"" reset."); + + private readonly ILogger _logger; + + public SocketsTrace(ILogger logger) + { + _logger = logger; + } + + public void ConnectionRead(string connectionId, int count) + { + // Don't log for now since this could be *too* verbose. + // Reserved: Event ID 3 + } + + public void ConnectionReadFin(string connectionId) + { + _connectionReadFin(_logger, connectionId, null); + } + + public void ConnectionWriteFin(string connectionId) + { + _connectionWriteFin(_logger, connectionId, null); + } + + public void ConnectionWrite(string connectionId, int count) + { + // Don't log for now since this could be *too* verbose. + // Reserved: Event ID 11 + } + + public void ConnectionWriteCallback(string connectionId, int status) + { + // Don't log for now since this could be *too* verbose. + // Reserved: Event ID 12 + } + + public void ConnectionError(string connectionId, Exception ex) + { + _connectionError(_logger, connectionId, ex); + } + + public void ConnectionReset(string connectionId) + { + _connectionReset(_logger, connectionId, null); + } + + public void ConnectionPause(string connectionId) + { + _connectionPause(_logger, connectionId, null); + } + + public void ConnectionResume(string connectionId) + { + _connectionResume(_logger, connectionId, null); + } + + public IDisposable BeginScope(TState state) => _logger.BeginScope(state); + + public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel); + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + => _logger.Log(logLevel, eventId, state, exception, formatter); + } +} diff --git a/src/Kestrel.Transport.Sockets/SocketConnection.cs b/src/Kestrel.Transport.Sockets/SocketConnection.cs index 4f11d7cd1..6a7f765af 100644 --- a/src/Kestrel.Transport.Sockets/SocketConnection.cs +++ b/src/Kestrel.Transport.Sockets/SocketConnection.cs @@ -5,30 +5,36 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Pipelines; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; -using System.IO.Pipelines; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Protocols; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { internal sealed class SocketConnection : TransportConnection { + private const int MinAllocBufferSize = 2048; + private readonly Socket _socket; - private readonly SocketTransport _transport; + private readonly ISocketsTrace _trace; private IList> _sendBufferList; - private const int MinAllocBufferSize = 2048; + private volatile bool _aborted; - internal SocketConnection(Socket socket, SocketTransport transport) + internal SocketConnection(Socket socket, PipeFactory pipeFactory, ISocketsTrace trace) { Debug.Assert(socket != null); - Debug.Assert(transport != null); + Debug.Assert(pipeFactory != null); + Debug.Assert(trace != null); _socket = socket; - _transport = transport; + PipeFactory = pipeFactory; + _trace = trace; var localEndPoint = (IPEndPoint)_socket.LocalEndPoint; var remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; @@ -40,6 +46,10 @@ internal SocketConnection(Socket socket, SocketTransport transport) RemotePort = remoteEndPoint.Port; } + public override PipeFactory PipeFactory { get; } + public override IScheduler InputWriterScheduler => InlineScheduler.Default; + public override IScheduler OutputReaderScheduler => TaskRunScheduler.Default; + public async Task StartAsync(IConnectionHandler connectionHandler) { try @@ -66,9 +76,9 @@ public async Task StartAsync(IConnectionHandler connectionHandler) // Dispose the socket(should noop if already called) _socket.Dispose(); } - catch (Exception) + catch (Exception ex) { - // TODO: Log + _trace.LogError(0, ex, $"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}."); } } @@ -90,6 +100,7 @@ private async Task DoReceive() if (bytesReceived == 0) { // FIN + _trace.ConnectionReadFin(ConnectionId); break; } @@ -100,7 +111,18 @@ private async Task DoReceive() buffer.Commit(); } - var result = await buffer.FlushAsync(); + var flushTask = buffer.FlushAsync(); + + if (!flushTask.IsCompleted) + { + _trace.ConnectionPause(ConnectionId); + + await flushTask; + + _trace.ConnectionResume(ConnectionId); + } + + var result = flushTask.GetAwaiter().GetResult(); if (result.IsCompleted) { // Pipe consumer is shut down, do we stop writing @@ -111,25 +133,36 @@ private async Task DoReceive() catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset) { error = new ConnectionResetException(ex.Message, ex); + _trace.ConnectionReset(ConnectionId); } - catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || + ex.SocketErrorCode == SocketError.Interrupted) { error = new ConnectionAbortedException(); + _trace.ConnectionError(ConnectionId, error); } catch (ObjectDisposedException) { error = new ConnectionAbortedException(); + _trace.ConnectionError(ConnectionId, error); } catch (IOException ex) { error = ex; + _trace.ConnectionError(ConnectionId, error); } catch (Exception ex) { error = new IOException(ex.Message, ex); + _trace.ConnectionError(ConnectionId, error); } finally { + if (_aborted) + { + error = error ?? new ConnectionAbortedException(); + } + Input.Complete(error); } } @@ -202,8 +235,6 @@ private async Task DoSend() Output.Advance(buffer.End); } } - - _socket.Shutdown(SocketShutdown.Send); } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) { @@ -224,6 +255,13 @@ private async Task DoSend() finally { Output.Complete(error); + + // Make sure to close the connection only after the _aborted flag is set. + // Without this, the RequestsCanBeAbortedMidRead test will sometimes fail when + // a BadHttpRequestException is thrown instead of a TaskCanceledException. + _aborted = true; + _trace.ConnectionWriteFin(ConnectionId); + _socket.Shutdown(SocketShutdown.Both); } } @@ -237,8 +275,5 @@ private static ArraySegment GetArraySegment(Buffer buffer) return segment; } - public override PipeFactory PipeFactory => _transport.TransportFactory.PipeFactory; - public override IScheduler InputWriterScheduler => InlineScheduler.Default; - public override IScheduler OutputReaderScheduler => TaskRunScheduler.Default; } } diff --git a/src/Kestrel.Transport.Sockets/SocketTransport.cs b/src/Kestrel.Transport.Sockets/SocketTransport.cs index 83a4cb290..61ede997e 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransport.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransport.cs @@ -3,32 +3,38 @@ using System; using System.Diagnostics; +using System.IO.Pipelines; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { internal sealed class SocketTransport : ITransport { - private readonly SocketTransportFactory _transportFactory; + private readonly PipeFactory _pipeFactory = new PipeFactory(); private readonly IEndPointInformation _endPointInformation; private readonly IConnectionHandler _handler; + private readonly ISocketsTrace _trace; private Socket _listenSocket; private Task _listenTask; - internal SocketTransport(SocketTransportFactory transportFactory, IEndPointInformation endPointInformation, IConnectionHandler handler) + internal SocketTransport( + IEndPointInformation endPointInformation, + IConnectionHandler handler, + ISocketsTrace trace) { - Debug.Assert(transportFactory != null); Debug.Assert(endPointInformation != null); Debug.Assert(endPointInformation.Type == ListenType.IPEndPoint); Debug.Assert(handler != null); + Debug.Assert(trace != null); - _transportFactory = transportFactory; _endPointInformation = endPointInformation; _handler = handler; + _trace = trace; _listenSocket = null; _listenTask = null; @@ -92,6 +98,7 @@ public async Task UnbindAsync() public Task StopAsync() { + _pipeFactory.Dispose(); return Task.CompletedTask; } @@ -105,7 +112,7 @@ private async Task RunAcceptLoopAsync() acceptSocket.NoDelay = _endPointInformation.NoDelay; - var connection = new SocketConnection(acceptSocket, this); + var connection = new SocketConnection(acceptSocket, _pipeFactory, _trace); _ = connection.StartAsync(_handler); } } @@ -121,7 +128,5 @@ private async Task RunAcceptLoopAsync() } } } - - internal SocketTransportFactory TransportFactory => _transportFactory; } } diff --git a/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs b/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs index 849b16834..b8ee97791 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs @@ -2,18 +2,32 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { public sealed class SocketTransportFactory : ITransportFactory { - private readonly PipeFactory _pipeFactory = new PipeFactory(); + private readonly SocketsTrace _trace; - public SocketTransportFactory(IOptions options) + public SocketTransportFactory( + IOptions options, + ILoggerFactory loggerFactory) { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); + _trace = new SocketsTrace(logger); } public ITransport Create(IEndPointInformation endPointInformation, IConnectionHandler handler) @@ -33,9 +47,7 @@ public ITransport Create(IEndPointInformation endPointInformation, IConnectionHa throw new ArgumentNullException(nameof(handler)); } - return new SocketTransport(this, endPointInformation, handler); + return new SocketTransport(endPointInformation, handler, _trace); } - - internal PipeFactory PipeFactory => _pipeFactory; } } diff --git a/src/Kestrel.Transport.Sockets/baseline.netcore.json b/src/Kestrel.Transport.Sockets/baseline.netcore.json index e614b8097..bd469c578 100644 --- a/src/Kestrel.Transport.Sockets/baseline.netcore.json +++ b/src/Kestrel.Transport.Sockets/baseline.netcore.json @@ -83,6 +83,10 @@ { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" } ], "Visibility": "Public", diff --git a/src/Kestrel/Kestrel.csproj b/src/Kestrel/Kestrel.csproj index cdd0e1bc7..1376f1e89 100644 --- a/src/Kestrel/Kestrel.csproj +++ b/src/Kestrel/Kestrel.csproj @@ -16,7 +16,9 @@ + + diff --git a/src/Kestrel/WebHostBuilderKestrelExtensions.cs b/src/Kestrel/WebHostBuilderKestrelExtensions.cs index 3d3dad73c..5ed947e96 100644 --- a/src/Kestrel/WebHostBuilderKestrelExtensions.cs +++ b/src/Kestrel/WebHostBuilderKestrelExtensions.cs @@ -5,7 +5,10 @@ using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Hosting @@ -23,10 +26,11 @@ public static class WebHostBuilderKestrelExtensions /// public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) { - hostBuilder.UseLibuv(); - return hostBuilder.ConfigureServices(services => { + // Don't override an already-configured transport + services.TryAddSingleton(); + services.AddTransient, KestrelServerOptionsSetup>(); services.AddSingleton(); }); diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs index b9f6e4303..66f2a62c2 100644 --- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -163,7 +163,7 @@ public async Task RegisterAddresses_IPv6LocalhostStaticPort_Success() private async Task RegisterAddresses_Success(string addressInput, string[] testUrls, int testPort = 0) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .ConfigureLogging(_configureLoggingDelegate) .UseUrls(addressInput) @@ -223,7 +223,7 @@ private Task RegisterAddresses_StaticPort_Success(string addressInput, string te private async Task RegisterIPEndPoint_Success(IPEndPoint endPoint, string testUrl, int testPort = 0) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { @@ -304,7 +304,7 @@ private async Task RegisterDefaultServerAddresses_Success(IEnumerable ad { var testLogger = new TestApplicationErrorLogger(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .ConfigureLogging(builder => builder @@ -336,7 +336,7 @@ public void ThrowsWhenBindingToIPv4AddressInUse() socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); var port = ((IPEndPoint)socket.LocalEndPoint).Port; - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .UseUrls($"http://127.0.0.1:{port}") @@ -359,7 +359,7 @@ public void ThrowsWhenBindingToIPv6AddressInUse() socket.Bind(new IPEndPoint(IPAddress.IPv6Loopback, 0)); var port = ((IPEndPoint)socket.LocalEndPoint).Port; - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .UseUrls($"http://[::1]:{port}") @@ -378,7 +378,7 @@ public async Task OverrideDirectConfigurationWithIServerAddressesFeature_Succeed { var useUrlsAddress = $"http://127.0.0.1:0"; var testLogger = new TestApplicationErrorLogger(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -417,7 +417,7 @@ public async Task DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_ { var useUrlsAddress = $"http://127.0.0.1:0"; var testLogger = new TestApplicationErrorLogger(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { @@ -455,7 +455,7 @@ public async Task DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_ [Fact] public async Task DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_IfAddressesEmpty() { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { @@ -499,7 +499,7 @@ public void ThrowsWhenBindingLocalhostToIPv6AddressInUse() [Fact] public void ThrowsWhenBindingLocalhostToDynamicPort() { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://localhost:0") .Configure(ConfigureEchoAddress); @@ -515,7 +515,7 @@ public void ThrowsWhenBindingLocalhostToDynamicPort() [InlineData("ftp://localhost")] public void ThrowsForUnsupportedAddressFromHosting(string addr) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls(addr) .Configure(ConfigureEchoAddress); @@ -526,6 +526,91 @@ public void ThrowsForUnsupportedAddressFromHosting(string addr) } } + // https://github.com/dotnet/corefx/issues/24562 + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Sockets transport fails to rebind on macOS.")] + [OSSkipCondition(OperatingSystems.Linux, SkipReason = "Sockets transport fails to rebind on Linux.")] + public async Task CanRebindToEndPoint() + { + var port = GetNextPort(); + var endPointAddress = $"http://127.0.0.1:{port}/"; + + var hostBuilder = TransportSelector.GetWebHostBuilder() + .ConfigureLogging(_configureLoggingDelegate) + .UseKestrel(options => + { + options.Listen(IPAddress.Loopback, port); + }) + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) + { + host.Start(); + + Assert.Equal(endPointAddress, await HttpClientSlim.GetStringAsync(endPointAddress)); + } + + hostBuilder = TransportSelector.GetWebHostBuilder() + .ConfigureLogging(_configureLoggingDelegate) + .UseKestrel(options => + { + options.Listen(IPAddress.Loopback, port); + }) + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) + { + host.Start(); + + Assert.Equal(endPointAddress, await HttpClientSlim.GetStringAsync(endPointAddress)); + } + } + + // https://github.com/dotnet/corefx/issues/24562 + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Sockets transport fails to rebind on macOS.")] + [OSSkipCondition(OperatingSystems.Linux, SkipReason = "Sockets transport fails to rebind on Linux.")] + public async Task CanRebindToMultipleEndPoints() + { + var port = GetNextPort(); + var ipv4endPointAddress = $"http://127.0.0.1:{port}/"; + var ipv6endPointAddress = $"http://[::1]:{port}/"; + + var hostBuilder = TransportSelector.GetWebHostBuilder() + .ConfigureLogging(_configureLoggingDelegate) + .UseKestrel(options => + { + options.Listen(IPAddress.Loopback, port); + options.Listen(IPAddress.IPv6Loopback, port); + }) + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) + { + host.Start(); + + Assert.Equal(ipv4endPointAddress, await HttpClientSlim.GetStringAsync(ipv4endPointAddress)); + Assert.Equal(ipv6endPointAddress, await HttpClientSlim.GetStringAsync(ipv6endPointAddress)); + } + + hostBuilder = TransportSelector.GetWebHostBuilder() + .ConfigureLogging(_configureLoggingDelegate) + .UseKestrel(options => + { + options.Listen(IPAddress.Loopback, port); + options.Listen(IPAddress.IPv6Loopback, port); + }) + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) + { + host.Start(); + + Assert.Equal(ipv4endPointAddress, await HttpClientSlim.GetStringAsync(ipv4endPointAddress)); + Assert.Equal(ipv6endPointAddress, await HttpClientSlim.GetStringAsync(ipv6endPointAddress)); + } + } + private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamily, IPAddress address) { using (var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)) @@ -533,7 +618,7 @@ private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamil socket.Bind(new IPEndPoint(address, 0)); var port = ((IPEndPoint)socket.LocalEndPoint).Port; - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .UseUrls($"http://localhost:{port}") @@ -807,6 +892,12 @@ public PortSupportedConditionAttribute(int port) private bool CanBindToPort() { +#if MACOS && SOCKETS + // Binding to a port with a Socket, disposing the Socket, and rebinding often fails with + // SocketError.AddressAlreadyInUse on macOS. This isn't an issue if binding with libuv second. + // https://github.com/dotnet/corefx/issues/24562 + return false; +#else try { using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) @@ -819,6 +910,7 @@ private bool CanBindToPort() { return false; } +#endif } } } diff --git a/test/Kestrel.FunctionalTests/HttpProtocolSelectionTests.cs b/test/Kestrel.FunctionalTests/HttpProtocolSelectionTests.cs index 7b665d5ef..c2dd742c1 100644 --- a/test/Kestrel.FunctionalTests/HttpProtocolSelectionTests.cs +++ b/test/Kestrel.FunctionalTests/HttpProtocolSelectionTests.cs @@ -45,7 +45,7 @@ public Task Server_Http2Only_Cleartext_Success() private async Task TestSuccess(HttpProtocols serverProtocols, string request, string expectedResponse) { - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(IPAddress.Loopback, 0, listenOptions => listenOptions.Protocols = serverProtocols); @@ -73,7 +73,7 @@ private async Task TestError(HttpProtocols serverProtocols, string e .Setup(provider => provider.CreateLogger(It.IsAny())) .Returns(logger); - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(loggerProvider.Object)) .UseKestrel(options => options.Listen(IPAddress.Loopback, 0, listenOptions => listenOptions.Protocols = serverProtocols)) .Configure(app => app.Run(context => Task.CompletedTask)); diff --git a/test/Kestrel.FunctionalTests/HttpsTests.cs b/test/Kestrel.FunctionalTests/HttpsTests.cs index 604df5943..8bc8fb7f6 100644 --- a/test/Kestrel.FunctionalTests/HttpsTests.cs +++ b/test/Kestrel.FunctionalTests/HttpsTests.cs @@ -31,7 +31,7 @@ public async Task EmptyRequestLoggedAsInformation() { var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -65,7 +65,7 @@ public async Task ClientHandshakeFailureLoggedAsInformation() { var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -101,7 +101,7 @@ public async Task ClientHandshakeFailureLoggedAsInformation() public async Task DoesNotThrowObjectDisposedExceptionOnConnectionAbort() { var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -153,7 +153,7 @@ public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnecti { var tcs = new TaskCompletionSource(); var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -202,7 +202,7 @@ await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, public async Task DoesNotThrowObjectDisposedExceptionOnEmptyConnection() { var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -236,7 +236,7 @@ public void ConnectionFilterDoesNotLeakBlock() { var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -265,7 +265,7 @@ public void ConnectionFilterDoesNotLeakBlock() public async Task HandshakeTimesOutAndIsLoggedAsInformation() { var loggerProvider = new HandshakeErrorLoggerProvider(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => diff --git a/test/Kestrel.FunctionalTests/LoggingConnectionAdapterTests.cs b/test/Kestrel.FunctionalTests/LoggingConnectionAdapterTests.cs index 24e72aa33..12f7e604e 100644 --- a/test/Kestrel.FunctionalTests/LoggingConnectionAdapterTests.cs +++ b/test/Kestrel.FunctionalTests/LoggingConnectionAdapterTests.cs @@ -26,7 +26,7 @@ public LoggingConnectionAdapterTests(ITestOutputHelper output) [Fact] public async Task LoggingConnectionAdapterCanBeAddedBeforeAndAfterHttpsAdapter() { - var host = new WebHostBuilder() + var host = TransportSelector.GetWebHostBuilder() .ConfigureLogging(builder => { builder.SetMinimumLevel(LogLevel.Trace); diff --git a/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs b/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs index 9cda0bbde..79cdee529 100644 --- a/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs @@ -258,7 +258,7 @@ private IWebHost StartWebHost(long? maxRequestBufferSize, TaskCompletionSource startReadingRequestBody, TaskCompletionSource clientFinishedSendingRequestBody) { - var host = new WebHostBuilder() + var host = TransportSelector.GetWebHostBuilder() .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { diff --git a/test/Kestrel.FunctionalTests/RequestTests.cs b/test/Kestrel.FunctionalTests/RequestTests.cs index 1362651bd..46933f427 100644 --- a/test/Kestrel.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.FunctionalTests/RequestTests.cs @@ -69,7 +69,7 @@ public void LargeUpload(long contentLength, bool checkBytes) Assert.True(contentLength % bufferLength == 0, $"{nameof(contentLength)} sent must be evenly divisible by {bufferLength}."); Assert.True(bufferLength % 256 == 0, $"{nameof(bufferLength)} must be evenly divisible by 256"); - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel(options => { options.Limits.MaxRequestBodySize = contentLength; @@ -155,7 +155,7 @@ public Task RemoteIPv6Address() [Fact] public async Task DoesNotHangOnConnectionCloseRequest() { - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0") .Configure(app => @@ -185,7 +185,7 @@ public async Task StreamsAreNotPersistedAcrossRequests() var requestBodyPersisted = false; var responseBodyPersisted = false; - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0") .Configure(app => @@ -228,7 +228,7 @@ public async Task StreamsAreNotPersistedAcrossRequests() public void CanUpgradeRequestWithConnectionKeepAliveUpgradeHeader() { var dataRead = false; - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0") .Configure(app => @@ -300,7 +300,9 @@ public async Task ConnectionResetPriorToRequestIsLoggedAsDebug() .Setup(factory => factory.CreateLogger(It.IsAny())) .Returns(Mock.Of()); mockLoggerFactory - .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"))) + .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(mockLoggerFactory.Object))) @@ -354,7 +356,9 @@ public async Task ConnectionResetBetweenRequestsIsLoggedAsDebug() .Setup(factory => factory.CreateLogger(It.IsAny())) .Returns(Mock.Of()); mockLoggerFactory - .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"))) + .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(mockLoggerFactory.Object))) @@ -424,7 +428,9 @@ public async Task ConnectionResetMidRequestIsLoggedAsDebug() .Setup(factory => factory.CreateLogger(It.IsAny())) .Returns(Mock.Of()); mockLoggerFactory - .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"))) + .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", + "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); using (var server = new TestServer(async context => @@ -464,7 +470,7 @@ public async Task ThrowsOnReadAfterConnectionError() var appDone = new SemaphoreSlim(0); var expectedExceptionThrown = false; - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0") .Configure(app => app.Run(async context => @@ -508,7 +514,7 @@ public async Task RequestAbortedTokenFiredOnClientFIN() { var appStarted = new SemaphoreSlim(0); var requestAborted = new SemaphoreSlim(0); - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0") .Configure(app => app.Run(async context => @@ -538,7 +544,7 @@ public async Task RequestAbortedTokenFiredOnClientFIN() [Fact] public void AbortingTheConnectionSendsFIN() { - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0") .Configure(app => app.Run(context => @@ -1617,7 +1623,7 @@ await connection.Receive( private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress) { - var builder = new WebHostBuilder() + var builder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls($"http://{registerAddress}:0") .Configure(app => diff --git a/test/Kestrel.FunctionalTests/ResponseTests.cs b/test/Kestrel.FunctionalTests/ResponseTests.cs index d19ffbfb1..7879e4d72 100644 --- a/test/Kestrel.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.FunctionalTests/ResponseTests.cs @@ -49,7 +49,7 @@ public class ResponseTests [Fact] public async Task LargeDownload() { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0/") .Configure(app => @@ -101,7 +101,7 @@ public async Task LargeDownload() [Theory, MemberData(nameof(NullHeaderData))] public async Task IgnoreNullHeaderValues(string headerName, StringValues headerValue, string expectedValue) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0/") .Configure(app => @@ -144,7 +144,7 @@ public async Task OnCompleteCalledEvenWhenOnStartingNotCalled() var onStartingCalled = false; var onCompletedCalled = false; - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0/") .Configure(app => @@ -179,7 +179,7 @@ public async Task OnStartingThrowsWhenSetAfterResponseHasAlreadyStarted() { InvalidOperationException ex = null; - var hostBuilder = new WebHostBuilder() + var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0/") .Configure(app => @@ -2506,6 +2506,7 @@ public void ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate() totalReceived += received; } while (received > 0 && totalReceived < responseSize); } + catch (SocketException) { } catch (IOException) { // Socket.Receive could throw, and that is fine diff --git a/test/Kestrel.FunctionalTests/TestHelpers/TestServer.cs b/test/Kestrel.FunctionalTests/TestHelpers/TestServer.cs index 5e36ddf4a..f0972bb4f 100644 --- a/test/Kestrel.FunctionalTests/TestHelpers/TestServer.cs +++ b/test/Kestrel.FunctionalTests/TestHelpers/TestServer.cs @@ -48,7 +48,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions _listenOptions = listenOptions; Context = context; - _host = new WebHostBuilder() + _host = TransportSelector.GetWebHostBuilder() .UseKestrel(o => { o.ListenOptions.Add(_listenOptions); diff --git a/test/Kestrel.FunctionalTests/ThreadCountTests.cs b/test/Kestrel.FunctionalTests/ThreadCountTests.cs deleted file mode 100644 index 67f5cbafb..000000000 --- a/test/Kestrel.FunctionalTests/ThreadCountTests.cs +++ /dev/null @@ -1,72 +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.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Testing.xunit; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests -{ - public class ThreadCountTests - { - [ConditionalTheory] - [MemberData(nameof(OneToTen))] - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Tests fail on OS X due to low file descriptor limit.")] - public async Task OneToTenThreads(int threadCount) - { - var hostBuilder = new WebHostBuilder() - .UseKestrel() - .UseLibuv(options => - { - options.ThreadCount = threadCount; - }) - .UseUrls("http://127.0.0.1:0/") - .Configure(app => - { - app.Run(context => - { - return context.Response.WriteAsync("Hello World"); - }); - }); - - using (var host = hostBuilder.Build()) - { - host.Start(); - - using (var client = new HttpClient()) - { - // Send 20 requests just to make sure we don't get any failures - var requestTasks = new List>(); - for (int i = 0; i < 20; i++) - { - var requestTask = client.GetStringAsync($"http://127.0.0.1:{host.GetPort()}/"); - requestTasks.Add(requestTask); - } - - foreach (var result in await Task.WhenAll(requestTasks)) - { - Assert.Equal("Hello World", result); - } - } - } - } - - public static TheoryData OneToTen - { - get - { - var dataset = new TheoryData(); - for (int i = 1; i <= 10; i++) - { - dataset.Add(i); - } - return dataset; - } - } - } -} diff --git a/test/Kestrel.FunctionalTests/Kestrel.FunctionalTests.csproj b/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Trasnport.Libuv.FunctionalTests.csproj similarity index 76% rename from test/Kestrel.FunctionalTests/Kestrel.FunctionalTests.csproj rename to test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Trasnport.Libuv.FunctionalTests.csproj index 3ed0a438b..b4c4d93b6 100644 --- a/test/Kestrel.FunctionalTests/Kestrel.FunctionalTests.csproj +++ b/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Trasnport.Libuv.FunctionalTests.csproj @@ -1,8 +1,8 @@  - Microsoft.AspNetCore.Server.Kestrel.FunctionalTests - Microsoft.AspNetCore.Server.Kestrel.FunctionalTests + Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.FunctionalTests + Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.FunctionalTests netcoreapp2.0;net461 netcoreapp2.0 $(DefineConstants);MACOS @@ -11,6 +11,7 @@ + @@ -20,6 +21,7 @@ + diff --git a/test/Kestrel.FunctionalTests/ListenHandleTests.cs b/test/Kestrel.Transport.Libuv.FunctionalTests/ListenHandleTests.cs similarity index 100% rename from test/Kestrel.FunctionalTests/ListenHandleTests.cs rename to test/Kestrel.Transport.Libuv.FunctionalTests/ListenHandleTests.cs diff --git a/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs b/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs new file mode 100644 index 000000000..cba3395d1 --- /dev/null +++ b/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs @@ -0,0 +1,15 @@ +// 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 Microsoft.AspNetCore.Hosting; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public static class TransportSelector + { + public static IWebHostBuilder GetWebHostBuilder() + { + return new WebHostBuilder().UseLibuv(); + } + } +} diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs index d3739ae07..f62a792b8 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs @@ -1,7 +1,10 @@ // 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.Collections.Generic; +using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; @@ -10,6 +13,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests @@ -25,6 +29,8 @@ public class LibuvTransportTests } }; + public static IEnumerable OneToTen => Enumerable.Range(1, 10).Select(i => new object[] { i }); + [Fact] public async Task TransportCanBindAndStop() { @@ -80,5 +86,49 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) await transport.UnbindAsync(); await transport.StopAsync(); } + + [ConditionalTheory] + [MemberData(nameof(OneToTen))] + [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Tests fail on OS X due to low file descriptor limit.")] + public async Task OneToTenThreads(int threadCount) + { + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + var serviceContext = new TestServiceContext(); + var testApplication = new DummyApplication(context => + { + return context.Response.WriteAsync("Hello World"); + }); + + listenOptions.UseHttpServer(listenOptions.ConnectionAdapters, serviceContext, testApplication, HttpProtocols.Http1); + + var transportContext = new TestLibuvTransportContext() + { + ConnectionHandler = new ConnectionHandler(serviceContext, listenOptions.Build()), + Options = new LibuvTransportOptions { ThreadCount = threadCount } + }; + + var transport = new LibuvTransport(transportContext, listenOptions); + + await transport.BindAsync(); + + using (var client = new HttpClient()) + { + // Send 20 requests just to make sure we don't get any failures + var requestTasks = new List>(); + for (int i = 0; i < 20; i++) + { + var requestTask = client.GetStringAsync($"http://127.0.0.1:{listenOptions.IPEndPoint.Port}/"); + requestTasks.Add(requestTask); + } + + foreach (var result in await Task.WhenAll(requestTasks)) + { + Assert.Equal("Hello World", result); + } + } + + await transport.UnbindAsync(); + await transport.StopAsync(); + } } } diff --git a/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Trasnport.Sockets.FunctionalTests.csproj b/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Trasnport.Sockets.FunctionalTests.csproj new file mode 100644 index 000000000..8030d1846 --- /dev/null +++ b/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Trasnport.Sockets.FunctionalTests.csproj @@ -0,0 +1,36 @@ + + + + Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.FunctionalTests + Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.FunctionalTests + netcoreapp2.0;net461 + netcoreapp2.0 + $(DefineConstants);MACOS + $(DefineConstants);SOCKETS + true + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs b/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs new file mode 100644 index 000000000..1a433cc9e --- /dev/null +++ b/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs @@ -0,0 +1,15 @@ +// 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 Microsoft.AspNetCore.Hosting; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public static class TransportSelector + { + public static IWebHostBuilder GetWebHostBuilder() + { + return new WebHostBuilder().UseSockets(); + } + } +} diff --git a/test/Kestrel.FunctionalTests/SystemdActivation/Dockerfile b/test/SystemdActivation/Dockerfile similarity index 100% rename from test/Kestrel.FunctionalTests/SystemdActivation/Dockerfile rename to test/SystemdActivation/Dockerfile diff --git a/test/Kestrel.FunctionalTests/SystemdActivation/docker-entrypoint.sh b/test/SystemdActivation/docker-entrypoint.sh similarity index 100% rename from test/Kestrel.FunctionalTests/SystemdActivation/docker-entrypoint.sh rename to test/SystemdActivation/docker-entrypoint.sh diff --git a/test/Kestrel.FunctionalTests/SystemdActivation/docker.sh b/test/SystemdActivation/docker.sh old mode 100755 new mode 100644 similarity index 100% rename from test/Kestrel.FunctionalTests/SystemdActivation/docker.sh rename to test/SystemdActivation/docker.sh