diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index a2fc4e3108..2fba69b809 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel", "src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj", "{56139957-5C29-4E7D-89BD-7D20598B4EAF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", "{6950B18F-A3D2-41A4-AFEC-8B7C49517611}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj", "{2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests", "test\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.csproj", "{D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4}" @@ -198,6 +200,18 @@ Global {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x64.Build.0 = Release|Any CPU {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x86.ActiveCfg = Release|Any CPU {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x86.Build.0 = Release|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x64.ActiveCfg = Debug|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x64.Build.0 = Debug|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x86.ActiveCfg = Debug|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Debug|x86.Build.0 = Debug|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|Any CPU.Build.0 = Release|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x64.ActiveCfg = Release|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x64.Build.0 = Release|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x86.ActiveCfg = Release|Any CPU + {6950B18F-A3D2-41A4-AFEC-8B7C49517611}.Release|x86.Build.0 = Release|Any CPU {2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -251,6 +265,7 @@ Global {2822C132-BFFB-4D53-AC5B-E7E47DD81A6E} = {0EF2ACDF-012F-4472-A13A-4272419E2903} {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} {56139957-5C29-4E7D-89BD-7D20598B4EAF} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} + {6950B18F-A3D2-41A4-AFEC-8B7C49517611} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} {2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} {D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs index db9236d9d8..0ea0494d0e 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs @@ -27,8 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public IConnectionContext OnConnection(IConnectionInformation connectionInfo) { - var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(connectionInfo.InputWriterScheduler)); - var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputReaderScheduler)); + var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(requiresDispatch: connectionInfo.RequiresDispatch, writerScheduler: connectionInfo.InputWriterScheduler)); + var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(requiresDispatch: connectionInfo.RequiresDispatch, readerScheduler: connectionInfo.OutputReaderScheduler)); var connectionId = CorrelationIdGenerator.GetNextId(); var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId); @@ -70,18 +70,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } // Internal for testing - internal PipeOptions GetInputPipeOptions(IScheduler writerScheduler) => new PipeOptions + internal PipeOptions GetInputPipeOptions(bool requiresDispatch, IScheduler writerScheduler) => new PipeOptions { - ReaderScheduler = _serviceContext.ThreadPool, + ReaderScheduler = (requiresDispatch ? (IScheduler)_serviceContext.ThreadPool : (IScheduler)InlineScheduler.Default), WriterScheduler = writerScheduler, MaximumSizeHigh = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, MaximumSizeLow = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0 }; - internal PipeOptions GetOutputPipeOptions(IScheduler readerScheduler) => new PipeOptions + internal PipeOptions GetOutputPipeOptions(bool requiresDispatch, IScheduler readerScheduler) => new PipeOptions { ReaderScheduler = readerScheduler, - WriterScheduler = _serviceContext.ThreadPool, + WriterScheduler = (requiresDispatch ? (IScheduler)_serviceContext.ThreadPool : (IScheduler)InlineScheduler.Default), MaximumSizeHigh = GetOutputResponseBufferSize(), MaximumSizeLow = GetOutputResponseBufferSize() }; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/IConnectionInformation.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/IConnectionInformation.cs index a06bd16dd9..ca3cd9afbe 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/IConnectionInformation.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/IConnectionInformation.cs @@ -12,6 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions IPEndPoint LocalEndPoint { get; } PipeFactory PipeFactory { get; } + + bool RequiresDispatch { get; } + IScheduler InputWriterScheduler { get; } IScheduler OutputReaderScheduler { get; } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs index 6c9d0cfcf5..b28739ed2e 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal public IPEndPoint LocalEndPoint { get; set; } public PipeFactory PipeFactory => ListenerContext.Thread.PipeFactory; + public bool RequiresDispatch => true; public IScheduler InputWriterScheduler => ListenerContext.Thread; public IScheduler OutputReaderScheduler => ListenerContext.Thread; } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj new file mode 100644 index 0000000000..911f42f7b2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj @@ -0,0 +1,24 @@ + + + + + + Managed socket transport for the ASP.NET Core Kestrel cross-platform web server. + netstandard1.3 + true + aspnetcore;kestrel + true + CS1591;$(NoWarn) + false + + + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs new file mode 100644 index 0000000000..922fb53186 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs @@ -0,0 +1,216 @@ +// 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.Server.Kestrel.Internal.System.Buffers; +using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets +{ + internal sealed class SocketConnection : IConnectionInformation + { + private readonly Socket _socket; + private readonly SocketTransport _transport; + private readonly IPEndPoint _localEndPoint; + private readonly IPEndPoint _remoteEndPoint; + private IConnectionContext _connectionContext; + private IPipeWriter _input; + private IPipeReader _output; + private IList> _sendBufferList; + + private const int MinAllocBufferSize = 2048; // from libuv transport + + internal SocketConnection(Socket socket, SocketTransport transport) + { + Debug.Assert(socket != null); + Debug.Assert(transport != null); + + _socket = socket; + _transport = transport; + + _localEndPoint = (IPEndPoint)_socket.LocalEndPoint; + _remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; + } + + public async void Start(IConnectionHandler connectionHandler) + { + _connectionContext = connectionHandler.OnConnection(this); + + _input = _connectionContext.Input; + _output = _connectionContext.Output; + + // Spawn send and receive logic + Task receiveTask = DoReceive(); + Task sendTask = DoSend(); + + // Wait for them to complete (note they won't throw exceptions) + await receiveTask; + await sendTask; + + _socket.Dispose(); + + _connectionContext.OnConnectionClosed(); + } + + private async Task DoReceive() + { + try + { + bool done = false; + while (!done) + { + // Ensure we have some reasonable amount of buffer space + WritableBuffer buffer = _input.Alloc(MinAllocBufferSize); + + int bytesReceived; + try + { + bytesReceived = await _socket.ReceiveAsync(GetArraySegment(buffer.Buffer), SocketFlags.None); + } + catch (Exception ex) + { + buffer.Commit(); + _connectionContext.Abort(ex); + _input.Complete(ex); + break; + } + + if (bytesReceived == 0) + { + // EOF + Exception ex = new TaskCanceledException(); + buffer.Commit(); + _connectionContext.Abort(ex); + _input.Complete(ex); + break; + } + + // record what data we filled into the buffer and push to pipe + buffer.Advance(bytesReceived); + var result = await buffer.FlushAsync(); + if (result.IsCompleted) + { + // Pipe consumer is shut down + _socket.Shutdown(SocketShutdown.Receive); + done = true; + } + } + } + catch (Exception) + { + // We don't expect any exceptions here, but eat it anyway as caller does not handle this. + Debug.Assert(false); + } + } + + private void SetupSendBuffers(ReadableBuffer buffer) + { + Debug.Assert(!buffer.IsEmpty); + Debug.Assert(!buffer.IsSingleSpan); + + if (_sendBufferList == null) + { + _sendBufferList = new List>(); + } + + // We should always clear the list after the send + Debug.Assert(_sendBufferList.Count == 0); + + foreach (var b in buffer) + { + _sendBufferList.Add(GetArraySegment(b)); + } + } + + private async Task DoSend() + { + try + { + bool done = false; + while (!done) + { + // Wait for data to write from the pipe producer + ReadResult result = await _output.ReadAsync(); + ReadableBuffer buffer = result.Buffer; + + try + { + if (!buffer.IsEmpty) + { + if (buffer.IsSingleSpan) + { + await _socket.SendAsync(GetArraySegment(buffer.First), SocketFlags.None); + } + else + { + SetupSendBuffers(buffer); + try + { + await _socket.SendAsync(_sendBufferList, SocketFlags.None); + } + finally + { + _sendBufferList.Clear(); + } + } + } + + if (result.IsCancelled) + { + // Send a FIN + _socket.Shutdown(SocketShutdown.Send); + break; + } + + if (buffer.IsEmpty && result.IsCompleted) + { + // Send a FIN + _socket.Shutdown(SocketShutdown.Send); + break; + } + } + finally + { + _output.Advance(buffer.End); + } + } + + // We're done reading + _output.Complete(); + } + catch (Exception ex) + { + _output.Complete(ex); + } + } + + private static ArraySegment GetArraySegment(Buffer buffer) + { + ArraySegment segment; + if (!buffer.TryGetArray(out segment)) + { + throw new InvalidOperationException(); + } + + return segment; + } + + public IPEndPoint RemoteEndPoint => _remoteEndPoint; + + public IPEndPoint LocalEndPoint => _localEndPoint; + + public PipeFactory PipeFactory => _transport.TransportFactory.PipeFactory; + + public bool RequiresDispatch => _transport.TransportFactory.ForceDispatch; + + public IScheduler InputWriterScheduler => InlineScheduler.Default; + + public IScheduler OutputReaderScheduler => InlineScheduler.Default; + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketTransport.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketTransport.cs new file mode 100644 index 0000000000..0a7aaa15ae --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketTransport.cs @@ -0,0 +1,127 @@ +// 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.Server.Kestrel.Internal.System.IO.Pipelines; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets +{ + internal sealed class SocketTransport : ITransport + { + private readonly SocketTransportFactory _transportFactory; + private readonly IEndPointInformation _endPointInformation; + private readonly IConnectionHandler _handler; + private Socket _listenSocket; + private Task _listenTask; + + internal SocketTransport(SocketTransportFactory transportFactory, IEndPointInformation endPointInformation, IConnectionHandler handler) + { + Debug.Assert(transportFactory != null); + Debug.Assert(endPointInformation != null); + Debug.Assert(endPointInformation.Type == ListenType.IPEndPoint); + Debug.Assert(handler != null); + + _transportFactory = transportFactory; + _endPointInformation = endPointInformation; + _handler = handler; + + _listenSocket = null; + _listenTask = null; + } + + public Task BindAsync() + { + if (_listenSocket != null) + { + throw new InvalidOperationException("Transport is already bound"); + } + + IPEndPoint endPoint = _endPointInformation.IPEndPoint; + + var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + + // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 + if (endPoint.Address == IPAddress.IPv6Any) + { + listenSocket.DualMode = true; + } + + try + { + listenSocket.Bind(endPoint); + } + catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) + { + throw new AddressInUseException(e.Message, e); + } + + // If requested port was "0", replace with assigned dynamic port. + if (_endPointInformation.IPEndPoint.Port == 0) + { + _endPointInformation.IPEndPoint = (IPEndPoint)listenSocket.LocalEndPoint; + } + + listenSocket.Listen(512); + + _listenSocket = listenSocket; + + _listenTask = Task.Run(() => RunAcceptLoopAsync()); + + return Task.CompletedTask; + } + + public async Task UnbindAsync() + { + if (_listenSocket != null) + { + var listenSocket = _listenSocket; + _listenSocket = null; + + listenSocket.Dispose(); + + Debug.Assert(_listenTask != null); + await _listenTask; + _listenTask = null; + } + } + + public Task StopAsync() + { + return Task.CompletedTask; + } + + private async Task RunAcceptLoopAsync() + { + try + { + while (true) + { + Socket acceptSocket = await _listenSocket.AcceptAsync(); + + acceptSocket.NoDelay = _endPointInformation.NoDelay; + + SocketConnection connection = new SocketConnection(acceptSocket, this); + connection.Start(_handler); + } + } + catch (Exception) + { + if (_listenSocket == null) + { + // Means we must be unbinding. Eat the exception. + } + else + { + throw; + } + } + } + + internal SocketTransportFactory TransportFactory => _transportFactory; + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketTransportFactory.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketTransportFactory.cs new file mode 100644 index 0000000000..48f15ffc35 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketTransportFactory.cs @@ -0,0 +1,44 @@ +// 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.Server.Kestrel.Internal.System.IO.Pipelines; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; +using System; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets +{ + public sealed class SocketTransportFactory : ITransportFactory + { + private readonly PipeFactory _pipeFactory = new PipeFactory(); + private readonly bool _forceDispatch; + + public SocketTransportFactory(bool forceDispatch = false) + { + _forceDispatch = forceDispatch; + } + + public ITransport Create(IEndPointInformation endPointInformation, IConnectionHandler handler) + { + if (endPointInformation == null) + { + throw new ArgumentNullException(nameof(endPointInformation)); + } + + if (endPointInformation.Type != ListenType.IPEndPoint) + { + throw new ArgumentException("Only ListenType.IPEndPoint is supported", nameof(endPointInformation)); + } + + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return new SocketTransport(this, endPointInformation, handler); + } + + internal PipeFactory PipeFactory => _pipeFactory; + + internal bool ForceDispatch => _forceDispatch; + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/WebHostBuilderSocketExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/WebHostBuilderSocketExtensions.cs new file mode 100644 index 0000000000..402a982c45 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/WebHostBuilderSocketExtensions.cs @@ -0,0 +1,29 @@ +// 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.Server.Kestrel.Transport.Abstractions; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class WebHostBuilderSocketExtensions + { + /// + /// Specify Sockets as the transport to be used by Kestrel. + /// + /// + /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. + /// + /// + /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. + /// + public static IWebHostBuilder UseSockets(this IWebHostBuilder hostBuilder) + { + return hostBuilder.ConfigureServices(services => + { + services.AddSingleton(); + }); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj index 91e491c4f9..8adf6b216c 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj @@ -18,6 +18,7 @@ + diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs index 33646df6ed..f2928ac9c2 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs @@ -813,6 +813,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public IPEndPoint RemoteEndPoint { get; } public IPEndPoint LocalEndPoint { get; } public PipeFactory PipeFactory { get; } + public bool RequiresDispatch { get; } public IScheduler InputWriterScheduler { get; } public IScheduler OutputReaderScheduler { get; } } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/PipeOptionsTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/PipeOptionsTests.cs index 6183e278fe..b8fe2bb78b 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/PipeOptionsTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/PipeOptionsTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var connectionHandler = new ConnectionHandler(listenOptions: null, serviceContext: serviceContext, application: null); var mockScheduler = Mock.Of(); - var outputPipeOptions = connectionHandler.GetOutputPipeOptions(mockScheduler); + var outputPipeOptions = connectionHandler.GetOutputPipeOptions(requiresDispatch: true, readerScheduler: mockScheduler); Assert.Equal(expectedMaximumSizeLow, outputPipeOptions.MaximumSizeLow); Assert.Equal(expectedMaximumSizeHigh, outputPipeOptions.MaximumSizeHigh); @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var connectionHandler = new ConnectionHandler(listenOptions: null, serviceContext: serviceContext, application: null); var mockScheduler = Mock.Of(); - var inputPipeOptions = connectionHandler.GetInputPipeOptions(mockScheduler); + var inputPipeOptions = connectionHandler.GetInputPipeOptions(requiresDispatch: true, writerScheduler: mockScheduler); Assert.Equal(expectedMaximumSizeLow, inputPipeOptions.MaximumSizeLow); Assert.Equal(expectedMaximumSizeHigh, inputPipeOptions.MaximumSizeHigh); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestServer.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestServer.cs index 42bfa542ab..9a6681d7af 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestServer.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestServer.cs @@ -7,9 +7,11 @@ using System.Net.Sockets; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { @@ -18,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests /// public class TestServer : IDisposable { - private LibuvTransport _transport; + private ITransport _transport; private ListenOptions _listenOptions; public TestServer(RequestDelegate app) @@ -46,37 +48,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests _listenOptions = listenOptions; Context = context; - TransportContext = new LibuvTransportContext() - { - AppLifetime = new LifetimeNotImplemented(), - ConnectionHandler = new ConnectionHandler(listenOptions, Context, new DummyApplication(app, httpContextFactory)), - Log = new LibuvTrace(new TestApplicationErrorLogger()), - Options = new LibuvTransportOptions() - { - ThreadCount = 1, - ShutdownTimeout = TimeSpan.FromSeconds(5) - } - }; + + _transport = s_transportFactory.Create(listenOptions, new ConnectionHandler(listenOptions, context, new DummyApplication(app, httpContextFactory))); try { - _transport = new LibuvTransport(TransportContext, _listenOptions); _transport.BindAsync().Wait(); } catch { _transport.UnbindAsync().Wait(); _transport.StopAsync().Wait(); + _transport = null; throw; } } + // Switch this to test on socket transport + private static readonly ITransportFactory s_transportFactory = CreateLibuvTransportFactory(); +// private static readonly ITransportFactory s_transportFactory = CreateSocketTransportFactory(); + + private static ITransportFactory CreateLibuvTransportFactory() + { + var transportOptions = new LibuvTransportOptions() + { + ThreadCount = 1, + ShutdownTimeout = TimeSpan.FromSeconds(5) + }; + + var transportFactory = new LibuvTransportFactory( + Options.Create(transportOptions), + new LifetimeNotImplemented(), + new KestrelTestLoggerFactory(new TestApplicationErrorLogger())); + + return transportFactory; + } + + private static ITransportFactory CreateSocketTransportFactory() + { + // For now, force the socket transport to do threadpool dispatch for tests. + // There are a handful of tests that deadlock due to test issues if we don't do dispatch. + // We should clean these up, but for now, make them work by forcing dispatch. + return new SocketTransportFactory(true); + } + public IPEndPoint EndPoint => _listenOptions.IPEndPoint; public int Port => _listenOptions.IPEndPoint.Port; public AddressFamily AddressFamily => _listenOptions.IPEndPoint.AddressFamily; public TestServiceContext Context { get; } - public LibuvTransportContext TransportContext { get; } public TestConnection CreateConnection() { diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Mocks/MockConnectionInformation.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Mocks/MockConnectionInformation.cs index 7aa345898b..6928a6f70b 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Mocks/MockConnectionInformation.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Mocks/MockConnectionInformation.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance public IPEndPoint LocalEndPoint { get; } public PipeFactory PipeFactory { get; } + public bool RequiresDispatch { get; } public IScheduler InputWriterScheduler { get; } public IScheduler OutputReaderScheduler { get; } }