From 42622b7f81b90ecf4aac4c74f9fbedbf999e1244 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 17 May 2019 00:01:50 -0700 Subject: [PATCH 01/78] Spike Kestrel with new transport abstraction. --- .../src/ConnectionContext.cs | 10 + .../src/DefaultConnectionContext.cs | 6 +- .../Features/IConnectionEndpointFeature.cs | 10 + .../src/IConnectionFactory.cs | 12 + .../src/IConnectionListener.cs | 14 ++ .../src/IConnectionListenerFactory.cs | 12 + .../Core/src/Internal/ConnectionDispatcher.cs | 48 ++-- src/Servers/Kestrel/Core/src/KestrelServer.cs | 26 +-- src/Servers/Kestrel/Core/src/ListenOptions.cs | 4 +- .../src/WebHostBuilderKestrelExtensions.cs | 3 +- .../src/Internal/TransportConnection.cs | 3 + .../src/Internal/SocketConnection.cs | 43 +++- ...re.Server.Kestrel.Transport.Sockets.csproj | 6 +- .../src/SocketConnectionListener.cs | 131 +++++++++++ .../Transport.Sockets/src/SocketTransport.cs | 205 ------------------ .../src/SocketTransportFactory.cs | 58 +++-- .../src/WebHostBuilderSocketExtensions.cs | 6 +- .../Kestrel/samples/PlaintextApp/Startup.cs | 48 +++- 18 files changed, 369 insertions(+), 276 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs create mode 100644 src/Servers/Connections.Abstractions/src/IConnectionFactory.cs create mode 100644 src/Servers/Connections.Abstractions/src/IConnectionListener.cs create mode 100644 src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs create mode 100644 src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs delete mode 100644 src/Servers/Kestrel/Transport.Sockets/src/SocketTransport.cs diff --git a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs index a709a5f89195..75d4b2714636 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO.Pipelines; +using System.Net; +using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; @@ -18,6 +20,9 @@ public abstract class ConnectionContext public abstract IDuplexPipe Transport { get; set; } + public abstract EndPoint LocalEndpoint { get; set; } + public abstract EndPoint RemoteEndpoint { get; set; } + public virtual void Abort(ConnectionAbortedException abortReason) { // We expect this to be overridden, but this helps maintain back compat @@ -27,5 +32,10 @@ public virtual void Abort(ConnectionAbortedException abortReason) } public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); + + public virtual ValueTask DisposeAsync() + { + return default; + } } } diff --git a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs index fab7c929e24f..e2c7c70e82d0 100644 --- a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO.Pipelines; +using System.Net; using System.Security.Claims; using System.Threading; using Microsoft.AspNetCore.Connections.Features; @@ -17,7 +18,8 @@ public class DefaultConnectionContext : ConnectionContext, IConnectionItemsFeature, IConnectionTransportFeature, IConnectionUserFeature, - IConnectionLifetimeFeature + IConnectionLifetimeFeature, + IConnectionEndPointFeature { private CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource(); @@ -64,6 +66,8 @@ public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe ap public override IDuplexPipe Transport { get; set; } public CancellationToken ConnectionClosed { get; set; } + public override EndPoint LocalEndpoint { get; set; } + public override EndPoint RemoteEndpoint { get; set; } public override void Abort(ConnectionAbortedException abortReason) { diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs new file mode 100644 index 000000000000..53c4b0bd4973 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace Microsoft.AspNetCore.Connections.Features +{ + public interface IConnectionEndPointFeature + { + EndPoint LocalEndpoint { get; set; } + EndPoint RemoteEndpoint { get; set; } + } +} diff --git a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs new file mode 100644 index 000000000000..2ef2aaa7f5f0 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + public interface IConnectionFactory + { + ValueTask ConnectAsync(System.Net.EndPoint endpoint); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs new file mode 100644 index 000000000000..4421222d1d20 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + public interface IConnectionListener + { + ValueTask AcceptAsync(); + + ValueTask DisposeAsync(); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs new file mode 100644 index 000000000000..6f7dcb1bbc74 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + public interface IConnectionListenerFactory + { + ValueTask BindAsync(System.Net.EndPoint endpoint); + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 0c0cca15732d..7c3c622deb37 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - internal class ConnectionDispatcher : IConnectionDispatcher + internal class ConnectionDispatcher { private static long _lastConnectionId = long.MinValue; @@ -28,23 +28,45 @@ public ConnectionDispatcher(ServiceContext serviceContext, ConnectionDelegate co private IKestrelTrace Log => _serviceContext.Log; - public Task OnConnection(TransportConnection connection) + public void StartAcceptingConnections(IConnectionListener listener) { - // REVIEW: Unfortunately, we still need to use the service context to create the pipes since the settings - // for the scheduler and limits are specified here - var inputOptions = GetInputPipeOptions(_serviceContext, connection.MemoryPool, connection.InputWriterScheduler); - var outputOptions = GetOutputPipeOptions(_serviceContext, connection.MemoryPool, connection.OutputReaderScheduler); + ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false); + } - var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); + private void StartAcceptingConnectionsCore(IConnectionListener listener) + { + // REVIEW: Multiple accept loops in parallel? + _ = AcceptConnectionsAsync(); - // Set the transport and connection id - connection.ConnectionId = CorrelationIdGenerator.GetNextId(); - connection.Transport = pair.Transport; + async Task AcceptConnectionsAsync() + { + try + { + while (true) + { + var connection = await listener.AcceptAsync(); - // This *must* be set before returning from OnConnection - connection.Application = pair.Application; + // TODO: We need a bit of command and control to do connection management and that requires + // that we have access to a couple of methods that only exists on TransportConnection + // (specifically RequestClose, TickHeartbeat and CompleteAsync. This dependency needs to be removed or + // the return value of AcceptAsync needs to change. - return Execute(new KestrelConnection(connection)); + _ = OnConnection((TransportConnection)connection); + } + } + catch (Exception) + { + // REVIEW: Today the only way to end the accept loop is an exception + } + } + } + + private async Task OnConnection(TransportConnection connection) + { + await using (connection) + { + await Execute(new KestrelConnection(connection)); + } } private async Task Execute(KestrelConnection connection) diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 51b444ada232..edf1c297b069 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -6,6 +6,7 @@ using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http.Features; @@ -20,23 +21,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core { public class KestrelServer : IServer { - private readonly List _transports = new List(); + private readonly List _transports = new List(); private readonly IServerAddressesFeature _serverAddresses; - private readonly ITransportFactory _transportFactory; + private readonly IConnectionListenerFactory _transportFactory; private bool _hasStarted; private int _stopping; private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); -#pragma warning disable PUB0001 // Pubternal type in public API - public KestrelServer(IOptions options, ITransportFactory transportFactory, ILoggerFactory loggerFactory) -#pragma warning restore PUB0001 + public KestrelServer(IOptions options, IConnectionListenerFactory transportFactory, ILoggerFactory loggerFactory) : this(transportFactory, CreateServiceContext(options, loggerFactory)) { } // For testing - internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext) + internal KestrelServer(IConnectionListenerFactory transportFactory, ServiceContext serviceContext) { if (transportFactory == null) { @@ -152,10 +151,10 @@ async Task OnBind(ListenOptions endpoint) } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); - var transport = _transportFactory.Create(endpoint, connectionDispatcher); + var transport = await _transportFactory.BindAsync(endpoint.Endpoint).ConfigureAwait(false); _transports.Add(transport); - await transport.BindAsync().ConfigureAwait(false); + connectionDispatcher.StartAcceptingConnections(transport); } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); @@ -182,7 +181,7 @@ public async Task StopAsync(CancellationToken cancellationToken) var tasks = new Task[_transports.Count]; for (int i = 0; i < _transports.Count; i++) { - tasks[i] = _transports[i].UnbindAsync(); + tasks[i] = _transports[i].DisposeAsync().AsTask(); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -196,10 +195,11 @@ public async Task StopAsync(CancellationToken cancellationToken) } } - for (int i = 0; i < _transports.Count; i++) - { - tasks[i] = _transports[i].StopAsync(); - } + // ???? + //for (int i = 0; i < _transports.Count; i++) + //{ + // tasks[i] = _transports[i].StopAsync(); + //} await Task.WhenAll(tasks).ConfigureAwait(false); ServiceContext.Heartbeat?.Dispose(); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index b3e4ec21ced0..ed4e2e136276 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -90,6 +90,8 @@ public FileHandleType HandleType } } + public EndPoint Endpoint => IPEndPoint; + // IPEndPoint is mutable so port 0 can be updated to the bound port. /// /// The to bind to. diff --git a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs index 62411b168d44..1b93e76d5fd4 100644 --- a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs +++ b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.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 Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; @@ -29,7 +30,7 @@ public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) return hostBuilder.ConfigureServices(services => { // Don't override an already-configured transport - services.TryAddSingleton(); + services.TryAddSingleton(); services.AddTransient, KestrelServerOptionsSetup>(); services.AddSingleton(); diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs index e3d012cc27f1..a684a96357b7 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs @@ -32,6 +32,9 @@ public TransportConnection() public IPAddress LocalAddress { get; set; } public int LocalPort { get; set; } + public override EndPoint LocalEndpoint { get; set; } + public override EndPoint RemoteEndpoint { get; set; } + public override string ConnectionId { get; set; } public override IFeatureCollection Features => this; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index bfb467f94abb..ed0695b8035d 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal { - internal sealed class SocketConnection : TransportConnection, IDisposable + internal sealed class SocketConnection : TransportConnection { private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -32,6 +32,7 @@ internal sealed class SocketConnection : TransportConnection, IDisposable private readonly object _shutdownLock = new object(); private volatile bool _socketDisposed; private volatile Exception _shutdownReason; + private Task _task; internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeScheduler scheduler, ISocketsTrace trace) { @@ -48,6 +49,9 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu var localEndPoint = (IPEndPoint)_socket.LocalEndPoint; var remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; + LocalEndpoint = localEndPoint; + RemoteEndpoint = remoteEndPoint; + LocalAddress = localEndPoint.Address; LocalPort = localEndPoint.Port; @@ -63,13 +67,28 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); + + var inputOptions = new PipeOptions(MemoryPool, awaiterScheduler, awaiterScheduler, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, awaiterScheduler, awaiterScheduler, useSynchronizationContext: false); + + var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); + + // Set the transport and connection id + // connection.ConnectionId = CorrelationIdGenerator.GetNextId(); + Transport = pair.Transport; + Application = pair.Application; } public override MemoryPool MemoryPool { get; } public override PipeScheduler InputWriterScheduler => _scheduler; public override PipeScheduler OutputReaderScheduler => _scheduler; - public async Task StartAsync() + public void Start() + { + _task = StartAsync(); + } + + private async Task StartAsync() { try { @@ -88,6 +107,8 @@ public async Task StartAsync() catch (Exception ex) { _trace.LogError(0, ex, $"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}."); + + // REVIEW: Should this dispose the socket? } } @@ -101,8 +122,22 @@ public override void Abort(ConnectionAbortedException abortReason) } // Only called after connection middleware is complete which means the ConnectionClosed token has fired. - public void Dispose() + public override async ValueTask DisposeAsync() { + if (_task != null) + { + // TODO: Make this timeout configurable, this gives the task time to flush the data to the socket + // before timing out + var result = await Task.WhenAny(_task, Task.Delay(5000)); + + if (result != _task) + { + Abort(); + } + + await _task; + } + _connectionClosedTokenSource.Dispose(); _connectionClosingCts.Dispose(); } @@ -211,7 +246,7 @@ private async Task DoSend() } catch (SocketException ex) when (IsConnectionResetError(ex.SocketErrorCode)) { - shutdownReason = new ConnectionResetException(ex.Message, ex);; + shutdownReason = new ConnectionResetException(ex.Message, ex); _trace.ConnectionReset(ConnectionId); } catch (Exception ex) diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj index e5ffbb53b647..6924af105e8f 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj +++ b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj @@ -1,4 +1,4 @@ - + Managed socket transport for the ASP.NET Core Kestrel cross-platform web server. @@ -10,6 +10,10 @@ CS1591;$(NoWarn) + + + + diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs new file mode 100644 index 000000000000..74a98adefac3 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -0,0 +1,131 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets +{ + internal sealed class SocketConnectionListener : IConnectionListener + { + private readonly MemoryPool _memoryPool; + private readonly IPEndPoint _endpoint; + private readonly int _numSchedulers; + private readonly PipeScheduler[] _schedulers; + private readonly ISocketsTrace _trace; + private Socket _listenSocket; + private int _schedulerIndex; + + internal SocketConnectionListener( + EndPoint endpoint, + int ioQueueCount, + ISocketsTrace trace, + MemoryPool memoryPool) + { + Debug.Assert(endpoint != null); + Debug.Assert(endpoint is IPEndPoint); + Debug.Assert(trace != null); + + _endpoint = (IPEndPoint)endpoint; + _trace = trace; + _memoryPool = memoryPool; + + if (ioQueueCount > 0) + { + _numSchedulers = ioQueueCount; + _schedulers = new IOQueue[_numSchedulers]; + + for (var i = 0; i < _numSchedulers; i++) + { + _schedulers[i] = new IOQueue(); + } + } + else + { + var directScheduler = new PipeScheduler[] { PipeScheduler.ThreadPool }; + _numSchedulers = directScheduler.Length; + _schedulers = directScheduler; + } + } + + internal void Bind() + { + if (_listenSocket != null) + { + throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); + } + + IPEndPoint endPoint = _endpoint; + + 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); + } + + // TODO: This is a problem, we need to enable a way for the caller to know what address got bound. + // This is specific to IPEndpoint + // 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; + } + + public async ValueTask AcceptAsync() + { + try + { + var acceptSocket = await _listenSocket.AcceptAsync(); + + // REVIEW: This doesn't work anymore, these need to be on SocketOptions + // acceptSocket.NoDelay = _endPointInformation.NoDelay; + + var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); + + connection.Start(); + + _schedulerIndex = (_schedulerIndex + 1) % _numSchedulers; + + return connection; + } + catch (SocketException ex) + { + // REVIEW: Do we need more exception types? + throw new ConnectionResetException("The connection was reset", ex); + } + } + + public ValueTask DisposeAsync() + { + _listenSocket?.Dispose(); + _listenSocket = null; + _memoryPool.Dispose(); + // TODO: Wait for all connections to drain (fixed timeout?) + return default; + } + + } +} diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransport.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransport.cs deleted file mode 100644 index a1b01d45bba7..000000000000 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransport.cs +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Diagnostics; -using System.IO.Pipelines; -using System.Net; -using System.Net.Sockets; -using System.Runtime.ExceptionServices; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Hosting; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets -{ - internal sealed class SocketTransport : ITransport - { - private readonly MemoryPool _memoryPool; - private readonly IEndPointInformation _endPointInformation; - private readonly IConnectionDispatcher _dispatcher; - private readonly IHostApplicationLifetime _appLifetime; - private readonly int _numSchedulers; - private readonly PipeScheduler[] _schedulers; - private readonly ISocketsTrace _trace; - private Socket _listenSocket; - private Task _listenTask; - private Exception _listenException; - private volatile bool _unbinding; - - internal SocketTransport( - IEndPointInformation endPointInformation, - IConnectionDispatcher dispatcher, - IHostApplicationLifetime applicationLifetime, - int ioQueueCount, - ISocketsTrace trace, - MemoryPool memoryPool) - { - Debug.Assert(endPointInformation != null); - Debug.Assert(endPointInformation.Type == ListenType.IPEndPoint); - Debug.Assert(dispatcher != null); - Debug.Assert(applicationLifetime != null); - Debug.Assert(trace != null); - - _endPointInformation = endPointInformation; - _dispatcher = dispatcher; - _appLifetime = applicationLifetime; - _trace = trace; - _memoryPool = memoryPool; - - if (ioQueueCount > 0) - { - _numSchedulers = ioQueueCount; - _schedulers = new IOQueue[_numSchedulers]; - - for (var i = 0; i < _numSchedulers; i++) - { - _schedulers[i] = new IOQueue(); - } - } - else - { - var directScheduler = new PipeScheduler[] { PipeScheduler.ThreadPool }; - _numSchedulers = directScheduler.Length; - _schedulers = directScheduler; - } - } - - public Task BindAsync() - { - if (_listenSocket != null) - { - throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); - } - - 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) - { - _unbinding = true; - _listenSocket.Dispose(); - - Debug.Assert(_listenTask != null); - await _listenTask.ConfigureAwait(false); - - _unbinding = false; - _listenSocket = null; - _listenTask = null; - - if (_listenException != null) - { - var exInfo = ExceptionDispatchInfo.Capture(_listenException); - _listenException = null; - exInfo.Throw(); - } - } - } - - public Task StopAsync() - { - _memoryPool.Dispose(); - return Task.CompletedTask; - } - - private async Task RunAcceptLoopAsync() - { - try - { - while (true) - { - for (var schedulerIndex = 0; schedulerIndex < _numSchedulers; schedulerIndex++) - { - try - { - var acceptSocket = await _listenSocket.AcceptAsync(); - acceptSocket.NoDelay = _endPointInformation.NoDelay; - - var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[schedulerIndex], _trace); - - // REVIEW: This task should be tracked by the server for graceful shutdown - // Today it's handled specifically for http but not for arbitrary middleware - _ = HandleConnectionAsync(connection); - } - catch (SocketException) when (!_unbinding) - { - _trace.ConnectionReset(connectionId: "(null)"); - } - } - } - } - catch (Exception ex) - { - if (_unbinding) - { - // Means we must be unbinding. Eat the exception. - } - else - { - _trace.LogCritical(ex, $"Unexpected exception in {nameof(SocketTransport)}.{nameof(RunAcceptLoopAsync)}."); - _listenException = ex; - - // Request shutdown so we can rethrow this exception - // in Stop which should be observable. - _appLifetime.StopApplication(); - } - } - } - - private async Task HandleConnectionAsync(SocketConnection connection) - { - try - { - var middlewareTask = _dispatcher.OnConnection(connection); - var transportTask = connection.StartAsync(); - - await transportTask; - await middlewareTask; - - connection.Dispose(); - } - catch (Exception ex) - { - _trace.LogCritical(ex, $"Unexpected exception in {nameof(SocketTransport)}.{nameof(HandleConnectionAsync)}."); - } - } - } -} diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs index f6ef805fbbde..713982346255 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs @@ -2,66 +2,60 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { -#pragma warning disable PUB0001 // Pubternal type in public API - public sealed class SocketTransportFactory : ITransportFactory -#pragma warning restore PUB0001 // Pubternal type in public API + public sealed class SocketTransportFactory : IConnectionListenerFactory, IConnectionFactory { private readonly SocketTransportOptions _options; - private readonly IHostApplicationLifetime _appLifetime; private readonly SocketsTrace _trace; + public SocketTransportFactory(): this(Options.Create(new SocketTransportOptions()), NullLoggerFactory.Instance) + { + + } + public SocketTransportFactory( IOptions options, - IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } - if (applicationLifetime == null) - { - throw new ArgumentNullException(nameof(applicationLifetime)); - } + if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } _options = options.Value; - _appLifetime = applicationLifetime; - var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); _trace = new SocketsTrace(logger); } -#pragma warning disable PUB0001 // Pubternal type in public API - public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher dispatcher) -#pragma warning restore PUB0001 // Pubternal type in public API + public ValueTask BindAsync(EndPoint endpoint) { - if (endPointInformation == null) - { - throw new ArgumentNullException(nameof(endPointInformation)); - } - - if (endPointInformation.Type != ListenType.IPEndPoint) - { - throw new ArgumentException(SocketsStrings.OnlyIPEndPointsSupported, nameof(endPointInformation)); - } - - if (dispatcher == null) - { - throw new ArgumentNullException(nameof(dispatcher)); - } + var transport = new SocketConnectionListener(endpoint, _options.IOQueueCount, _trace, _options.MemoryPoolFactory()); + transport.Bind(); + return new ValueTask(transport); + } - return new SocketTransport(endPointInformation, dispatcher, _appLifetime, _options.IOQueueCount, _trace, _options.MemoryPoolFactory()); + public async ValueTask ConnectAsync(EndPoint endpoint) + { + // REVIEW: How do we pick the type of socket? Is the endpoint enough? + var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + await socket.ConnectAsync(endpoint); + return new SocketConnection(socket, _options.MemoryPoolFactory(), PipeScheduler.ThreadPool, _trace); } } } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs index 95d27e46db04..d073f91aa45d 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs @@ -1,8 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using Microsoft.Extensions.DependencyInjection; @@ -23,7 +23,7 @@ public static IWebHostBuilder UseSockets(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => { - services.AddSingleton(); + services.AddSingleton(); }); } diff --git a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs index 98dc353f23d9..15de3bf2e2ed 100644 --- a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs +++ b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs @@ -8,7 +8,9 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; namespace PlaintextApp { @@ -31,7 +33,7 @@ public void Configure(IApplicationBuilder app) }); } - public static Task Main(string[] args) + public static async Task Main(string[] args) { var host = new WebHostBuilder() .UseKestrel(options => @@ -42,7 +44,49 @@ public static Task Main(string[] args) .UseStartup() .Build(); - return host.RunAsync(); + var hostTask = host.RunAsync(); + var serverTask = ServerAsync(5002); + + await hostTask; + await serverTask; + } + + private static async Task ServerAsync(int port) + { + var factory = new SocketTransportFactory(); + await using var listener = await factory.BindAsync(new IPEndPoint(IPAddress.Loopback, port)); + + while (true) + { + var connection = await listener.AcceptAsync(); + + // Fire and forget so we can handle more than a single connection at a time + _ = HandleConnectionAsync(connection); + + static async Task HandleConnectionAsync(ConnectionContext connection) + { + await using (connection) + { + while (true) + { + var result = await connection.Transport.Input.ReadAsync(); + var buffer = result.Buffer; + + foreach (var segment in buffer) + { + await connection.Transport.Output.WriteAsync(segment); + } + + if (result.IsCompleted) + { + break; + } + + connection.Transport.Input.AdvanceTo(buffer.End); + } + } + } + } } } From 5d91d92b5807dd72ed1fa4e95bdb73c249b05ed3 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 17 May 2019 01:45:40 -0700 Subject: [PATCH 02/78] Libuv works --- .../src/Internal/LibuvConnection.cs | 19 ++- ...ransport.cs => LibuvConnectionListener.cs} | 78 ++++++++++-- .../src/Internal/LibuvThread.cs | 6 +- .../src/Internal/LibuvTransportContext.cs | 2 - .../src/Internal/LibuvTransportFactory.cs | 37 ++++-- .../Transport.Libuv/src/Internal/Listener.cs | 116 +++++++++--------- .../src/Internal/ListenerContext.cs | 67 ++++++---- .../src/Internal/ListenerPrimary.cs | 5 +- .../src/Internal/ListenerSecondary.cs | 8 +- ...Core.Server.Kestrel.Transport.Libuv.csproj | 5 + .../src/WebHostBuilderLibuvExtensions.cs | 5 +- .../test/LibuvConnectionTests.cs | 8 +- .../test/LibuvOutputConsumerTests.cs | 2 +- .../Transport.Libuv/test/LibuvThreadTests.cs | 2 +- .../test/LibuvTransportTests.cs | 8 +- .../test/ListenerPrimaryTests.cs | 6 +- .../samples/PlaintextApp/PlaintextApp.csproj | 3 +- .../Kestrel/samples/PlaintextApp/Startup.cs | 11 +- 18 files changed, 252 insertions(+), 136 deletions(-) rename src/Servers/Kestrel/Transport.Libuv/src/Internal/{LibuvTransport.cs => LibuvConnectionListener.cs} (63%) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index adcc5cd09d23..e6b1298401ba 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { - internal partial class LibuvConnection : TransportConnection, IDisposable + internal partial class LibuvConnection : TransportConnection { private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; @@ -42,10 +42,23 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa LocalAddress = localEndPoint?.Address; LocalPort = localEndPoint?.Port ?? 0; + LocalEndpoint = localEndPoint; + RemoteEndpoint = remoteEndPoint; + ConnectionClosed = _connectionClosedTokenSource.Token; Logger = log; Log = log; Thread = thread; + + var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, Thread, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, Thread, PipeScheduler.ThreadPool, useSynchronizationContext: false); + + var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); + + // Set the transport and connection id + // connection.ConnectionId = CorrelationIdGenerator.GetNextId(); + Transport = pair.Transport; + Application = pair.Application; } public LibuvOutputConsumer OutputConsumer { get; set; } @@ -131,11 +144,11 @@ public override void Abort(ConnectionAbortedException abortReason) Thread.Post(s => s.Dispose(), _socket); } - // Only called after connection middleware is complete which means the ConnectionClosed token has fired. - public void Dispose() + public override ValueTask DisposeAsync() { _connectionClosedTokenSource.Dispose(); _connectionClosingCts.Dispose(); + return base.DisposeAsync(); } // Called on Libuv thread diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransport.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs similarity index 63% rename from src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransport.cs rename to src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 4bbeae88f13f..23cef8ba4baa 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransport.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -6,30 +6,33 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Hosting; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { - internal class LibuvTransport : ITransport + internal class LibuvConnectionListener : IConnectionListener { - private readonly IEndPointInformation _endPointInformation; + private readonly EndPoint _endPoint; - private readonly List _listeners = new List(); + private readonly List _listeners = new List(); + private IAsyncEnumerator _acceptEnumerator; - public LibuvTransport(LibuvTransportContext context, IEndPointInformation endPointInformation) - : this(new LibuvFunctions(), context, endPointInformation) + public LibuvConnectionListener(LibuvTransportContext context, EndPoint endPoint) + : this(new LibuvFunctions(), context, endPoint) { } // For testing - public LibuvTransport(LibuvFunctions uv, LibuvTransportContext context, IEndPointInformation endPointInformation) + public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, EndPoint endPoint) { Libuv = uv; TransportContext = context; - _endPointInformation = endPointInformation; + _endPoint = endPoint; } public LibuvFunctions Libuv { get; } @@ -88,7 +91,7 @@ public async Task BindAsync() { var listener = new Listener(TransportContext); _listeners.Add(listener); - await listener.StartAsync(_endPointInformation, Threads[0]).ConfigureAwait(false); + await listener.StartAsync(_endPoint, Threads[0]).ConfigureAwait(false); } else { @@ -97,15 +100,16 @@ public async Task BindAsync() var listenerPrimary = new ListenerPrimary(TransportContext); _listeners.Add(listenerPrimary); - await listenerPrimary.StartAsync(pipeName, pipeMessage, _endPointInformation, Threads[0]).ConfigureAwait(false); + await listenerPrimary.StartAsync(pipeName, pipeMessage, _endPoint, Threads[0]).ConfigureAwait(false); foreach (var thread in Threads.Skip(1)) { var listenerSecondary = new ListenerSecondary(TransportContext); _listeners.Add(listenerSecondary); - await listenerSecondary.StartAsync(pipeName, pipeMessage, _endPointInformation, thread).ConfigureAwait(false); + await listenerSecondary.StartAsync(pipeName, pipeMessage, _endPoint, thread).ConfigureAwait(false); } } + _acceptEnumerator = AcceptConnections(); } catch (UvException ex) when (ex.StatusCode == LibuvConstants.EADDRINUSE) { @@ -119,9 +123,39 @@ public async Task BindAsync() } } + private async IAsyncEnumerator AcceptConnections() + { + var slots = new Task<(LibuvConnection Connection, int Slot)>[_listeners.Count]; + + // Issue parallel accepts on all listeners + for (int i = 0; i < slots.Length; i++) + { + var elem = slots[i]; + if (elem == null) + { + slots[i] = AcceptAsync(_listeners[i], i); + } + } + + while (true) + { + (LibuvConnection connection, int slot) = await await Task.WhenAny(slots); + + // Fill that slot with another accept and yield the connection + slots[slot] = AcceptAsync(_listeners[slot], slot); + + yield return connection; + } + + static async Task<(LibuvConnection, int)> AcceptAsync(ListenerContext listener, int slot) + { + return (await listener.AcceptAsync(), slot); + } + } + public async Task UnbindAsync() { - var disposeTasks = _listeners.Select(listener => listener.DisposeAsync()).ToArray(); + var disposeTasks = _listeners.Select(listener => ((IAsyncDisposable)listener).DisposeAsync()).ToArray(); if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false)) { @@ -135,5 +169,25 @@ private static async Task WaitAsync(Task task, TimeSpan timeout) { return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task; } + + public async ValueTask AcceptAsync() + { + if (await _acceptEnumerator.MoveNextAsync()) + { + return _acceptEnumerator.Current; + } + + throw new OperationCanceledException(); + } + + public async ValueTask DisposeAsync() + { + await UnbindAsync().ConfigureAwait(false); + + if (_acceptEnumerator != null) + { + await _acceptEnumerator.DisposeAsync(); + } + } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs index 9d50eb5f6778..e151b6ab17c6 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs @@ -22,7 +22,7 @@ internal class LibuvThread : PipeScheduler // otherwise it needs to wait till the next pass of the libuv loop private readonly int _maxLoops = 8; - private readonly LibuvTransport _transport; + private readonly LibuvConnectionListener _transport; private readonly IHostApplicationLifetime _appLifetime; private readonly Thread _thread; private readonly TaskCompletionSource _threadTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -40,7 +40,7 @@ internal class LibuvThread : PipeScheduler private Exception _closeError; private readonly ILibuvTrace _log; - public LibuvThread(LibuvTransport transport) + public LibuvThread(LibuvConnectionListener transport) { _transport = transport; _appLifetime = transport.AppLifetime; @@ -65,7 +65,7 @@ public LibuvThread(LibuvTransport transport) } // For testing - public LibuvThread(LibuvTransport transport, int maxLoops) + public LibuvThread(LibuvConnectionListener transport, int maxLoops) : this(transport) { _maxLoops = maxLoops; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs index 7f9e1d62f900..da5efe029758 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs @@ -13,7 +13,5 @@ internal class LibuvTransportContext public IHostApplicationLifetime AppLifetime { get; set; } public ILibuvTrace Log { get; set; } - - public IConnectionDispatcher ConnectionDispatcher { get; set; } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs index 485582355d45..cd39f5a4575e 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs @@ -2,17 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { - internal class LibuvTransportFactory : ITransportFactory + public class LibuvTransportFactory : IConnectionListenerFactory { private readonly LibuvTransportContext _baseTransportContext; + public LibuvTransportFactory() : this(Options.Create(new LibuvTransportOptions()), new DefaultHostLifetime(), NullLoggerFactory.Instance) + { + + } + public LibuvTransportFactory( IOptions options, IHostApplicationLifetime applicationLifetime, @@ -31,7 +40,7 @@ public LibuvTransportFactory( throw new ArgumentNullException(nameof(loggerFactory)); } - var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"); + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"); var trace = new LibuvTrace(logger); var threadCount = options.Value.ThreadCount; @@ -61,17 +70,31 @@ public LibuvTransportFactory( }; } - public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher dispatcher) + public async ValueTask BindAsync(EndPoint endpoint) { var transportContext = new LibuvTransportContext { Options = _baseTransportContext.Options, AppLifetime = _baseTransportContext.AppLifetime, - Log = _baseTransportContext.Log, - ConnectionDispatcher = dispatcher + Log = _baseTransportContext.Log }; - return new LibuvTransport(transportContext, endPointInformation); + var transport = new LibuvConnectionListener(transportContext, endpoint); + await transport.BindAsync(); + return transport; + } + + private class DefaultHostLifetime : IHostApplicationLifetime + { + public CancellationToken ApplicationStarted => throw new NotSupportedException(); + + public CancellationToken ApplicationStopping => throw new NotSupportedException(); + + public CancellationToken ApplicationStopped => throw new NotSupportedException(); + + public void StopApplication() + { + } } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 010a84e00660..1380020e8049 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net; +using System.Net.Sockets; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -25,10 +26,10 @@ public Listener(LibuvTransportContext transportContext) : base(transportContext) public ILibuvTrace Log => TransportContext.Log; public Task StartAsync( - IEndPointInformation endPointInformation, + EndPoint endPoint, LibuvThread thread) { - EndPointInformation = endPointInformation; + EndPoint = endPoint; Thread = thread; return Thread.PostAsync(listener => @@ -43,39 +44,41 @@ public Task StartAsync( /// private UvStreamHandle CreateListenSocket() { - switch (EndPointInformation.Type) + switch (EndPoint) { - case ListenType.IPEndPoint: - return ListenTcp(useFileHandle: false); - case ListenType.SocketPath: - return ListenPipe(useFileHandle: false); - case ListenType.FileHandle: - return ListenHandle(); + case IPEndPoint ip: + return ListenTcp(useFileHandle: false, ip); + case UnixDomainSocketEndPoint domainSocketEndPoint: + return ListenPipe(useFileHandle: false, domainSocketEndPoint); + //case ListenType.FileHandle: + // return ListenHandle(); default: throw new NotSupportedException(); } } - private UvTcpHandle ListenTcp(bool useFileHandle) + private UvTcpHandle ListenTcp(bool useFileHandle, IPEndPoint endPoint) { var socket = new UvTcpHandle(Log); try { socket.Init(Thread.Loop, Thread.QueueCloseHandle); - socket.NoDelay(EndPointInformation.NoDelay); + // socket.NoDelay(EndPointInformation.NoDelay); - if (!useFileHandle) - { - socket.Bind(EndPointInformation.IPEndPoint); + socket.Bind(endPoint); - // If requested port was "0", replace with assigned dynamic port. - EndPointInformation.IPEndPoint = socket.GetSockIPEndPoint(); - } - else - { - socket.Open((IntPtr)EndPointInformation.FileHandle); - } + //if (!useFileHandle) + //{ + // socket.Bind(EndPointInformation.IPEndPoint); + + // // If requested port was "0", replace with assigned dynamic port. + // EndPointInformation.IPEndPoint = socket.GetSockIPEndPoint(); + //} + //else + //{ + // socket.Open((IntPtr)EndPointInformation.FileHandle); + //} } catch { @@ -86,7 +89,7 @@ private UvTcpHandle ListenTcp(bool useFileHandle) return socket; } - private UvPipeHandle ListenPipe(bool useFileHandle) + private UvPipeHandle ListenPipe(bool useFileHandle, UnixDomainSocketEndPoint domainSocketEndPoint) { var pipe = new UvPipeHandle(Log); @@ -96,11 +99,11 @@ private UvPipeHandle ListenPipe(bool useFileHandle) if (!useFileHandle) { - pipe.Bind(EndPointInformation.SocketPath); + pipe.Bind(domainSocketEndPoint.ToString()); } else { - pipe.Open((IntPtr)EndPointInformation.FileHandle); + // pipe.Open((IntPtr)EndPointInformation.FileHandle); } } catch @@ -112,36 +115,36 @@ private UvPipeHandle ListenPipe(bool useFileHandle) return pipe; } - private UvStreamHandle ListenHandle() - { - switch (EndPointInformation.HandleType) - { - case FileHandleType.Auto: - break; - case FileHandleType.Tcp: - return ListenTcp(useFileHandle: true); - case FileHandleType.Pipe: - return ListenPipe(useFileHandle: true); - default: - throw new NotSupportedException(); - } - - UvStreamHandle handle; - try - { - handle = ListenTcp(useFileHandle: true); - EndPointInformation.HandleType = FileHandleType.Tcp; - return handle; - } - catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP) - { - Log.LogDebug(0, exception, "Listener.ListenHandle"); - } - - handle = ListenPipe(useFileHandle: true); - EndPointInformation.HandleType = FileHandleType.Pipe; - return handle; - } + //private UvStreamHandle ListenHandle() + //{ + // switch (EndPointInformation.HandleType) + // { + // case FileHandleType.Auto: + // break; + // case FileHandleType.Tcp: + // return ListenTcp(useFileHandle: true); + // case FileHandleType.Pipe: + // return ListenPipe(useFileHandle: true); + // default: + // throw new NotSupportedException(); + // } + + // UvStreamHandle handle; + // try + // { + // handle = ListenTcp(useFileHandle: true); + // EndPointInformation.HandleType = FileHandleType.Tcp; + // return handle; + // } + // catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP) + // { + // Log.LogDebug(0, exception, "Listener.ListenHandle"); + // } + + // handle = ListenPipe(useFileHandle: true); + // EndPointInformation.HandleType = FileHandleType.Pipe; + // return handle; + //} private static void ConnectionCallback(UvStreamHandle stream, int status, UvException error, object state) { @@ -186,13 +189,12 @@ private void OnConnection(UvStreamHandle listenSocket, int status) protected virtual void DispatchConnection(UvStreamHandle socket) { - // REVIEW: This task should be tracked by the server for graceful shutdown - // Today it's handled specifically for http but not for arbitrary middleware _ = HandleConnectionAsync(socket); } public virtual async Task DisposeAsync() { + StopAcceptingConnections(); // Ensure the event loop is still running. // If the event loop isn't running and we try to wait on this Post // to complete, then LibuvTransport will never be disposed and diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index e153565af80a..43286ed5af0a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -3,8 +3,9 @@ using System; using System.Net; +using System.Net.Sockets; +using System.Threading.Channels; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -12,6 +13,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal class ListenerContext { + // REVIEW: This needs to be bounded and we need a strategy for what to do when the queue is full + private Channel _acceptQueue = Channel.CreateBounded(new BoundedChannelOptions(512) + { + // REVIEW: Not sure if this is right as nothing is stopping the libuv callback today + FullMode = BoundedChannelFullMode.Wait + }); + public ListenerContext(LibuvTransportContext transportContext) { TransportContext = transportContext; @@ -19,23 +27,28 @@ public ListenerContext(LibuvTransportContext transportContext) public LibuvTransportContext TransportContext { get; set; } - public IEndPointInformation EndPointInformation { get; set; } + public EndPoint EndPoint { get; set; } public LibuvThread Thread { get; set; } + public ValueTask AcceptAsync() + { + return _acceptQueue.Reader.ReadAsync(); + } + /// /// Creates a socket which can be used to accept an incoming connection. /// protected UvStreamHandle CreateAcceptSocket() { - switch (EndPointInformation.Type) + switch (EndPoint) { - case ListenType.IPEndPoint: + case IPEndPoint ip: return AcceptTcp(); - case ListenType.SocketPath: + case UnixDomainSocketEndPoint domainSocket: return AcceptPipe(); - case ListenType.FileHandle: - return AcceptHandle(); + //case ListenType.FileHandle: + // return AcceptHandle(); default: throw new InvalidOperationException(); } @@ -64,13 +77,9 @@ protected async Task HandleConnectionAsync(UvStreamHandle socket) } var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint); - var middlewareTask = TransportContext.ConnectionDispatcher.OnConnection(connection); - var transportTask = connection.Start(); - - await transportTask; - await middlewareTask; + _ = connection.Start(); - connection.Dispose(); + await _acceptQueue.Writer.WriteAsync(connection); } catch (Exception ex) { @@ -85,7 +94,7 @@ private UvTcpHandle AcceptTcp() try { socket.Init(Thread.Loop, Thread.QueueCloseHandle); - socket.NoDelay(EndPointInformation.NoDelay); + // socket.NoDelay(EndPointInformation.NoDelay); } catch { @@ -113,19 +122,25 @@ private UvPipeHandle AcceptPipe() return pipe; } - private UvStreamHandle AcceptHandle() + protected void StopAcceptingConnections() { - switch (EndPointInformation.HandleType) - { - case FileHandleType.Auto: - throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first."); - case FileHandleType.Tcp: - return AcceptTcp(); - case FileHandleType.Pipe: - return AcceptPipe(); - default: - throw new NotSupportedException(); - } + _acceptQueue.Writer.Complete(); } + + // TODO: We need a new custom endpoint + //private UvStreamHandle AcceptHandle() + //{ + // switch (EndPointInformation.HandleType) + // { + // case FileHandleType.Auto: + // throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first."); + // case FileHandleType.Tcp: + // return AcceptTcp(); + // case FileHandleType.Pipe: + // return AcceptPipe(); + // default: + // throw new NotSupportedException(); + // } + //} } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs index 4ce0afab29be..c6051198a07f 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; @@ -46,7 +47,7 @@ public ListenerPrimary(LibuvTransportContext transportContext) : base(transportC public async Task StartAsync( string pipeName, byte[] pipeMessage, - IEndPointInformation endPointInformation, + EndPoint endPoint, LibuvThread thread) { _pipeName = pipeName; @@ -59,7 +60,7 @@ public async Task StartAsync( Marshal.StructureToPtr(fileCompletionInfo, _fileCompletionInfoPtr, false); } - await StartAsync(endPointInformation, thread).ConfigureAwait(false); + await StartAsync(endPoint, thread).ConfigureAwait(false); await Thread.PostAsync(listener => listener.PostCallback(), this).ConfigureAwait(false); } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs index 6597df28d77d..fab9a4e43614 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.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.Net; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -35,14 +36,14 @@ public ListenerSecondary(LibuvTransportContext transportContext) : base(transpor public Task StartAsync( string pipeName, byte[] pipeMessage, - IEndPointInformation endPointInformation, + EndPoint endPoint, LibuvThread thread) { _pipeName = pipeName; _pipeMessage = pipeMessage; _buf = thread.Loop.Libuv.buf_init(_ptr, 4); - EndPointInformation = endPointInformation; + EndPoint = endPoint; Thread = thread; DispatchPipe = new UvPipeHandle(Log); @@ -152,8 +153,6 @@ private void ReadStartCallback(UvStreamHandle handle, int status) { DispatchPipe.Accept(acceptSocket); - // REVIEW: This task should be tracked by the server for graceful shutdown - // Today it's handled specifically for http but not for arbitrary middleware _ = HandleConnectionAsync(acceptSocket); } catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode)) @@ -179,6 +178,7 @@ private void FreeBuffer() public async Task DisposeAsync() { + StopAcceptingConnections(); // Ensure the event loop is still running. // If the event loop isn't running and we try to wait on this Post // to complete, then LibuvTransport will never be disposed and diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj index 2b52fe7f4627..3d9faac3ca5f 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj +++ b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj @@ -10,12 +10,17 @@ true + + + + + diff --git a/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs b/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs index 386d0b667950..c8514caed2c0 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; @@ -24,7 +25,7 @@ public static IWebHostBuilder UseLibuv(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => { - services.AddSingleton(); + services.AddSingleton(); }); } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs index c284ec1584d4..79345760611a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs @@ -22,7 +22,7 @@ public async Task DoesNotEndConnectionOnZeroRead() var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvTransport(mockLibuv, transportContext, null); + var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); Task connectionTask = null; try @@ -62,7 +62,7 @@ public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvTransport(mockLibuv, transportContext, null); + var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); mockConnectionDispatcher.InputOptions = pool => new PipeOptions( @@ -127,7 +127,7 @@ public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketClos var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvTransport(mockLibuv, transportContext, null); + var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); var mockScheduler = new Mock(); Action backPressure = null; @@ -212,7 +212,7 @@ public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCa var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvTransport(mockLibuv, transportContext, null); + var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); Task connectionTask = null; diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs index d72e6485b50a..1616414d95c3 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs @@ -45,7 +45,7 @@ public LibuvOutputConsumerTests() _memoryPool = KestrelMemoryPool.Create(); _mockLibuv = new MockLibuv(); - var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0)); + var libuvTransport = new LibuvConnectionListener(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0)); _libuvThread = new LibuvThread(libuvTransport, maxLoops: 1); _libuvThread.StartAsync().Wait(); } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs index 69daacab351a..9c1cf34e4214 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs @@ -17,7 +17,7 @@ public async Task LibuvThreadDoesNotThrowIfPostingWorkAfterDispose() var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvTransport(mockLibuv, transportContext, null); + var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); var ranOne = false; var ranTwo = false; diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index abdd8e1c7a13..4f58b1677a22 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -38,7 +38,7 @@ public class LibuvTransportTests public async Task TransportCanBindAndStop() { var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvTransport(transportContext, + var transport = new LibuvConnectionListener(transportContext, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))); // The transport can no longer start threads without binding to an endpoint. @@ -50,7 +50,7 @@ public async Task TransportCanBindAndStop() public async Task TransportCanBindUnbindAndStop() { var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvTransport(transportContext, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))); + var transport = new LibuvConnectionListener(transportContext, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))); await transport.BindAsync(); await transport.UnbindAsync(); @@ -69,7 +69,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) ConnectionDispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()) }; - var transport = new LibuvTransport(transportContext, listenOptions); + var transport = new LibuvConnectionListener(transportContext, listenOptions); await transport.BindAsync(); @@ -110,7 +110,7 @@ public async Task OneToTenThreads(int threadCount) Options = new LibuvTransportOptions { ThreadCount = threadCount } }; - var transport = new LibuvTransport(transportContext, listenOptions); + var transport = new LibuvConnectionListener(transportContext, listenOptions); await transport.BindAsync(); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index b1a338bd56dd..15a73a25b92c 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -42,7 +42,7 @@ public async Task ConnectionsGetRoundRobinedToSecondaryListeners() var transportContextSecondary = new TestLibuvTransportContext(); transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions); + var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeMessage = Guid.NewGuid().ToByteArray(); @@ -117,7 +117,7 @@ public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() var transportContextSecondary = new TestLibuvTransportContext(); transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions); + var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeMessage = Guid.NewGuid().ToByteArray(); @@ -229,7 +229,7 @@ public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() var transportContextSecondary = new TestLibuvTransportContext(); transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions); + var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeMessage = Guid.NewGuid().ToByteArray(); diff --git a/src/Servers/Kestrel/samples/PlaintextApp/PlaintextApp.csproj b/src/Servers/Kestrel/samples/PlaintextApp/PlaintextApp.csproj index 86fd68771b90..8b48f958a95a 100644 --- a/src/Servers/Kestrel/samples/PlaintextApp/PlaintextApp.csproj +++ b/src/Servers/Kestrel/samples/PlaintextApp/PlaintextApp.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -8,6 +8,7 @@ + diff --git a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs index 15de3bf2e2ed..0ab6a4d3f345 100644 --- a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs +++ b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; namespace PlaintextApp @@ -40,20 +41,22 @@ public static async Task Main(string[] args) { options.Listen(IPAddress.Loopback, 5001); }) + .UseLibuv() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .Build(); var hostTask = host.RunAsync(); - var serverTask = ServerAsync(5002); + // var serverTask = ServerAsync(new SocketTransportFactory(), 5002); + // var serverTask2 = ServerAsync(new LibuvTransportFactory(), 5003); await hostTask; - await serverTask; + // await serverTask; + // await serverTask2; } - private static async Task ServerAsync(int port) + private static async Task ServerAsync(IConnectionListenerFactory factory, int port) { - var factory = new SocketTransportFactory(); await using var listener = await factory.BindAsync(new IPEndPoint(IPAddress.Loopback, port)); while (true) From b4e7ac842c144bf3c55cd38229e884099938f167 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 17 May 2019 08:59:35 -0700 Subject: [PATCH 03/78] Removed features --- .../src/IConnectionFactory.cs | 3 +- .../Internal/IApplicationTransportFeature.cs | 12 ----- .../src/Internal/IConnectionDispatcher.cs | 12 ----- .../src/Internal/ITransport.cs | 15 ------ .../src/Internal/ITransportFactory.cs | 10 ---- .../Internal/ITransportSchedulerFeature.cs | 14 ------ .../TransportConnection.FeatureCollection.cs | 11 ----- .../Internal/TransportConnection.Generated.cs | 46 ------------------- .../src/SocketTransportFactory.cs | 3 +- .../TransportConnectionFeatureCollection.cs | 2 - 10 files changed, 4 insertions(+), 124 deletions(-) delete mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/Internal/IApplicationTransportFeature.cs delete mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/Internal/IConnectionDispatcher.cs delete mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransport.cs delete mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportFactory.cs delete mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportSchedulerFeature.cs diff --git a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs index 2ef2aaa7f5f0..1770171c5e98 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Connections { public interface IConnectionFactory { - ValueTask ConnectAsync(System.Net.EndPoint endpoint); + ValueTask ConnectAsync(System.Net.EndPoint endpoint, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IApplicationTransportFeature.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IApplicationTransportFeature.cs deleted file mode 100644 index 8aa8328a6b2a..000000000000 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IApplicationTransportFeature.cs +++ /dev/null @@ -1,12 +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.IO.Pipelines; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface IApplicationTransportFeature - { - IDuplexPipe Application { get; set; } - } -} diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IConnectionDispatcher.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IConnectionDispatcher.cs deleted file mode 100644 index 813c541d1a56..000000000000 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IConnectionDispatcher.cs +++ /dev/null @@ -1,12 +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.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface IConnectionDispatcher - { - Task OnConnection(TransportConnection connection); - } -} diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransport.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransport.cs deleted file mode 100644 index 5a6dc0c20c81..000000000000 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransport.cs +++ /dev/null @@ -1,15 +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.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface ITransport - { - // Can only be called once per ITransport - Task BindAsync(); - Task UnbindAsync(); - Task StopAsync(); - } -} diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportFactory.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportFactory.cs deleted file mode 100644 index 4037467e87a5..000000000000 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportFactory.cs +++ /dev/null @@ -1,10 +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. - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface ITransportFactory - { - ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher dispatcher); - } -} diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportSchedulerFeature.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportSchedulerFeature.cs deleted file mode 100644 index c4df6d5a3799..000000000000 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportSchedulerFeature.cs +++ /dev/null @@ -1,14 +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.IO.Pipelines; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface ITransportSchedulerFeature - { - PipeScheduler InputWriterScheduler { get; } - - PipeScheduler OutputReaderScheduler { get; } - } -} diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs index 7f98162507c8..4165460c11c1 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs @@ -20,8 +20,6 @@ public partial class TransportConnection : IHttpConnectionFeature, IConnectionTransportFeature, IConnectionItemsFeature, IMemoryPoolFeature, - IApplicationTransportFeature, - ITransportSchedulerFeature, IConnectionLifetimeFeature, IConnectionHeartbeatFeature, IConnectionLifetimeNotificationFeature, @@ -72,21 +70,12 @@ IDuplexPipe IConnectionTransportFeature.Transport set => Transport = value; } - IDuplexPipe IApplicationTransportFeature.Application - { - get => Application; - set => Application = value; - } - IDictionary IConnectionItemsFeature.Items { get => Items; set => Items = value; } - PipeScheduler ITransportSchedulerFeature.InputWriterScheduler => InputWriterScheduler; - PipeScheduler ITransportSchedulerFeature.OutputReaderScheduler => OutputReaderScheduler; - CancellationToken IConnectionLifetimeFeature.ConnectionClosed { get => ConnectionClosed; diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs index b5d0122ffb03..b43bde362d61 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs @@ -17,8 +17,6 @@ public partial class TransportConnection : IFeatureCollection private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); - private static readonly Type IApplicationTransportFeatureType = typeof(IApplicationTransportFeature); - private static readonly Type ITransportSchedulerFeatureType = typeof(ITransportSchedulerFeature); private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); private static readonly Type IConnectionHeartbeatFeatureType = typeof(IConnectionHeartbeatFeature); private static readonly Type IConnectionLifetimeNotificationFeatureType = typeof(IConnectionLifetimeNotificationFeature); @@ -29,8 +27,6 @@ public partial class TransportConnection : IFeatureCollection private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; private object _currentIMemoryPoolFeature; - private object _currentIApplicationTransportFeature; - private object _currentITransportSchedulerFeature; private object _currentIConnectionLifetimeFeature; private object _currentIConnectionHeartbeatFeature; private object _currentIConnectionLifetimeNotificationFeature; @@ -47,8 +43,6 @@ private void FastReset() _currentIConnectionTransportFeature = this; _currentIConnectionItemsFeature = this; _currentIMemoryPoolFeature = this; - _currentIApplicationTransportFeature = this; - _currentITransportSchedulerFeature = this; _currentIConnectionLifetimeFeature = this; _currentIConnectionHeartbeatFeature = this; _currentIConnectionLifetimeNotificationFeature = this; @@ -128,14 +122,6 @@ object IFeatureCollection.this[Type key] { feature = _currentIMemoryPoolFeature; } - else if (key == IApplicationTransportFeatureType) - { - feature = _currentIApplicationTransportFeature; - } - else if (key == ITransportSchedulerFeatureType) - { - feature = _currentITransportSchedulerFeature; - } else if (key == IConnectionLifetimeFeatureType) { feature = _currentIConnectionLifetimeFeature; @@ -184,14 +170,6 @@ object IFeatureCollection.this[Type key] { _currentIMemoryPoolFeature = value; } - else if (key == IApplicationTransportFeatureType) - { - _currentIApplicationTransportFeature = value; - } - else if (key == ITransportSchedulerFeatureType) - { - _currentITransportSchedulerFeature = value; - } else if (key == IConnectionLifetimeFeatureType) { _currentIConnectionLifetimeFeature = value; @@ -238,14 +216,6 @@ TFeature IFeatureCollection.Get() { feature = (TFeature)_currentIMemoryPoolFeature; } - else if (typeof(TFeature) == typeof(IApplicationTransportFeature)) - { - feature = (TFeature)_currentIApplicationTransportFeature; - } - else if (typeof(TFeature) == typeof(ITransportSchedulerFeature)) - { - feature = (TFeature)_currentITransportSchedulerFeature; - } else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) { feature = (TFeature)_currentIConnectionLifetimeFeature; @@ -293,14 +263,6 @@ void IFeatureCollection.Set(TFeature feature) { _currentIMemoryPoolFeature = feature; } - else if (typeof(TFeature) == typeof(IApplicationTransportFeature)) - { - _currentIApplicationTransportFeature = feature; - } - else if (typeof(TFeature) == typeof(ITransportSchedulerFeature)) - { - _currentITransportSchedulerFeature = feature; - } else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) { _currentIConnectionLifetimeFeature = feature; @@ -345,14 +307,6 @@ private IEnumerable> FastEnumerable() { yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); } - if (_currentIApplicationTransportFeature != null) - { - yield return new KeyValuePair(IApplicationTransportFeatureType, _currentIApplicationTransportFeature); - } - if (_currentITransportSchedulerFeature != null) - { - yield return new KeyValuePair(ITransportSchedulerFeatureType, _currentITransportSchedulerFeature); - } if (_currentIConnectionLifetimeFeature != null) { yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs index 713982346255..90c55e3e0d63 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs @@ -5,6 +5,7 @@ using System.IO.Pipelines; using System.Net; using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; @@ -50,7 +51,7 @@ public ValueTask BindAsync(EndPoint endpoint) return new ValueTask(transport); } - public async ValueTask ConnectAsync(EndPoint endpoint) + public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { // REVIEW: How do we pick the type of socket? Is the endpoint enough? var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index 93d0339a53d7..cd332f16af66 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -16,8 +16,6 @@ public static string GenerateFile() "IConnectionTransportFeature", "IConnectionItemsFeature", "IMemoryPoolFeature", - "IApplicationTransportFeature", - "ITransportSchedulerFeature", "IConnectionLifetimeFeature", "IConnectionHeartbeatFeature", "IConnectionLifetimeNotificationFeature", From a60552014b808236e36745f630a4fe5ff7c20e83 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 17 May 2019 09:25:05 -0700 Subject: [PATCH 04/78] Added EndPoint to IConnectionListener --- .../src/IConnectionListener.cs | 3 +++ src/Servers/Kestrel/Core/src/KestrelServer.cs | 11 +++++--- src/Servers/Kestrel/Core/src/ListenOptions.cs | 5 +++- .../src/Internal/LibuvConnectionListener.cs | 27 +++++++++++-------- .../Transport.Libuv/src/Internal/Listener.cs | 2 +- .../src/SocketConnectionListener.cs | 21 ++++++--------- .../Kestrel/samples/PlaintextApp/Startup.cs | 2 +- 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 4421222d1d20..481e5193ad09 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -7,6 +8,8 @@ namespace Microsoft.AspNetCore.Connections { public interface IConnectionListener { + EndPoint Endpoint { get; } + ValueTask AcceptAsync(); ValueTask DisposeAsync(); diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index edf1c297b069..e86df8c6430b 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -137,12 +137,12 @@ public async Task StartAsync(IHttpApplication application, C ServiceContext.Heartbeat?.Start(); - async Task OnBind(ListenOptions endpoint) + async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware - endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application, endpoint.Protocols); + options.UseHttpServer(options.ConnectionAdapters, ServiceContext, application, options.Protocols); - var connectionDelegate = endpoint.Build(); + var connectionDelegate = options.Build(); // Add the connection limit middleware if (Options.Limits.MaxConcurrentConnections.HasValue) @@ -151,7 +151,10 @@ async Task OnBind(ListenOptions endpoint) } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); - var transport = await _transportFactory.BindAsync(endpoint.Endpoint).ConfigureAwait(false); + var transport = await _transportFactory.BindAsync(options.Endpoint).ConfigureAwait(false); + + // Update the endpoint + options.Endpoint = transport.Endpoint; _transports.Add(transport); connectionDispatcher.StartAcceptingConnections(transport); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index ed4e2e136276..20b067c42fdd 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; @@ -26,12 +27,14 @@ internal ListenOptions(IPEndPoint endPoint) { Type = ListenType.IPEndPoint; IPEndPoint = endPoint; + Endpoint = endPoint; } internal ListenOptions(string socketPath) { Type = ListenType.SocketPath; SocketPath = socketPath; + Endpoint = new UnixDomainSocketEndPoint(socketPath); } internal ListenOptions(ulong fileHandle) @@ -90,7 +93,7 @@ public FileHandleType HandleType } } - public EndPoint Endpoint => IPEndPoint; + public EndPoint Endpoint { get; set; } // IPEndPoint is mutable so port 0 can be updated to the bound port. /// diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 23cef8ba4baa..6e9d3ab72292 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -4,21 +4,17 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Hosting; -using System.Net; -using System.Runtime.CompilerServices; -using System.Threading; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal class LibuvConnectionListener : IConnectionListener { - private readonly EndPoint _endPoint; - private readonly List _listeners = new List(); private IAsyncEnumerator _acceptEnumerator; @@ -32,7 +28,7 @@ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, Libuv = uv; TransportContext = context; - _endPoint = endPoint; + Endpoint = endPoint; } public LibuvFunctions Libuv { get; } @@ -43,6 +39,8 @@ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, public ILibuvTrace Log => TransportContext.Log; public LibuvTransportOptions TransportOptions => TransportContext.Options; + public EndPoint Endpoint { get; set; } + public async Task StopAsync() { try @@ -91,7 +89,13 @@ public async Task BindAsync() { var listener = new Listener(TransportContext); _listeners.Add(listener); - await listener.StartAsync(_endPoint, Threads[0]).ConfigureAwait(false); + await listener.StartAsync(Endpoint, Threads[0]).ConfigureAwait(false); + + if (listener.ListenSocket is UvTcpHandle handle) + { + // If requested port was "0", replace with assigned dynamic port. + Endpoint = handle.GetSockIPEndPoint(); + } } else { @@ -100,13 +104,13 @@ public async Task BindAsync() var listenerPrimary = new ListenerPrimary(TransportContext); _listeners.Add(listenerPrimary); - await listenerPrimary.StartAsync(pipeName, pipeMessage, _endPoint, Threads[0]).ConfigureAwait(false); + await listenerPrimary.StartAsync(pipeName, pipeMessage, Endpoint, Threads[0]).ConfigureAwait(false); foreach (var thread in Threads.Skip(1)) { var listenerSecondary = new ListenerSecondary(TransportContext); _listeners.Add(listenerSecondary); - await listenerSecondary.StartAsync(pipeName, pipeMessage, _endPoint, thread).ConfigureAwait(false); + await listenerSecondary.StartAsync(pipeName, pipeMessage, Endpoint, thread).ConfigureAwait(false); } } _acceptEnumerator = AcceptConnections(); @@ -139,7 +143,8 @@ private async IAsyncEnumerator AcceptConnections() while (true) { - (LibuvConnection connection, int slot) = await await Task.WhenAny(slots); + // Calling GetAwaiter().GetResult() is safe because we know the task is completed + (LibuvConnection connection, int slot) = (await Task.WhenAny(slots)).GetAwaiter().GetResult(); // Fill that slot with another accept and yield the connection slots[slot] = AcceptAsync(_listeners[slot], slot); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 1380020e8049..fdd4e4784dec 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -21,7 +21,7 @@ public Listener(LibuvTransportContext transportContext) : base(transportContext) { } - protected UvStreamHandle ListenSocket { get; private set; } + public UvStreamHandle ListenSocket { get; private set; } public ILibuvTrace Log => TransportContext.Log; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 74a98adefac3..fc6f944ccca9 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -16,13 +16,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets internal sealed class SocketConnectionListener : IConnectionListener { private readonly MemoryPool _memoryPool; - private readonly IPEndPoint _endpoint; private readonly int _numSchedulers; private readonly PipeScheduler[] _schedulers; private readonly ISocketsTrace _trace; private Socket _listenSocket; private int _schedulerIndex; + public EndPoint Endpoint { get; private set; } + internal SocketConnectionListener( EndPoint endpoint, int ioQueueCount, @@ -33,7 +34,7 @@ internal SocketConnectionListener( Debug.Assert(endpoint is IPEndPoint); Debug.Assert(trace != null); - _endpoint = (IPEndPoint)endpoint; + Endpoint = endpoint; _trace = trace; _memoryPool = memoryPool; @@ -62,32 +63,26 @@ internal void Bind() throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } - IPEndPoint endPoint = _endpoint; + var ip = (IPEndPoint)Endpoint; - var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + var listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 - if (endPoint.Address == IPAddress.IPv6Any) + if (ip.Address == IPAddress.IPv6Any) { listenSocket.DualMode = true; } try { - listenSocket.Bind(endPoint); + listenSocket.Bind(ip); } catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) { throw new AddressInUseException(e.Message, e); } - // TODO: This is a problem, we need to enable a way for the caller to know what address got bound. - // This is specific to IPEndpoint - // If requested port was "0", replace with assigned dynamic port. - //if (_endPointInformation.IPEndPoint.Port == 0) - //{ - // _endPointInformation.IPEndPoint = (IPEndPoint)listenSocket.LocalEndPoint; - //} + Endpoint = listenSocket.LocalEndPoint; listenSocket.Listen(512); diff --git a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs index 0ab6a4d3f345..4614d3f2ac63 100644 --- a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs +++ b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs @@ -41,7 +41,7 @@ public static async Task Main(string[] args) { options.Listen(IPAddress.Loopback, 5001); }) - .UseLibuv() + // .UseLibuv() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .Build(); From 582cbbfd459926e60c6a517677d31812ba390ecc Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 17 May 2019 15:33:21 -0700 Subject: [PATCH 05/78] Remove null check --- .../Transport.Libuv/src/Internal/LibuvConnectionListener.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 6e9d3ab72292..9840ddbed3d9 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -134,11 +134,7 @@ private async IAsyncEnumerator AcceptConnections() // Issue parallel accepts on all listeners for (int i = 0; i < slots.Length; i++) { - var elem = slots[i]; - if (elem == null) - { - slots[i] = AcceptAsync(_listeners[i], i); - } + slots[i] = AcceptAsync(_listeners[i], i); } while (true) From 6d539b116119459bfaf1ab9579fa49ae2f05dccb Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 17 May 2019 16:11:04 -0700 Subject: [PATCH 06/78] Null means no more connections --- .../Kestrel/Core/src/Internal/ConnectionDispatcher.cs | 6 ++++++ .../Transport.Libuv/src/Internal/LibuvConnectionListener.cs | 3 ++- .../Transport.Sockets/src/SocketConnectionListener.cs | 5 ++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 7c3c622deb37..1f20527b6137 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -46,6 +46,12 @@ async Task AcceptConnectionsAsync() { var connection = await listener.AcceptAsync(); + if (connection == null) + { + // We're done listening + break; + } + // TODO: We need a bit of command and control to do connection management and that requires // that we have access to a couple of methods that only exists on TransportConnection // (specifically RequestClose, TickHeartbeat and CompleteAsync. This dependency needs to be removed or diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 9840ddbed3d9..6fc31713b4c7 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -178,7 +178,8 @@ public async ValueTask AcceptAsync() return _acceptEnumerator.Current; } - throw new OperationCanceledException(); + // null means we're done... + return null; } public async ValueTask DisposeAsync() diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index fc6f944ccca9..3994826f3a2c 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -106,10 +106,9 @@ public async ValueTask AcceptAsync() return connection; } - catch (SocketException ex) + catch (SocketException) { - // REVIEW: Do we need more exception types? - throw new ConnectionResetException("The connection was reset", ex); + return null; } } From 2c797d094432d538ab5a1c31b272269961769af5 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 00:54:34 -0700 Subject: [PATCH 07/78] More cleanup - Added NoDelay to transport options - Added FileHandleEndPoint to represent listening to a file handle --- .../src/IConnectionListenerFactory.cs | 3 +- src/Servers/Kestrel/Core/src/KestrelServer.cs | 4 +- src/Servers/Kestrel/Core/src/ListenOptions.cs | 3 + .../src/FileHandleEndPoint.cs | 20 ++++ .../src/{Internal => }/FileHandleType.cs | 4 +- .../Transport.Libuv/src/Internal/Listener.cs | 103 +++++++++--------- .../src/Internal/ListenerContext.cs | 42 +++---- .../src/LibuvTransportOptions.cs | 4 +- .../src/SocketConnectionListener.cs | 22 ++-- .../src/SocketTransportFactory.cs | 2 +- .../src/SocketTransportOptions.cs | 4 +- 11 files changed, 116 insertions(+), 95 deletions(-) create mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs rename src/Servers/Kestrel/Transport.Abstractions/src/{Internal => }/FileHandleType.cs (83%) diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs index 6f7dcb1bbc74..60ad5053a087 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -7,6 +8,6 @@ namespace Microsoft.AspNetCore.Connections { public interface IConnectionListenerFactory { - ValueTask BindAsync(System.Net.EndPoint endpoint); + ValueTask BindAsync(EndPoint endpoint); } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index e86df8c6430b..9cc9cba38276 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -186,6 +186,7 @@ public async Task StopAsync(CancellationToken cancellationToken) { tasks[i] = _transports[i].DisposeAsync().AsTask(); } + await Task.WhenAll(tasks).ConfigureAwait(false); if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) @@ -198,12 +199,11 @@ public async Task StopAsync(CancellationToken cancellationToken) } } - // ???? //for (int i = 0; i < _transports.Count; i++) //{ // tasks[i] = _transports[i].StopAsync(); //} - await Task.WhenAll(tasks).ConfigureAwait(false); + //await Task.WhenAll(tasks).ConfigureAwait(false); ServiceContext.Heartbeat?.Dispose(); } diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 20b067c42fdd..2fde7f84b465 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core @@ -44,6 +45,8 @@ internal ListenOptions(ulong fileHandle) internal ListenOptions(ulong fileHandle, FileHandleType handleType) { + Endpoint = new FileHandleEndPoint(fileHandle, handleType); + Type = ListenType.FileHandle; FileHandle = fileHandle; switch (handleType) diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs new file mode 100644 index 000000000000..4aef9eb66ecc --- /dev/null +++ b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions +{ + public class FileHandleEndPoint : EndPoint + { + public FileHandleEndPoint(ulong fileHandle, FileHandleType fileHandleType) + { + FileHandle = fileHandle; + FileHandleType = fileHandleType; + } + + public ulong FileHandle { get; } + public FileHandleType FileHandleType { get; } + } +} diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/FileHandleType.cs b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleType.cs similarity index 83% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/FileHandleType.cs rename to src/Servers/Kestrel/Transport.Abstractions/src/FileHandleType.cs index bb70e4ec344a..d9b8a50acc57 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/FileHandleType.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleType.cs @@ -1,7 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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. -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions { /// /// Enumerates the types. diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index fdd4e4784dec..6aa8cb72b5c5 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -46,39 +47,34 @@ private UvStreamHandle CreateListenSocket() { switch (EndPoint) { - case IPEndPoint ip: - return ListenTcp(useFileHandle: false, ip); - case UnixDomainSocketEndPoint domainSocketEndPoint: - return ListenPipe(useFileHandle: false, domainSocketEndPoint); - //case ListenType.FileHandle: - // return ListenHandle(); + case IPEndPoint _: + return ListenTcp(useFileHandle: false); + case UnixDomainSocketEndPoint _: + return ListenPipe(useFileHandle: false); + case FileHandleEndPoint _: + return ListenHandle(); default: throw new NotSupportedException(); } } - private UvTcpHandle ListenTcp(bool useFileHandle, IPEndPoint endPoint) + private UvTcpHandle ListenTcp(bool useFileHandle) { var socket = new UvTcpHandle(Log); try { socket.Init(Thread.Loop, Thread.QueueCloseHandle); - // socket.NoDelay(EndPointInformation.NoDelay); + socket.NoDelay(TransportContext.Options.NoDelay); - socket.Bind(endPoint); - - //if (!useFileHandle) - //{ - // socket.Bind(EndPointInformation.IPEndPoint); - - // // If requested port was "0", replace with assigned dynamic port. - // EndPointInformation.IPEndPoint = socket.GetSockIPEndPoint(); - //} - //else - //{ - // socket.Open((IntPtr)EndPointInformation.FileHandle); - //} + if (!useFileHandle) + { + socket.Bind((IPEndPoint)EndPoint); + } + else + { + socket.Open((IntPtr)((FileHandleEndPoint)EndPoint).FileHandle); + } } catch { @@ -89,7 +85,7 @@ private UvTcpHandle ListenTcp(bool useFileHandle, IPEndPoint endPoint) return socket; } - private UvPipeHandle ListenPipe(bool useFileHandle, UnixDomainSocketEndPoint domainSocketEndPoint) + private UvPipeHandle ListenPipe(bool useFileHandle) { var pipe = new UvPipeHandle(Log); @@ -99,11 +95,12 @@ private UvPipeHandle ListenPipe(bool useFileHandle, UnixDomainSocketEndPoint dom if (!useFileHandle) { - pipe.Bind(domainSocketEndPoint.ToString()); + // UnixDomainSocketEndPoint.ToString() returns the path + pipe.Bind(EndPoint.ToString()); } else { - // pipe.Open((IntPtr)EndPointInformation.FileHandle); + pipe.Open((IntPtr)((FileHandleEndPoint)EndPoint).FileHandle); } } catch @@ -115,36 +112,34 @@ private UvPipeHandle ListenPipe(bool useFileHandle, UnixDomainSocketEndPoint dom return pipe; } - //private UvStreamHandle ListenHandle() - //{ - // switch (EndPointInformation.HandleType) - // { - // case FileHandleType.Auto: - // break; - // case FileHandleType.Tcp: - // return ListenTcp(useFileHandle: true); - // case FileHandleType.Pipe: - // return ListenPipe(useFileHandle: true); - // default: - // throw new NotSupportedException(); - // } - - // UvStreamHandle handle; - // try - // { - // handle = ListenTcp(useFileHandle: true); - // EndPointInformation.HandleType = FileHandleType.Tcp; - // return handle; - // } - // catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP) - // { - // Log.LogDebug(0, exception, "Listener.ListenHandle"); - // } - - // handle = ListenPipe(useFileHandle: true); - // EndPointInformation.HandleType = FileHandleType.Pipe; - // return handle; - //} + private UvStreamHandle ListenHandle() + { + switch (EndPoint) + { + case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Auto: + break; + case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Tcp: + return ListenTcp(useFileHandle: true); + case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Pipe: + return ListenPipe(useFileHandle: true); + default: + throw new NotSupportedException(); + } + + UvStreamHandle handle; + try + { + handle = ListenTcp(useFileHandle: true); + return handle; + } + catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP) + { + Log.LogDebug(0, exception, "Listener.ListenHandle"); + } + + handle = ListenPipe(useFileHandle: true); + return handle; + } private static void ConnectionCallback(UvStreamHandle stream, int status, UvException error, object state) { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index 43286ed5af0a..ba2f1f5ce125 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Threading.Channels; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal internal class ListenerContext { // REVIEW: This needs to be bounded and we need a strategy for what to do when the queue is full - private Channel _acceptQueue = Channel.CreateBounded(new BoundedChannelOptions(512) + private readonly Channel _acceptQueue = Channel.CreateBounded(new BoundedChannelOptions(512) { // REVIEW: Not sure if this is right as nothing is stopping the libuv callback today FullMode = BoundedChannelFullMode.Wait @@ -43,12 +44,12 @@ protected UvStreamHandle CreateAcceptSocket() { switch (EndPoint) { - case IPEndPoint ip: + case IPEndPoint _: return AcceptTcp(); - case UnixDomainSocketEndPoint domainSocket: + case UnixDomainSocketEndPoint _: return AcceptPipe(); - //case ListenType.FileHandle: - // return AcceptHandle(); + case FileHandleEndPoint _: + return AcceptHandle(); default: throw new InvalidOperationException(); } @@ -94,7 +95,7 @@ private UvTcpHandle AcceptTcp() try { socket.Init(Thread.Loop, Thread.QueueCloseHandle); - // socket.NoDelay(EndPointInformation.NoDelay); + socket.NoDelay(TransportContext.Options.NoDelay); } catch { @@ -127,20 +128,19 @@ protected void StopAcceptingConnections() _acceptQueue.Writer.Complete(); } - // TODO: We need a new custom endpoint - //private UvStreamHandle AcceptHandle() - //{ - // switch (EndPointInformation.HandleType) - // { - // case FileHandleType.Auto: - // throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first."); - // case FileHandleType.Tcp: - // return AcceptTcp(); - // case FileHandleType.Pipe: - // return AcceptPipe(); - // default: - // throw new NotSupportedException(); - // } - //} + private UvStreamHandle AcceptHandle() + { + switch (EndPoint) + { + case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Auto: + throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first."); + case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Tcp: + return AcceptTcp(); + case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Pipe: + return AcceptPipe(); + default: + throw new NotSupportedException(); + } + } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index a040dea87b79..02946e023611 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -20,6 +20,8 @@ public class LibuvTransportOptions /// public int ThreadCount { get; set; } = ProcessorThreadCount; + public bool NoDelay { get; set; } + internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); private static int ProcessorThreadCount diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 3994826f3a2c..df810ee0e566 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -21,14 +21,14 @@ internal sealed class SocketConnectionListener : IConnectionListener private readonly ISocketsTrace _trace; private Socket _listenSocket; private int _schedulerIndex; + private readonly SocketTransportOptions _options; public EndPoint Endpoint { get; private set; } internal SocketConnectionListener( EndPoint endpoint, - int ioQueueCount, - ISocketsTrace trace, - MemoryPool memoryPool) + SocketTransportOptions options, + ISocketsTrace trace) { Debug.Assert(endpoint != null); Debug.Assert(endpoint is IPEndPoint); @@ -36,7 +36,9 @@ internal SocketConnectionListener( Endpoint = endpoint; _trace = trace; - _memoryPool = memoryPool; + _options = options; + _memoryPool = _options.MemoryPoolFactory(); + var ioQueueCount = options.IOQueueCount; if (ioQueueCount > 0) { @@ -63,19 +65,17 @@ internal void Bind() throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } - var ip = (IPEndPoint)Endpoint; - - var listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + var listenSocket = new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 - if (ip.Address == IPAddress.IPv6Any) + if (Endpoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any) { listenSocket.DualMode = true; } try { - listenSocket.Bind(ip); + listenSocket.Bind(Endpoint); } catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) { @@ -94,9 +94,7 @@ public async ValueTask AcceptAsync() try { var acceptSocket = await _listenSocket.AcceptAsync(); - - // REVIEW: This doesn't work anymore, these need to be on SocketOptions - // acceptSocket.NoDelay = _endPointInformation.NoDelay; + acceptSocket.NoDelay = _options.NoDelay; var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs index 90c55e3e0d63..8564c9394104 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs @@ -46,7 +46,7 @@ public SocketTransportFactory( public ValueTask BindAsync(EndPoint endpoint) { - var transport = new SocketConnectionListener(endpoint, _options.IOQueueCount, _trace, _options.MemoryPoolFactory()); + var transport = new SocketConnectionListener(endpoint, _options, _trace); transport.Bind(); return new ValueTask(transport); } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index 2dad91442311..f5bc3e54db9e 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -17,6 +17,8 @@ public class SocketTransportOptions /// public int IOQueueCount { get; set; } = Math.Min(Environment.ProcessorCount, 16); + public bool NoDelay { get; set; } + internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); } } From daec05665b5072cc537a921277719187efcd21b9 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 01:01:10 -0700 Subject: [PATCH 08/78] Endpoint -> EndPoint --- .../src/IConnectionListener.cs | 2 +- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 +- .../src/Internal/LibuvConnectionListener.cs | 12 ++++++------ .../src/SocketConnectionListener.cs | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 481e5193ad09..0295a84829f3 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Connections { public interface IConnectionListener { - EndPoint Endpoint { get; } + EndPoint EndPoint { get; } ValueTask AcceptAsync(); diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 9cc9cba38276..2987f866534a 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -154,7 +154,7 @@ async Task OnBind(ListenOptions options) var transport = await _transportFactory.BindAsync(options.Endpoint).ConfigureAwait(false); // Update the endpoint - options.Endpoint = transport.Endpoint; + options.Endpoint = transport.EndPoint; _transports.Add(transport); connectionDispatcher.StartAcceptingConnections(transport); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 6fc31713b4c7..d0275ae634c7 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -28,7 +28,7 @@ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, Libuv = uv; TransportContext = context; - Endpoint = endPoint; + EndPoint = endPoint; } public LibuvFunctions Libuv { get; } @@ -39,7 +39,7 @@ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, public ILibuvTrace Log => TransportContext.Log; public LibuvTransportOptions TransportOptions => TransportContext.Options; - public EndPoint Endpoint { get; set; } + public EndPoint EndPoint { get; set; } public async Task StopAsync() { @@ -89,12 +89,12 @@ public async Task BindAsync() { var listener = new Listener(TransportContext); _listeners.Add(listener); - await listener.StartAsync(Endpoint, Threads[0]).ConfigureAwait(false); + await listener.StartAsync(EndPoint, Threads[0]).ConfigureAwait(false); if (listener.ListenSocket is UvTcpHandle handle) { // If requested port was "0", replace with assigned dynamic port. - Endpoint = handle.GetSockIPEndPoint(); + EndPoint = handle.GetSockIPEndPoint(); } } else @@ -104,13 +104,13 @@ public async Task BindAsync() var listenerPrimary = new ListenerPrimary(TransportContext); _listeners.Add(listenerPrimary); - await listenerPrimary.StartAsync(pipeName, pipeMessage, Endpoint, Threads[0]).ConfigureAwait(false); + await listenerPrimary.StartAsync(pipeName, pipeMessage, EndPoint, Threads[0]).ConfigureAwait(false); foreach (var thread in Threads.Skip(1)) { var listenerSecondary = new ListenerSecondary(TransportContext); _listeners.Add(listenerSecondary); - await listenerSecondary.StartAsync(pipeName, pipeMessage, Endpoint, thread).ConfigureAwait(false); + await listenerSecondary.StartAsync(pipeName, pipeMessage, EndPoint, thread).ConfigureAwait(false); } } _acceptEnumerator = AcceptConnections(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index df810ee0e566..84f425791323 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -23,7 +23,7 @@ internal sealed class SocketConnectionListener : IConnectionListener private int _schedulerIndex; private readonly SocketTransportOptions _options; - public EndPoint Endpoint { get; private set; } + public EndPoint EndPoint { get; private set; } internal SocketConnectionListener( EndPoint endpoint, @@ -34,7 +34,7 @@ internal SocketConnectionListener( Debug.Assert(endpoint is IPEndPoint); Debug.Assert(trace != null); - Endpoint = endpoint; + EndPoint = endpoint; _trace = trace; _options = options; _memoryPool = _options.MemoryPoolFactory(); @@ -65,24 +65,24 @@ internal void Bind() throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } - var listenSocket = new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + var listenSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 - if (Endpoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any) + if (EndPoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any) { listenSocket.DualMode = true; } try { - listenSocket.Bind(Endpoint); + listenSocket.Bind(EndPoint); } catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) { throw new AddressInUseException(e.Message, e); } - Endpoint = listenSocket.LocalEndPoint; + EndPoint = listenSocket.LocalEndPoint; listenSocket.Listen(512); From a2281c2ce0b257a1b932770d78d73083c58cd860 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 01:14:23 -0700 Subject: [PATCH 09/78] Generate the connection id in kestrel --- .../Core/src/Internal/ConnectionDispatcher.cs | 39 ++----------------- .../src/Internal/SocketConnection.cs | 5 +-- 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 1f20527b6137..4ef0453c036a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -52,6 +52,9 @@ async Task AcceptConnectionsAsync() break; } + // Set a connection id if the transport didn't set one + connection.ConnectionId ??= CorrelationIdGenerator.GetNextId(); + // TODO: We need a bit of command and control to do connection management and that requires // that we have access to a couple of methods that only exists on TransportConnection // (specifically RequestClose, TickHeartbeat and CompleteAsync. This dependency needs to be removed or @@ -144,41 +147,5 @@ private static Task CancellationTokenAsTask(CancellationToken token) token.Register(state => ((TaskCompletionSource)state).SetResult(null), tcs); return tcs.Task; } - - // Internal for testing - internal static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler writerScheduler) => new PipeOptions - ( - pool: memoryPool, - readerScheduler: serviceContext.Scheduler, - writerScheduler: writerScheduler, - pauseWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, - resumeWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize - ); - - internal static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler readerScheduler) => new PipeOptions - ( - pool: memoryPool, - readerScheduler: readerScheduler, - writerScheduler: serviceContext.Scheduler, - pauseWriterThreshold: GetOutputResponseBufferSize(serviceContext), - resumeWriterThreshold: GetOutputResponseBufferSize(serviceContext), - useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize - ); - - private static long GetOutputResponseBufferSize(ServiceContext serviceContext) - { - var bufferSize = serviceContext.ServerOptions.Limits.MaxResponseBufferSize; - if (bufferSize == 0) - { - // 0 = no buffering so we need to configure the pipe so the writer waits on the reader directly - return 1; - } - - // null means that we have no back pressure - return bufferSize ?? 0; - } } } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index ed0695b8035d..dec491b0159f 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -68,13 +68,12 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); - var inputOptions = new PipeOptions(MemoryPool, awaiterScheduler, awaiterScheduler, useSynchronizationContext: false); - var outputOptions = new PipeOptions(MemoryPool, awaiterScheduler, awaiterScheduler, useSynchronizationContext: false); + var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, awaiterScheduler, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, awaiterScheduler, PipeScheduler.ThreadPool, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); // Set the transport and connection id - // connection.ConnectionId = CorrelationIdGenerator.GetNextId(); Transport = pair.Transport; Application = pair.Application; } From c471ce01724e3564a055e5632cd57505370d56cf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 21:58:38 -0700 Subject: [PATCH 10/78] Implement shutdown in the socket transport --- .../src/IConnectionListener.cs | 3 + src/Servers/Kestrel/Core/src/KestrelServer.cs | 12 +- .../src/Internal/LibuvConnectionListener.cs | 9 +- .../src/Internal/SocketConnection.cs | 2 +- .../src/SocketConnectionListener.cs | 133 +++++++++++++++++- 5 files changed, 142 insertions(+), 17 deletions(-) diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 0295a84829f3..d94404d168a4 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Connections @@ -12,6 +13,8 @@ public interface IConnectionListener ValueTask AcceptAsync(); + ValueTask StopAsync(CancellationToken cancellationToken = default); + ValueTask DisposeAsync(); } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 2987f866534a..16b797896d51 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -184,12 +184,12 @@ public async Task StopAsync(CancellationToken cancellationToken) var tasks = new Task[_transports.Count]; for (int i = 0; i < _transports.Count; i++) { - tasks[i] = _transports[i].DisposeAsync().AsTask(); + tasks[i] = _transports[i].StopAsync(cancellationToken).AsTask(); } await Task.WhenAll(tasks).ConfigureAwait(false); - if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) + /*if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) { Trace.NotAllConnectionsClosedGracefully(); @@ -197,13 +197,7 @@ public async Task StopAsync(CancellationToken cancellationToken) { Trace.NotAllConnectionsAborted(); } - } - - //for (int i = 0; i < _transports.Count; i++) - //{ - // tasks[i] = _transports[i].StopAsync(); - //} - //await Task.WhenAll(tasks).ConfigureAwait(false); + }*/ ServiceContext.Heartbeat?.Dispose(); } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index d0275ae634c7..eed64a8537e4 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; @@ -182,7 +184,7 @@ public async ValueTask AcceptAsync() return null; } - public async ValueTask DisposeAsync() + public async ValueTask StopAsync(CancellationToken cancellationToken) { await UnbindAsync().ConfigureAwait(false); @@ -191,5 +193,10 @@ public async ValueTask DisposeAsync() await _acceptEnumerator.DisposeAsync(); } } + + public ValueTask DisposeAsync() + { + return StopAsync(default); + } } } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index dec491b0159f..9ec489c3eb72 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal { - internal sealed class SocketConnection : TransportConnection + internal class SocketConnection : TransportConnection { private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 84f425791323..5ed031c33756 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -3,10 +3,13 @@ using System; using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Net; using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; @@ -22,6 +25,10 @@ internal sealed class SocketConnectionListener : IConnectionListener private Socket _listenSocket; private int _schedulerIndex; private readonly SocketTransportOptions _options; + private TaskCompletionSource _connectionsDrainedTcs; + private int _pendingConnections; + private int _pendingAccepts; + private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); public EndPoint EndPoint { get; private set; } @@ -91,12 +98,17 @@ internal void Bind() public async ValueTask AcceptAsync() { + Interlocked.Increment(ref _pendingAccepts); + try { var acceptSocket = await _listenSocket.AcceptAsync(); acceptSocket.NoDelay = _options.NoDelay; - var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); + int id = Interlocked.Increment(ref _pendingConnections); + + var connection = new TrackedSocketConnection(this, id, acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); + _connections.TryAdd(id, connection); connection.Start(); @@ -108,16 +120,125 @@ public async ValueTask AcceptAsync() { return null; } + finally + { + Interlocked.Decrement(ref _pendingAccepts); + } } - public ValueTask DisposeAsync() + public async ValueTask StopAsync(CancellationToken cancellationToken) { - _listenSocket?.Dispose(); - _listenSocket = null; + var listenSocket = Interlocked.Exchange(ref _listenSocket, null); + + if (listenSocket != null) + { + // Unbind the listen socket, so no new connections can come in + listenSocket.Dispose(); + + // Wait for all pending accepts to drain + var spin = new SpinWait(); + while (_pendingAccepts > 0) + { + spin.SpinOnce(); + } + + // No more pending accepts by the time we get here, if there any any pending connections, we create a new TCS and wait for them to + // drain. + if (_pendingConnections > 0) + { + _connectionsDrainedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + // Try to do graceful close + foreach (var pair in _connections) + { + pair.Value.RequestClose(); + } + + if (!_connectionsDrainedTcs.Task.IsCompletedSuccessfully) + { + // Wait for connections to drain or for the token to fire + var task = CancellationTokenAsTask(cancellationToken); + var result = await Task.WhenAny(_connectionsDrainedTcs.Task, task); + + if (result != _connectionsDrainedTcs.Task) + { + // If the connections don't shutdown then we need to abort them + foreach (var pair in _connections) + { + pair.Value.Abort(); + } + } + } + + // REVIEW: Should we try to wait again? + // await _connectionsDrainedTcs.Task; + } + } + + // Dispose the memory pool _memoryPool.Dispose(); - // TODO: Wait for all connections to drain (fixed timeout?) - return default; } + private void OnConnectionDisposed(int id) + { + // Disconnect called, wait for _gracefulShutdownTcs to be assigned + if (_listenSocket == null && _connectionsDrainedTcs == null) + { + var spin = new SpinWait(); + while (_connectionsDrainedTcs == null) + { + spin.SpinOnce(); + } + } + + // If a call to dispose is currently running then we need to wait until the _gracefulShutdownTcs + // has been assigned + var connections = Interlocked.Decrement(ref _pendingConnections); + + _connections.TryRemove(id, out _); + + if (_connectionsDrainedTcs != null && connections == 0) + { + _connectionsDrainedTcs.TrySetResult(null); + } + } + + private static Task CancellationTokenAsTask(CancellationToken token) + { + if (token.IsCancellationRequested) + { + return Task.CompletedTask; + } + + // Transports already dispatch prior to tripping ConnectionClosed + // since application code can register to this token. + var tcs = new TaskCompletionSource(); + token.Register(state => ((TaskCompletionSource)state).SetResult(null), tcs); + return tcs.Task; + } + + public ValueTask DisposeAsync() + { + return StopAsync(new CancellationTokenSource(5000).Token); + } + + private class TrackedSocketConnection : SocketConnection + { + private readonly SocketConnectionListener _listener; + private readonly int _id; + + internal TrackedSocketConnection(SocketConnectionListener listener, int id, Socket socket, MemoryPool memoryPool, PipeScheduler scheduler, ISocketsTrace trace) : base(socket, memoryPool, scheduler, trace) + { + _listener = listener; + _id = id; + } + + public override async ValueTask DisposeAsync() + { + await base.DisposeAsync(); + + _listener.OnConnectionDisposed(_id); + } + } } } From 3f864d4068fc10d693d3c3df0f3cfff87464b70e Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 22:34:25 -0700 Subject: [PATCH 11/78] Progress towards removing the task --- .../src/ConnectionContext.cs | 3 ++ .../src/DefaultConnectionContext.cs | 2 +- .../Core/src/Internal/ConnectionDispatcher.cs | 14 ++--- .../ConnectionManagerShutdownExtensions.cs | 53 ------------------- .../Infrastructure/HeartbeatManager.cs | 4 +- .../Infrastructure/KestrelConnection.cs | 24 +++++---- .../Infrastructure/KestrelEventSource.cs | 11 ++-- src/Servers/Kestrel/Core/src/KestrelServer.cs | 10 ---- .../src/Internal/TransportConnection.cs | 2 +- .../src/Internal/LibuvConnection.cs | 19 +++++-- .../src/Internal/ListenerContext.cs | 2 +- .../src/Internal/SocketConnection.cs | 12 ++--- 12 files changed, 50 insertions(+), 106 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs diff --git a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs index 75d4b2714636..26d61492bcfd 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Net; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; @@ -20,6 +21,8 @@ public abstract class ConnectionContext public abstract IDuplexPipe Transport { get; set; } + public abstract CancellationToken ConnectionClosed { get; set; } + public abstract EndPoint LocalEndpoint { get; set; } public abstract EndPoint RemoteEndpoint { get; set; } diff --git a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs index e2c7c70e82d0..09208bc75e59 100644 --- a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs @@ -65,7 +65,7 @@ public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe ap public override IDuplexPipe Transport { get; set; } - public CancellationToken ConnectionClosed { get; set; } + public override CancellationToken ConnectionClosed { get; set; } public override EndPoint LocalEndpoint { get; set; } public override EndPoint RemoteEndpoint { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 4ef0453c036a..23efc818f2ca 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -60,7 +60,7 @@ async Task AcceptConnectionsAsync() // (specifically RequestClose, TickHeartbeat and CompleteAsync. This dependency needs to be removed or // the return value of AcceptAsync needs to change. - _ = OnConnection((TransportConnection)connection); + _ = OnConnection(connection); } } catch (Exception) @@ -70,7 +70,7 @@ async Task AcceptConnectionsAsync() } } - private async Task OnConnection(TransportConnection connection) + private async Task OnConnection(ConnectionContext connection) { await using (connection) { @@ -100,12 +100,6 @@ private async Task Execute(KestrelConnection connection) { Log.LogCritical(0, ex, $"{nameof(ConnectionDispatcher)}.{nameof(Execute)}() {connectionContext.ConnectionId}"); } - finally - { - // Complete the transport PipeReader and PipeWriter after calling into application code - connectionContext.Transport.Input.Complete(); - connectionContext.Transport.Output.Complete(); - } // Wait for the transport to close await CancellationTokenAsTask(connectionContext.ConnectionClosed); @@ -113,13 +107,11 @@ private async Task Execute(KestrelConnection connection) } finally { - await connectionContext.CompleteAsync(); + await connection.CompleteAsync(); Log.ConnectionStop(connectionContext.ConnectionId); KestrelEventSource.Log.ConnectionStop(connectionContext); - connection.Complete(); - _serviceContext.ConnectionManager.RemoveConnection(id); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs deleted file mode 100644 index 5877cbcf5572..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs +++ /dev/null @@ -1,53 +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.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal static class ConnectionManagerShutdownExtensions - { - public static async Task CloseAllConnectionsAsync(this ConnectionManager connectionManager, CancellationToken token) - { - var closeTasks = new List(); - - connectionManager.Walk(connection => - { - connection.TransportConnection.RequestClose(); - closeTasks.Add(connection.ExecutionTask); - }); - - var allClosedTask = Task.WhenAll(closeTasks.ToArray()); - return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; - } - - public static async Task AbortAllConnectionsAsync(this ConnectionManager connectionManager) - { - var abortTasks = new List(); - - connectionManager.Walk(connection => - { - connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); - abortTasks.Add(connection.ExecutionTask); - }); - - var allAbortedTask = Task.WhenAll(abortTasks.ToArray()); - return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; - } - - private static Task CancellationTokenAsTask(CancellationToken token) - { - if (token.IsCancellationRequested) - { - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - token.Register(() => tcs.SetResult(null)); - return tcs.Task; - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs index 8fcd25fe9830..18635e1f27b0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -35,7 +35,7 @@ public void OnHeartbeat(DateTimeOffset now) private void WalkCallback(KestrelConnection connection) { - connection.TransportConnection.TickHeartbeat(); + connection.TickHeartbeat(); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index a253aa5c328d..1e7fb44e967c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -1,25 +1,31 @@ // 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.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Connections; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { internal class KestrelConnection { - private TaskCompletionSource _executionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - public KestrelConnection(TransportConnection transportConnection) + public KestrelConnection(ConnectionContext connectionContext) { - TransportConnection = transportConnection; - ExecutionTask = _executionTcs.Task; + TransportConnection = connectionContext; } - public TransportConnection TransportConnection { get; } + public ConnectionContext TransportConnection { get; set; } - public Task ExecutionTask { get; } + public Task CompleteAsync() + { + // TODO + return Task.CompletedTask; + } - internal void Complete() => _executionTcs.TrySetResult(null); + public void TickHeartbeat() + { + + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs index ae6a03915b9c..1cbcdf50a4a6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs @@ -2,10 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics.Tracing; -using System.Net; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { @@ -27,15 +26,15 @@ private KestrelEventSource() // - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object. [NonEvent] - public void ConnectionStart(TransportConnection connection) + public void ConnectionStart(ConnectionContext connection) { // avoid allocating strings unless this event source is enabled if (IsEnabled()) { ConnectionStart( connection.ConnectionId, - connection.LocalAddress != null ? new IPEndPoint(connection.LocalAddress, connection.LocalPort).ToString() : null, - connection.RemoteAddress != null ? new IPEndPoint(connection.RemoteAddress, connection.RemotePort).ToString() : null); + connection.LocalEndpoint?.ToString(), + connection.RemoteEndpoint?.ToString()); } } @@ -54,7 +53,7 @@ private void ConnectionStart(string connectionId, } [NonEvent] - public void ConnectionStop(TransportConnection connection) + public void ConnectionStop(ConnectionContext connection) { if (IsEnabled()) { diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 16b797896d51..fef11a959694 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -189,16 +189,6 @@ public async Task StopAsync(CancellationToken cancellationToken) await Task.WhenAll(tasks).ConfigureAwait(false); - /*if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) - { - Trace.NotAllConnectionsClosedGracefully(); - - if (!await ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false)) - { - Trace.NotAllConnectionsAborted(); - } - }*/ - ServiceContext.Heartbeat?.Dispose(); } catch (Exception ex) diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs index a684a96357b7..1671d4e6bf71 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs @@ -64,7 +64,7 @@ public override IDictionary Items public PipeWriter Input => Application.Output; public PipeReader Output => Application.Input; - public CancellationToken ConnectionClosed { get; set; } + public override CancellationToken ConnectionClosed { get; set; } public CancellationToken ConnectionClosedRequested { get; set; } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index e6b1298401ba..43068d4add26 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -31,6 +31,7 @@ internal partial class LibuvConnection : TransportConnection private volatile ConnectionAbortedException _abortReason; private MemoryHandle _bufferHandle; + private Task _task; public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread thread, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint) { @@ -68,7 +69,12 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa public override PipeScheduler InputWriterScheduler => Thread; public override PipeScheduler OutputReaderScheduler => Thread; - public async Task Start() + public void Start() + { + _task = StartCore(); + } + + private async Task StartCore() { try { @@ -144,11 +150,18 @@ public override void Abort(ConnectionAbortedException abortReason) Thread.Post(s => s.Dispose(), _socket); } - public override ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { + if (_task != null) + { + await _task; + } + _connectionClosedTokenSource.Dispose(); _connectionClosingCts.Dispose(); - return base.DisposeAsync(); + + Transport.Input.Complete(); + Transport.Output.Complete(); } // Called on Libuv thread diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index ba2f1f5ce125..eef843dc711b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -78,7 +78,7 @@ protected async Task HandleConnectionAsync(UvStreamHandle socket) } var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint); - _ = connection.Start(); + connection.Start(); await _acceptQueue.Writer.WriteAsync(connection); } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 9ec489c3eb72..5e0f19e8bdf5 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -125,18 +125,12 @@ public override async ValueTask DisposeAsync() { if (_task != null) { - // TODO: Make this timeout configurable, this gives the task time to flush the data to the socket - // before timing out - var result = await Task.WhenAny(_task, Task.Delay(5000)); - - if (result != _task) - { - Abort(); - } - await _task; } + Transport.Input.Complete(); + Transport.Output.Complete(); + _connectionClosedTokenSource.Dispose(); _connectionClosingCts.Dispose(); } From 60efcbde2182a70d26a987b7057d3821a3d987ab Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 22:53:46 -0700 Subject: [PATCH 12/78] Remove the KestrelConnection cast - Moved feature implementations into KestrelConnection out of TransportConnection --- .../Core/src/Internal/ConnectionDispatcher.cs | 5 +- .../Infrastructure/KestrelConnection.cs | 120 +++++++++++++++++- .../TransportConnection.FeatureCollection.cs | 89 +------------ .../Internal/TransportConnection.Generated.cs | 46 ------- .../src/Internal/TransportConnection.cs | 31 ----- .../TransportConnectionFeatureCollection.cs | 2 - 6 files changed, 116 insertions(+), 177 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 23efc818f2ca..d1438c09691b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -2,13 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Buffers; -using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal @@ -74,7 +71,7 @@ private async Task OnConnection(ConnectionContext connection) { await using (connection) { - await Execute(new KestrelConnection(connection)); + await Execute(new KestrelConnection(connection, _serviceContext.Log)); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index 1e7fb44e967c..802364f7145c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -2,30 +2,138 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - internal class KestrelConnection + internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature { - public KestrelConnection(ConnectionContext connectionContext) + private List<(Action handler, object state)> _heartbeatHandlers; + private readonly object _heartbeatLock = new object(); + + private Stack, object>> _onCompleted; + private bool _completed; + + public KestrelConnection(ConnectionContext connectionContext, ILogger logger) { + Logger = logger; TransportConnection = connectionContext; + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); } + private ILogger Logger { get; } + public ConnectionContext TransportConnection { get; set; } + public void TickHeartbeat() + { + lock (_heartbeatLock) + { + if (_heartbeatHandlers == null) + { + return; + } + + foreach (var (handler, state) in _heartbeatHandlers) + { + handler(state); + } + } + } + + public void OnHeartbeat(Action action, object state) + { + lock (_heartbeatLock) + { + if (_heartbeatHandlers == null) + { + _heartbeatHandlers = new List<(Action handler, object state)>(); + } + + _heartbeatHandlers.Add((action, state)); + } + } + + void IConnectionCompleteFeature.OnCompleted(Func callback, object state) + { + if (_completed) + { + throw new InvalidOperationException("The connection is already complete."); + } + + if (_onCompleted == null) + { + _onCompleted = new Stack, object>>(); + } + _onCompleted.Push(new KeyValuePair, object>(callback, state)); + } + public Task CompleteAsync() { - // TODO + if (_completed) + { + throw new InvalidOperationException("The connection is already complete."); + } + + _completed = true; + var onCompleted = _onCompleted; + + if (onCompleted == null || onCompleted.Count == 0) + { + return Task.CompletedTask; + } + + return CompleteAsyncMayAwait(onCompleted); + } + + private Task CompleteAsyncMayAwait(Stack, object>> onCompleted) + { + while (onCompleted.TryPop(out var entry)) + { + try + { + var task = entry.Key.Invoke(entry.Value); + if (!ReferenceEquals(task, Task.CompletedTask)) + { + return CompleteAsyncAwaited(task, onCompleted); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); + } + } + return Task.CompletedTask; } - public void TickHeartbeat() + private async Task CompleteAsyncAwaited(Task currentTask, Stack, object>> onCompleted) { - + try + { + await currentTask; + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); + } + + while (onCompleted.TryPop(out var entry)) + { + try + { + await entry.Key.Invoke(entry.Value); + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); + } + } } } } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs index 4165460c11c1..fceb73849957 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs @@ -21,17 +21,12 @@ public partial class TransportConnection : IHttpConnectionFeature, IConnectionItemsFeature, IMemoryPoolFeature, IConnectionLifetimeFeature, - IConnectionHeartbeatFeature, - IConnectionLifetimeNotificationFeature, - IConnectionCompleteFeature + IConnectionLifetimeNotificationFeature { // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, // then the list of `features` in the generated code project MUST also be updated. // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs - private Stack, object>> _onCompleted; - private bool _completed; - string IHttpConnectionFeature.ConnectionId { get => ConnectionId; @@ -91,87 +86,5 @@ CancellationToken IConnectionLifetimeNotificationFeature.ConnectionClosedRequest void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); void IConnectionLifetimeNotificationFeature.RequestClose() => RequestClose(); - - void IConnectionHeartbeatFeature.OnHeartbeat(System.Action action, object state) - { - OnHeartbeat(action, state); - } - - void IConnectionCompleteFeature.OnCompleted(Func callback, object state) - { - if (_completed) - { - throw new InvalidOperationException("The connection is already complete."); - } - - if (_onCompleted == null) - { - _onCompleted = new Stack, object>>(); - } - _onCompleted.Push(new KeyValuePair, object>(callback, state)); - } - - public Task CompleteAsync() - { - if (_completed) - { - throw new InvalidOperationException("The connection is already complete."); - } - - _completed = true; - var onCompleted = _onCompleted; - - if (onCompleted == null || onCompleted.Count == 0) - { - return Task.CompletedTask; - } - - return CompleteAsyncMayAwait(onCompleted); - } - - private Task CompleteAsyncMayAwait(Stack, object>> onCompleted) - { - while (onCompleted.TryPop(out var entry)) - { - try - { - var task = entry.Key.Invoke(entry.Value); - if (!ReferenceEquals(task, Task.CompletedTask)) - { - return CompleteAsyncAwaited(task, onCompleted); - } - } - catch (Exception ex) - { - Logger?.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); - } - } - - return Task.CompletedTask; - } - - private async Task CompleteAsyncAwaited(Task currentTask, Stack, object>> onCompleted) - { - try - { - await currentTask; - } - catch (Exception ex) - { - Logger?.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); - } - - while (onCompleted.TryPop(out var entry)) - { - try - { - await entry.Key.Invoke(entry.Value); - } - catch (Exception ex) - { - Logger?.LogError(ex, "An error occured running an IConnectionCompleteFeature.OnCompleted callback."); - } - } - } } } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs index b43bde362d61..0f03677ee7c7 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs @@ -18,9 +18,7 @@ public partial class TransportConnection : IFeatureCollection private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); - private static readonly Type IConnectionHeartbeatFeatureType = typeof(IConnectionHeartbeatFeature); private static readonly Type IConnectionLifetimeNotificationFeatureType = typeof(IConnectionLifetimeNotificationFeature); - private static readonly Type IConnectionCompleteFeatureType = typeof(IConnectionCompleteFeature); private object _currentIHttpConnectionFeature; private object _currentIConnectionIdFeature; @@ -28,9 +26,7 @@ public partial class TransportConnection : IFeatureCollection private object _currentIConnectionItemsFeature; private object _currentIMemoryPoolFeature; private object _currentIConnectionLifetimeFeature; - private object _currentIConnectionHeartbeatFeature; private object _currentIConnectionLifetimeNotificationFeature; - private object _currentIConnectionCompleteFeature; private int _featureRevision; @@ -44,9 +40,7 @@ private void FastReset() _currentIConnectionItemsFeature = this; _currentIMemoryPoolFeature = this; _currentIConnectionLifetimeFeature = this; - _currentIConnectionHeartbeatFeature = this; _currentIConnectionLifetimeNotificationFeature = this; - _currentIConnectionCompleteFeature = this; } @@ -126,18 +120,10 @@ object IFeatureCollection.this[Type key] { feature = _currentIConnectionLifetimeFeature; } - else if (key == IConnectionHeartbeatFeatureType) - { - feature = _currentIConnectionHeartbeatFeature; - } else if (key == IConnectionLifetimeNotificationFeatureType) { feature = _currentIConnectionLifetimeNotificationFeature; } - else if (key == IConnectionCompleteFeatureType) - { - feature = _currentIConnectionCompleteFeature; - } else if (MaybeExtra != null) { feature = ExtraFeatureGet(key); @@ -174,18 +160,10 @@ object IFeatureCollection.this[Type key] { _currentIConnectionLifetimeFeature = value; } - else if (key == IConnectionHeartbeatFeatureType) - { - _currentIConnectionHeartbeatFeature = value; - } else if (key == IConnectionLifetimeNotificationFeatureType) { _currentIConnectionLifetimeNotificationFeature = value; } - else if (key == IConnectionCompleteFeatureType) - { - _currentIConnectionCompleteFeature = value; - } else { ExtraFeatureSet(key, value); @@ -220,18 +198,10 @@ TFeature IFeatureCollection.Get() { feature = (TFeature)_currentIConnectionLifetimeFeature; } - else if (typeof(TFeature) == typeof(IConnectionHeartbeatFeature)) - { - feature = (TFeature)_currentIConnectionHeartbeatFeature; - } else if (typeof(TFeature) == typeof(IConnectionLifetimeNotificationFeature)) { feature = (TFeature)_currentIConnectionLifetimeNotificationFeature; } - else if (typeof(TFeature) == typeof(IConnectionCompleteFeature)) - { - feature = (TFeature)_currentIConnectionCompleteFeature; - } else if (MaybeExtra != null) { feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); @@ -267,18 +237,10 @@ void IFeatureCollection.Set(TFeature feature) { _currentIConnectionLifetimeFeature = feature; } - else if (typeof(TFeature) == typeof(IConnectionHeartbeatFeature)) - { - _currentIConnectionHeartbeatFeature = feature; - } else if (typeof(TFeature) == typeof(IConnectionLifetimeNotificationFeature)) { _currentIConnectionLifetimeNotificationFeature = feature; } - else if (typeof(TFeature) == typeof(IConnectionCompleteFeature)) - { - _currentIConnectionCompleteFeature = feature; - } else { ExtraFeatureSet(typeof(TFeature), feature); @@ -311,18 +273,10 @@ private IEnumerable> FastEnumerable() { yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); } - if (_currentIConnectionHeartbeatFeature != null) - { - yield return new KeyValuePair(IConnectionHeartbeatFeatureType, _currentIConnectionHeartbeatFeature); - } if (_currentIConnectionLifetimeNotificationFeature != null) { yield return new KeyValuePair(IConnectionLifetimeNotificationFeatureType, _currentIConnectionLifetimeNotificationFeature); } - if (_currentIConnectionCompleteFeature != null) - { - yield return new KeyValuePair(IConnectionCompleteFeatureType, _currentIConnectionCompleteFeature); - } if (MaybeExtra != null) { diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs index 1671d4e6bf71..c1bc8968ff7e 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs @@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal public abstract partial class TransportConnection : ConnectionContext { private IDictionary _items; - private List<(Action handler, object state)> _heartbeatHandlers; - private readonly object _heartbeatLock = new object(); protected readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); public TransportConnection() @@ -68,35 +66,6 @@ public override IDictionary Items public CancellationToken ConnectionClosedRequested { get; set; } - public void TickHeartbeat() - { - lock (_heartbeatLock) - { - if (_heartbeatHandlers == null) - { - return; - } - - foreach (var (handler, state) in _heartbeatHandlers) - { - handler(state); - } - } - } - - public void OnHeartbeat(Action action, object state) - { - lock (_heartbeatLock) - { - if (_heartbeatHandlers == null) - { - _heartbeatHandlers = new List<(Action handler, object state)>(); - } - - _heartbeatHandlers.Add((action, state)); - } - } - // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause // any TransportConnection that does not override Abort or calls base.Abort // to stack overflow when IConnectionLifetimeFeature.Abort() is called. diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index cd332f16af66..15d98dcf04af 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -17,9 +17,7 @@ public static string GenerateFile() "IConnectionItemsFeature", "IMemoryPoolFeature", "IConnectionLifetimeFeature", - "IConnectionHeartbeatFeature", "IConnectionLifetimeNotificationFeature", - "IConnectionCompleteFeature" }; var usings = $@" From 17fa5fcfb06369ba9259a9e4e15d847940fafac2 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 23:12:21 -0700 Subject: [PATCH 13/78] Remove IHttpConnectionFeature --- .../src/ConnectionContext.cs | 5 ++- .../src/DefaultConnectionContext.cs | 4 +- .../Features/IConnectionEndpointFeature.cs | 4 +- .../src/Internal/HttpConnectionMiddleware.cs | 16 +------- .../Infrastructure/KestrelEventSource.cs | 4 +- .../TransportConnection.FeatureCollection.cs | 38 +------------------ .../Internal/TransportConnection.Generated.cs | 31 ++------------- .../src/Internal/TransportConnection.cs | 13 ++----- .../src/Internal/LibuvConnection.cs | 13 +------ .../src/Internal/SocketConnection.cs | 15 +------- .../TransportConnectionFeatureCollection.cs | 1 - 11 files changed, 23 insertions(+), 121 deletions(-) diff --git a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs index 26d61492bcfd..5e2a0ee5339f 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs @@ -23,8 +23,9 @@ public abstract class ConnectionContext public abstract CancellationToken ConnectionClosed { get; set; } - public abstract EndPoint LocalEndpoint { get; set; } - public abstract EndPoint RemoteEndpoint { get; set; } + public abstract EndPoint LocalEndPoint { get; set; } + + public abstract EndPoint RemoteEndPoint { get; set; } public virtual void Abort(ConnectionAbortedException abortReason) { diff --git a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs index 09208bc75e59..d0f2d94f8cc0 100644 --- a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs @@ -66,8 +66,8 @@ public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe ap public override IDuplexPipe Transport { get; set; } public override CancellationToken ConnectionClosed { get; set; } - public override EndPoint LocalEndpoint { get; set; } - public override EndPoint RemoteEndpoint { get; set; } + public override EndPoint LocalEndPoint { get; set; } + public override EndPoint RemoteEndPoint { get; set; } public override void Abort(ConnectionAbortedException abortReason) { diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs index 53c4b0bd4973..4431fc963d0a 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs @@ -4,7 +4,7 @@ namespace Microsoft.AspNetCore.Connections.Features { public interface IConnectionEndPointFeature { - EndPoint LocalEndpoint { get; set; } - EndPoint RemoteEndpoint { get; set; } + EndPoint LocalEndPoint { get; set; } + EndPoint RemoteEndPoint { get; set; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs index 76b2f3c8e0ce..efae39ce71bb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs @@ -47,20 +47,8 @@ public Task OnConnectionAsync(ConnectionContext connectionContext) Transport = connectionContext.Transport }; - var connectionFeature = connectionContext.Features.Get(); - - if (connectionFeature != null) - { - if (connectionFeature.LocalIpAddress != null) - { - httpConnectionContext.LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); - } - - if (connectionFeature.RemoteIpAddress != null) - { - httpConnectionContext.RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); - } - } + httpConnectionContext.LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint; + httpConnectionContext.RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint; var connection = new HttpConnection(httpConnectionContext); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs index 1cbcdf50a4a6..fdabf48247f6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs @@ -33,8 +33,8 @@ public void ConnectionStart(ConnectionContext connection) { ConnectionStart( connection.ConnectionId, - connection.LocalEndpoint?.ToString(), - connection.RemoteEndpoint?.ToString()); + connection.LocalEndPoint?.ToString(), + connection.RemoteEndPoint?.ToString()); } } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs index fceb73849957..7c0d6cc6895b 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs @@ -1,22 +1,16 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Buffers; using System.Collections.Generic; using System.IO.Pipelines; -using System.Net; using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { - public partial class TransportConnection : IHttpConnectionFeature, - IConnectionIdFeature, + public partial class TransportConnection : IConnectionIdFeature, IConnectionTransportFeature, IConnectionItemsFeature, IMemoryPoolFeature, @@ -27,36 +21,6 @@ public partial class TransportConnection : IHttpConnectionFeature, // then the list of `features` in the generated code project MUST also be updated. // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs - string IHttpConnectionFeature.ConnectionId - { - get => ConnectionId; - set => ConnectionId = value; - } - - IPAddress IHttpConnectionFeature.RemoteIpAddress - { - get => RemoteAddress; - set => RemoteAddress = value; - } - - IPAddress IHttpConnectionFeature.LocalIpAddress - { - get => LocalAddress; - set => LocalAddress = value; - } - - int IHttpConnectionFeature.RemotePort - { - get => RemotePort; - set => RemotePort = value; - } - - int IHttpConnectionFeature.LocalPort - { - get => LocalPort; - set => LocalPort = value; - } - MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; IDuplexPipe IConnectionTransportFeature.Transport diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs index 0f03677ee7c7..aba064e1453f 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs @@ -12,7 +12,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { public partial class TransportConnection : IFeatureCollection { - private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); @@ -20,7 +19,6 @@ public partial class TransportConnection : IFeatureCollection private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); private static readonly Type IConnectionLifetimeNotificationFeatureType = typeof(IConnectionLifetimeNotificationFeature); - private object _currentIHttpConnectionFeature; private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; @@ -34,7 +32,6 @@ public partial class TransportConnection : IFeatureCollection private void FastReset() { - _currentIHttpConnectionFeature = this; _currentIConnectionIdFeature = this; _currentIConnectionTransportFeature = this; _currentIConnectionItemsFeature = this; @@ -96,11 +93,7 @@ object IFeatureCollection.this[Type key] get { object feature = null; - if (key == IHttpConnectionFeatureType) - { - feature = _currentIHttpConnectionFeature; - } - else if (key == IConnectionIdFeatureType) + if (key == IConnectionIdFeatureType) { feature = _currentIConnectionIdFeature; } @@ -136,11 +129,7 @@ object IFeatureCollection.this[Type key] { _featureRevision++; - if (key == IHttpConnectionFeatureType) - { - _currentIHttpConnectionFeature = value; - } - else if (key == IConnectionIdFeatureType) + if (key == IConnectionIdFeatureType) { _currentIConnectionIdFeature = value; } @@ -174,11 +163,7 @@ object IFeatureCollection.this[Type key] TFeature IFeatureCollection.Get() { TFeature feature = default; - if (typeof(TFeature) == typeof(IHttpConnectionFeature)) - { - feature = (TFeature)_currentIHttpConnectionFeature; - } - else if (typeof(TFeature) == typeof(IConnectionIdFeature)) + if (typeof(TFeature) == typeof(IConnectionIdFeature)) { feature = (TFeature)_currentIConnectionIdFeature; } @@ -213,11 +198,7 @@ TFeature IFeatureCollection.Get() void IFeatureCollection.Set(TFeature feature) { _featureRevision++; - if (typeof(TFeature) == typeof(IHttpConnectionFeature)) - { - _currentIHttpConnectionFeature = feature; - } - else if (typeof(TFeature) == typeof(IConnectionIdFeature)) + if (typeof(TFeature) == typeof(IConnectionIdFeature)) { _currentIConnectionIdFeature = feature; } @@ -249,10 +230,6 @@ void IFeatureCollection.Set(TFeature feature) private IEnumerable> FastEnumerable() { - if (_currentIHttpConnectionFeature != null) - { - yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature); - } if (_currentIConnectionIdFeature != null) { yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs index c1bc8968ff7e..3c9ad0366d37 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs @@ -25,13 +25,8 @@ public TransportConnection() ConnectionClosedRequested = _connectionClosingCts.Token; } - public IPAddress RemoteAddress { get; set; } - public int RemotePort { get; set; } - public IPAddress LocalAddress { get; set; } - public int LocalPort { get; set; } - - public override EndPoint LocalEndpoint { get; set; } - public override EndPoint RemoteEndpoint { get; set; } + public override EndPoint LocalEndPoint { get; set; } + public override EndPoint RemoteEndPoint { get; set; } public override string ConnectionId { get; set; } @@ -40,9 +35,7 @@ public TransportConnection() protected internal virtual ILogger Logger { get; set; } public virtual MemoryPool MemoryPool { get; } - public virtual PipeScheduler InputWriterScheduler { get; } - public virtual PipeScheduler OutputReaderScheduler { get; } - + public override IDuplexPipe Transport { get; set; } public IDuplexPipe Application { get; set; } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 43068d4add26..306fdd73241f 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -37,14 +37,8 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa { _socket = socket; - RemoteAddress = remoteEndPoint?.Address; - RemotePort = remoteEndPoint?.Port ?? 0; - - LocalAddress = localEndPoint?.Address; - LocalPort = localEndPoint?.Port ?? 0; - - LocalEndpoint = localEndPoint; - RemoteEndpoint = remoteEndPoint; + LocalEndPoint = localEndPoint; + RemoteEndPoint = remoteEndPoint; ConnectionClosed = _connectionClosedTokenSource.Token; Logger = log; @@ -66,9 +60,6 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa private ILibuvTrace Log { get; } private LibuvThread Thread { get; } public override MemoryPool MemoryPool => Thread.MemoryPool; - public override PipeScheduler InputWriterScheduler => Thread; - public override PipeScheduler OutputReaderScheduler => Thread; - public void Start() { _task = StartCore(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 5e0f19e8bdf5..3837cbb22d9b 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -46,17 +46,8 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu Logger = trace; _trace = trace; - var localEndPoint = (IPEndPoint)_socket.LocalEndPoint; - var remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; - - LocalEndpoint = localEndPoint; - RemoteEndpoint = remoteEndPoint; - - LocalAddress = localEndPoint.Address; - LocalPort = localEndPoint.Port; - - RemoteAddress = remoteEndPoint.Address; - RemotePort = remoteEndPoint.Port; + LocalEndPoint = _socket.LocalEndPoint; + RemoteEndPoint = _socket.RemoteEndPoint; ConnectionClosed = _connectionClosedTokenSource.Token; @@ -79,8 +70,6 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu } public override MemoryPool MemoryPool { get; } - public override PipeScheduler InputWriterScheduler => _scheduler; - public override PipeScheduler OutputReaderScheduler => _scheduler; public void Start() { diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index 15d98dcf04af..cda25ed2f68e 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -11,7 +11,6 @@ public static string GenerateFile() // See also: src/Kestrel.Transport.Abstractions/Internal/TransportConnection.FeatureCollection.cs var features = new[] { - "IHttpConnectionFeature", "IConnectionIdFeature", "IConnectionTransportFeature", "IConnectionItemsFeature", From 789ae163c36b9a5497388b83ee2fe5fc330cc2c6 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 23:14:05 -0700 Subject: [PATCH 14/78] Remove comment --- .../Kestrel/Core/src/Internal/ConnectionDispatcher.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index d1438c09691b..87414853352c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -52,11 +52,6 @@ async Task AcceptConnectionsAsync() // Set a connection id if the transport didn't set one connection.ConnectionId ??= CorrelationIdGenerator.GetNextId(); - // TODO: We need a bit of command and control to do connection management and that requires - // that we have access to a couple of methods that only exists on TransportConnection - // (specifically RequestClose, TickHeartbeat and CompleteAsync. This dependency needs to be removed or - // the return value of AcceptAsync needs to change. - _ = OnConnection(connection); } } From edf0557a3a40fc682e56af5a7aa0db8e6789a821 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 18 May 2019 23:42:39 -0700 Subject: [PATCH 15/78] Fix in memory transport --- .../Http2/Http2TestBase.cs | 4 +- .../InMemory.FunctionalTests.csproj | 3 +- .../InMemoryTransportConnection.cs | 7 +-- .../TestTransport/InMemoryTransportFactory.cs | 55 ++++++++++++------- .../TestTransport/TestServer.cs | 32 +---------- 5 files changed, 41 insertions(+), 60 deletions(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 6751565db19c..4c17f3711b99 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -429,8 +429,8 @@ protected void CreateConnection() // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make // it easier to verify request frames are processed correctly immediately after sending the them. - var inputPipeOptions = ConnectionDispatcher.GetInputPipeOptions(_serviceContext, _memoryPool, PipeScheduler.ThreadPool); - var outputPipeOptions = ConnectionDispatcher.GetOutputPipeOptions(_serviceContext, _memoryPool, PipeScheduler.ThreadPool); + var inputPipeOptions = new PipeOptions(_memoryPool, PipeScheduler.ThreadPool); + var outputPipeOptions = new PipeOptions(_memoryPool, PipeScheduler.ThreadPool); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index c7b344c2c8c9..8bbf63c40087 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -14,6 +14,7 @@ + diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index 95c1907c0a72..3b437451921a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -24,17 +24,14 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger) MemoryPool = memoryPool; _logger = logger; - LocalAddress = IPAddress.Loopback; - RemoteAddress = IPAddress.Loopback; + LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 0); ConnectionClosed = _connectionClosedTokenSource.Token; } public override MemoryPool MemoryPool { get; } - public override PipeScheduler InputWriterScheduler => PipeScheduler.ThreadPool; - public override PipeScheduler OutputReaderScheduler => PipeScheduler.ThreadPool; - public ConnectionAbortedException AbortReason { get; private set; } public override void Abort(ConnectionAbortedException abortReason) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs index eeb271a70602..4aba82e4c907 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs @@ -2,43 +2,56 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net; +using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Connections; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport { - internal class InMemoryTransportFactory : ITransportFactory + internal class InMemoryTransportFactory : IConnectionListenerFactory, IConnectionListener { - public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher dispatcher) + private Channel _acceptQueue = Channel.CreateUnbounded(); + + public EndPoint EndPoint { get; } + + public void AddConnection(ConnectionContext connection) + { + _acceptQueue.Writer.TryWrite(connection); + } + + public async ValueTask AcceptAsync() { - if (ConnectionDispatcher != null) + if (await _acceptQueue.Reader.WaitToReadAsync()) { - throw new InvalidOperationException("InMemoryTransportFactory doesn't support creating multiple endpoints"); + while (_acceptQueue.Reader.TryRead(out var item)) + { + return item; + } } - ConnectionDispatcher = dispatcher; + return null; - return new NoopTransport(); } - public IConnectionDispatcher ConnectionDispatcher { get; private set; } + public ValueTask BindAsync(EndPoint endpoint) + { + // The endpoint isn't important + return new ValueTask(this); + } - private class NoopTransport : ITransport + public ValueTask DisposeAsync() { - public Task BindAsync() - { - return Task.CompletedTask; - } + return StopAsync(default); + } - public Task StopAsync() - { - return Task.CompletedTask; - } + public ValueTask StopAsync(CancellationToken cancellationToken = default) + { + _acceptQueue.Writer.TryComplete(); - public Task UnbindAsync() - { - return Task.CompletedTask; - } + // TODO: Graceful shutdown + return default; } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs index 9f3828f07b27..783c769f2c04 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs @@ -103,7 +103,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action(); - token.Register(() => tcs.SetResult(null)); - return tcs.Task; - } - public async ValueTask DisposeAsync() { // The concrete WebHost implements IAsyncDisposable From c1f9f13346aeff56f8c04a85f5707379d7a402d0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 19 May 2019 00:01:01 -0700 Subject: [PATCH 16/78] Make more tests compile --- .../Kestrel/Core/test/KestrelServerTests.cs | 276 +++++++++--------- .../WebHostBuilderKestrelExtensionsTests.cs | 12 +- .../test/TransportTestHelpers/TestServer.cs | 4 +- .../InMemoryTransportConnection.cs | 14 +- 4 files changed, 163 insertions(+), 143 deletions(-) diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 180c7e9bf8ca..9604ba1098da 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -203,7 +204,7 @@ public void LoggerCategoryNameIsKestrelServerNamespace() var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - new KestrelServer(Options.Create(null), Mock.Of(), mockLoggerFactory.Object); + new KestrelServer(Options.Create(null), Mock.Of(), mockLoggerFactory.Object); mockLoggerFactory.Verify(factory => factory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel")); } @@ -233,21 +234,18 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var unbind = new SemaphoreSlim(0); var stop = new SemaphoreSlim(0); - var mockTransport = new Mock(); + var mockTransport = new Mock(); mockTransport - .Setup(transport => transport.BindAsync()) - .Returns(Task.CompletedTask); + .Setup(transport => transport.AcceptAsync()) + .Returns(new ValueTask((ConnectionContext)null)); mockTransport - .Setup(transport => transport.UnbindAsync()) - .Returns(async () => await unbind.WaitAsync()); - mockTransport - .Setup(transport => transport.StopAsync()) + .Setup(transport => transport.StopAsync(It.IsAny())) .Returns(async () => await stop.WaitAsync()); - var mockTransportFactory = new Mock(); + var mockTransportFactory = new Mock(); mockTransportFactory - .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) - .Returns(mockTransport.Object); + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) + .Returns(new ValueTask(mockTransport.Object)); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); @@ -255,9 +253,9 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); await server.StartAsync(new DummyApplication(), CancellationToken.None); - var stopTask1 = server.StopAsync(default(CancellationToken)); - var stopTask2 = server.StopAsync(default(CancellationToken)); - var stopTask3 = server.StopAsync(default(CancellationToken)); + var stopTask1 = server.StopAsync(default); + var stopTask2 = server.StopAsync(default); + var stopTask3 = server.StopAsync(default); Assert.False(stopTask1.IsCompleted); Assert.False(stopTask2.IsCompleted); @@ -268,124 +266,136 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() await Task.WhenAll(new[] { stopTask1, stopTask2, stopTask3 }).DefaultTimeout(); - mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); - mockTransport.Verify(transport => transport.StopAsync(), Times.Once); + mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); } - [Fact] - public async Task StopAsyncCallsCompleteWithThrownException() - { - var options = new KestrelServerOptions - { - ListenOptions = - { - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - } - }; - - var unbind = new SemaphoreSlim(0); - var unbindException = new InvalidOperationException(); - - var mockTransport = new Mock(); - mockTransport - .Setup(transport => transport.BindAsync()) - .Returns(Task.CompletedTask); - mockTransport - .Setup(transport => transport.UnbindAsync()) - .Returns(async () => - { - await unbind.WaitAsync(); - throw unbindException; - }); - mockTransport - .Setup(transport => transport.StopAsync()) - .Returns(Task.CompletedTask); - - var mockTransportFactory = new Mock(); - mockTransportFactory - .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) - .Returns(mockTransport.Object); - - var mockLoggerFactory = new Mock(); - var mockLogger = new Mock(); - mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); - await server.StartAsync(new DummyApplication(), CancellationToken.None); - - var stopTask1 = server.StopAsync(default(CancellationToken)); - var stopTask2 = server.StopAsync(default(CancellationToken)); - var stopTask3 = server.StopAsync(default(CancellationToken)); - - Assert.False(stopTask1.IsCompleted); - Assert.False(stopTask2.IsCompleted); - Assert.False(stopTask3.IsCompleted); - - unbind.Release(); - - var timeout = TestConstants.DefaultTimeout; - Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask1.TimeoutAfter(timeout))); - Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask2.TimeoutAfter(timeout))); - Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask3.TimeoutAfter(timeout))); - - mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); - } - - [Fact] - public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() - { - var options = new KestrelServerOptions - { - ListenOptions = - { - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - } - }; - - var unbindTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var mockTransport = new Mock(); - mockTransport - .Setup(transport => transport.BindAsync()) - .Returns(Task.CompletedTask); - mockTransport - .Setup(transport => transport.UnbindAsync()) - .Returns(unbindTcs.Task); - mockTransport - .Setup(transport => transport.StopAsync()) - .Returns(Task.CompletedTask); - - var mockTransportFactory = new Mock(); - mockTransportFactory - .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) - .Returns(mockTransport.Object); - - var mockLoggerFactory = new Mock(); - var mockLogger = new Mock(); - mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); - await server.StartAsync(new DummyApplication(), default); - - var stopTask1 = server.StopAsync(default); - var stopTask2 = server.StopAsync(default); - - Assert.False(stopTask1.IsCompleted); - Assert.False(stopTask2.IsCompleted); - - var continuationTask = Task.Run(async () => - { - await stopTask2; - stopTask1.Wait(); - }); - - unbindTcs.SetResult(null); - - // If stopTask2 is completed inline by the first call to StopAsync, stopTask1 will never complete. - await stopTask1.DefaultTimeout(); - await stopTask2.DefaultTimeout(); - await continuationTask.DefaultTimeout(); - - mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); - } + //[Fact] + //public async Task StopAsyncCallsCompleteWithThrownException() + //{ + // var options = new KestrelServerOptions + // { + // ListenOptions = + // { + // new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + // } + // }; + + // var unbind = new SemaphoreSlim(0); + // var unbindException = new InvalidOperationException(); + + // var mockTransport = new Mock(); + // mockTransport + // .Setup(transport => transport.BindAsync()) + // .Returns(Task.CompletedTask); + // mockTransport + // .Setup(transport => transport.UnbindAsync()) + // .Returns(async () => + // { + // await unbind.WaitAsync(); + // throw unbindException; + // }); + // mockTransport + // .Setup(transport => transport.StopAsync()) + // .Returns(Task.CompletedTask); + + // var mockTransportFactory = new Mock(); + // mockTransportFactory + // .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) + // .Returns(mockTransport.Object); + + // var mockLoggerFactory = new Mock(); + // var mockLogger = new Mock(); + // mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); + // var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + // await server.StartAsync(new DummyApplication(), CancellationToken.None); + + // var stopTask1 = server.StopAsync(default); + // var stopTask2 = server.StopAsync(default); + // var stopTask3 = server.StopAsync(default); + + // Assert.False(stopTask1.IsCompleted); + // Assert.False(stopTask2.IsCompleted); + // Assert.False(stopTask3.IsCompleted); + + // unbind.Release(); + + // var timeout = TestConstants.DefaultTimeout; + // Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask1.TimeoutAfter(timeout))); + // Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask2.TimeoutAfter(timeout))); + // Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask3.TimeoutAfter(timeout))); + + // mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); + //} + + //[Fact] + //public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() + //{ + // var options = new KestrelServerOptions + // { + // ListenOptions = + // { + // new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + // } + // }; + + // var unbindTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + // //var mockTransport = new Mock(); + // //mockTransport + // // .Setup(transport => transport.BindAsync()) + // // .Returns(Task.CompletedTask); + // //mockTransport + // // .Setup(transport => transport.UnbindAsync()) + // // .Returns(unbindTcs.Task); + // //mockTransport + // // .Setup(transport => transport.StopAsync()) + // // .Returns(Task.CompletedTask); + + // //var mockTransportFactory = new Mock(); + // //mockTransportFactory + // // .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) + // // .Returns(mockTransport.Object); + + // var mockTransport = new Mock(); + // mockTransport + // .Setup(transport => transport.AcceptAsync()) + // .Returns(new ValueTask((ConnectionContext)null)); + // mockTransport + // .Setup(transport => transport.StopAsync(It.IsAny())) + // .Returns(unbindTcs.Task); + + // var mockTransportFactory = new Mock(); + // mockTransportFactory + // .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) + // .Returns(new ValueTask(mockTransport.Object)); + + // var mockLoggerFactory = new Mock(); + // var mockLogger = new Mock(); + // mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); + // var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + // await server.StartAsync(new DummyApplication(), default); + + // var stopTask1 = server.StopAsync(default); + // var stopTask2 = server.StopAsync(default); + + // Assert.False(stopTask1.IsCompleted); + // Assert.False(stopTask2.IsCompleted); + + // var continuationTask = Task.Run(async () => + // { + // await stopTask2; + // stopTask1.Wait(); + // }); + + // unbindTcs.SetResult(null); + + // // If stopTask2 is completed inline by the first call to StopAsync, stopTask1 will never complete. + // await stopTask1.DefaultTimeout(); + // await stopTask2.DefaultTimeout(); + // await continuationTask.DefaultTimeout(); + + // mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); + //} [Fact] public void StartingServerInitializesHeartbeat() @@ -438,11 +448,11 @@ private static void StartDummyApplication(IServer server) server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None).GetAwaiter().GetResult(); } - private class MockTransportFactory : ITransportFactory + private class MockTransportFactory : IConnectionListenerFactory { - public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher handler) + public ValueTask BindAsync(EndPoint endpoint) { - return Mock.Of(); + return new ValueTask(Mock.Of()); } } } diff --git a/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs b/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs index 7ea16d4c858e..fd5c31d032d0 100644 --- a/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs @@ -1,9 +1,9 @@ // 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.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using Microsoft.Extensions.DependencyInjection; @@ -57,7 +57,7 @@ public void SocketTransportIsTheDefault() .UseKestrel() .Configure(app => { }); - Assert.IsType(hostBuilder.Build().Services.GetService()); + Assert.IsType(hostBuilder.Build().Services.GetService()); } [Fact] @@ -68,14 +68,14 @@ public void LibuvTransportCanBeManuallySelectedIndependentOfOrder() .UseLibuv() .Configure(app => { }); - Assert.IsType(hostBuilder.Build().Services.GetService()); + Assert.IsType(hostBuilder.Build().Services.GetService()); var hostBuilderReversed = new WebHostBuilder() .UseLibuv() .UseKestrel() .Configure(app => { }); - Assert.IsType(hostBuilderReversed.Build().Services.GetService()); + Assert.IsType(hostBuilderReversed.Build().Services.GetService()); } [Fact] @@ -86,14 +86,14 @@ public void SocketsTransportCanBeManuallySelectedIndependentOfOrder() .UseSockets() .Configure(app => { }); - Assert.IsType(hostBuilder.Build().Services.GetService()); + Assert.IsType(hostBuilder.Build().Services.GetService()); var hostBuilderReversed = new WebHostBuilder() .UseSockets() .UseKestrel() .Configure(app => { }); - Assert.IsType(hostBuilderReversed.Build().Services.GetService()); + Assert.IsType(hostBuilderReversed.Build().Services.GetService()); } } } diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs index bbb6d6fe49de..cf36786f0476 100644 --- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs +++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs @@ -8,11 +8,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -86,7 +86,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action Assert.Equal(context.ExpectedConnectionMiddlewareCount, lo._middleware.Count)); - return new KestrelServer(sp.GetRequiredService(), context); + return new KestrelServer(sp.GetRequiredService(), context); }); configureServices(services); }) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index 3b437451921a..84c4aa71f5dc 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -6,13 +6,14 @@ using System.IO.Pipelines; using System.Net; using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport { - internal class InMemoryTransportConnection : TransportConnection, IDisposable + internal class InMemoryTransportConnection : TransportConnection { private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource(); @@ -27,6 +28,10 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger) LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(), new PipeOptions()); + Application = pair.Application; + Transport = pair.Transport; + ConnectionClosed = _connectionClosedTokenSource.Token; } @@ -59,9 +64,14 @@ public void OnClosed() }, this); } - public void Dispose() + public override ValueTask DisposeAsync() { _connectionClosedTokenSource.Dispose(); + + Transport.Input.Complete(); + Transport.Output.Complete(); + + return base.DisposeAsync(); } } } From 834811fda9425194e9abbcd622c071e4ff54268b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 19 May 2019 00:09:53 -0700 Subject: [PATCH 17/78] Use the memory pool --- .../TestTransport/InMemoryTransportConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index 84c4aa71f5dc..f3281075079b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -28,7 +28,7 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger) LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 0); - var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(), new PipeOptions()); + var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(memoryPool), new PipeOptions(memoryPool)); Application = pair.Application; Transport = pair.Transport; From 7592ba4814c575bdf5d5ce99748e9636e9f0e728 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 May 2019 21:37:23 -0700 Subject: [PATCH 18/78] Move graceful shutdown back to the caller --- .../src/IConnectionListener.cs | 2 +- .../Infrastructure/ConnectionManager.cs | 35 ++++- .../ConnectionManagerShutdownExtensions.cs | 47 ++++++ .../Infrastructure/KestrelConnection.cs | 21 ++- src/Servers/Kestrel/Core/src/KestrelServer.cs | 20 +++ .../Kestrel/Core/test/KestrelServerTests.cs | 4 +- .../TransportConnection.FeatureCollection.cs | 11 +- .../Internal/TransportConnection.Generated.cs | 23 --- .../src/Internal/TransportConnection.cs | 18 --- .../src/Internal/LibuvConnection.cs | 1 - .../src/Internal/LibuvConnectionListener.cs | 2 +- .../src/Internal/ListenerContext.cs | 5 +- .../src/Internal/SocketConnection.cs | 3 +- .../src/SocketConnectionListener.cs | 134 ++---------------- .../TestTransport/InMemoryTransportFactory.cs | 4 +- .../TransportConnectionFeatureCollection.cs | 3 +- 16 files changed, 143 insertions(+), 190 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index d94404d168a4..2a9b2bedb67e 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -11,7 +11,7 @@ public interface IConnectionListener { EndPoint EndPoint { get; } - ValueTask AcceptAsync(); + ValueTask AcceptAsync(CancellationToken cancellationToken = default); ValueTask StopAsync(CancellationToken cancellationToken = default); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs index cb402facea81..7c40b3bc8c53 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs @@ -1,8 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Concurrent; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { @@ -10,6 +12,8 @@ internal class ConnectionManager { private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); private readonly IKestrelTrace _trace; + private TaskCompletionSource _connectionDrainedTcs; + private int _connectionCount; public ConnectionManager(IKestrelTrace trace, long? upgradedConnectionLimit) : this(trace, GetCounter(upgradedConnectionLimit)) @@ -29,6 +33,7 @@ public ConnectionManager(IKestrelTrace trace, ResourceCounter upgradedConnection public void AddConnection(long id, KestrelConnection connection) { + Interlocked.Increment(ref _connectionCount); if (!_connectionReferences.TryAdd(id, new ConnectionReference(connection))) { throw new ArgumentException(nameof(id)); @@ -41,6 +46,34 @@ public void RemoveConnection(long id) { throw new ArgumentException(nameof(id)); } + var count = Interlocked.Decrement(ref _connectionCount); + + if (count == 0 && _connectionDrainedTcs != null) + { + _connectionDrainedTcs.TrySetResult(null); + } + } + + // This method should be called when no new connections can be added + public bool TryStartDrainingConnection() + { + if (_connectionCount == 0) + { + return false; + } + + _connectionDrainedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + return true; + } + + public Task WaitForConnectionDrainAsync() + { + if (_connectionDrainedTcs == null) + { + throw new InvalidOperationException("TryStartDrainingConnection must be called before WaitForConnectionDrainAsync()"); + } + + return _connectionDrainedTcs.Task; } public void Walk(Action callback) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs new file mode 100644 index 000000000000..720ff06af551 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.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.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal static class ConnectionManagerShutdownExtensions + { + public static async Task CloseAllConnectionsAsync(this ConnectionManager connectionManager, CancellationToken token) + { + connectionManager.Walk(connection => + { + connection.RequestClose(); + }); + + var allClosedTask = connectionManager.WaitForConnectionDrainAsync(); + return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; + } + + public static async Task AbortAllConnectionsAsync(this ConnectionManager connectionManager) + { + connectionManager.Walk(connection => + { + connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); + }); + + var allAbortedTask = connectionManager.WaitForConnectionDrainAsync(); + return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; + } + + private static Task CancellationTokenAsTask(CancellationToken token) + { + if (token.IsCancellationRequested) + { + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + token.Register(() => tcs.SetResult(null)); + return tcs.Task; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index 802364f7145c..b89a7d74c526 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; @@ -10,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature + internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature { private List<(Action handler, object state)> _heartbeatHandlers; private readonly object _heartbeatLock = new object(); @@ -18,17 +19,22 @@ internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompl private Stack, object>> _onCompleted; private bool _completed; + private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); + public KestrelConnection(ConnectionContext connectionContext, ILogger logger) { Logger = logger; TransportConnection = connectionContext; connectionContext.Features.Set(this); connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + ConnectionClosedRequested = _connectionClosingCts.Token; } private ILogger Logger { get; } public ConnectionContext TransportConnection { get; set; } + public CancellationToken ConnectionClosedRequested { get; set; } public void TickHeartbeat() { @@ -135,5 +141,18 @@ private async Task CompleteAsyncAwaited(Task currentTask, Stack transport.AcceptAsync()) .Returns(new ValueTask((ConnectionContext)null)); mockTransport - .Setup(transport => transport.StopAsync(It.IsAny())) + .Setup(transport => transport.StopListeningAsync(It.IsAny())) .Returns(async () => await stop.WaitAsync()); var mockTransportFactory = new Mock(); @@ -266,7 +266,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() await Task.WhenAll(new[] { stopTask1, stopTask2, stopTask3 }).DefaultTimeout(); - mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); + mockTransport.Verify(transport => transport.StopListeningAsync(It.IsAny()), Times.Once); } //[Fact] diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs index 7c0d6cc6895b..871d46945acd 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs @@ -14,8 +14,7 @@ public partial class TransportConnection : IConnectionIdFeature, IConnectionTransportFeature, IConnectionItemsFeature, IMemoryPoolFeature, - IConnectionLifetimeFeature, - IConnectionLifetimeNotificationFeature + IConnectionLifetimeFeature { // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, // then the list of `features` in the generated code project MUST also be updated. @@ -41,14 +40,6 @@ CancellationToken IConnectionLifetimeFeature.ConnectionClosed set => ConnectionClosed = value; } - CancellationToken IConnectionLifetimeNotificationFeature.ConnectionClosedRequested - { - get => ConnectionClosedRequested; - set => ConnectionClosedRequested = value; - } - void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); - - void IConnectionLifetimeNotificationFeature.RequestClose() => RequestClose(); } } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs index aba064e1453f..433563ca9912 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs @@ -17,14 +17,12 @@ public partial class TransportConnection : IFeatureCollection private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); - private static readonly Type IConnectionLifetimeNotificationFeatureType = typeof(IConnectionLifetimeNotificationFeature); private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; private object _currentIMemoryPoolFeature; private object _currentIConnectionLifetimeFeature; - private object _currentIConnectionLifetimeNotificationFeature; private int _featureRevision; @@ -37,7 +35,6 @@ private void FastReset() _currentIConnectionItemsFeature = this; _currentIMemoryPoolFeature = this; _currentIConnectionLifetimeFeature = this; - _currentIConnectionLifetimeNotificationFeature = this; } @@ -113,10 +110,6 @@ object IFeatureCollection.this[Type key] { feature = _currentIConnectionLifetimeFeature; } - else if (key == IConnectionLifetimeNotificationFeatureType) - { - feature = _currentIConnectionLifetimeNotificationFeature; - } else if (MaybeExtra != null) { feature = ExtraFeatureGet(key); @@ -149,10 +142,6 @@ object IFeatureCollection.this[Type key] { _currentIConnectionLifetimeFeature = value; } - else if (key == IConnectionLifetimeNotificationFeatureType) - { - _currentIConnectionLifetimeNotificationFeature = value; - } else { ExtraFeatureSet(key, value); @@ -183,10 +172,6 @@ TFeature IFeatureCollection.Get() { feature = (TFeature)_currentIConnectionLifetimeFeature; } - else if (typeof(TFeature) == typeof(IConnectionLifetimeNotificationFeature)) - { - feature = (TFeature)_currentIConnectionLifetimeNotificationFeature; - } else if (MaybeExtra != null) { feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); @@ -218,10 +203,6 @@ void IFeatureCollection.Set(TFeature feature) { _currentIConnectionLifetimeFeature = feature; } - else if (typeof(TFeature) == typeof(IConnectionLifetimeNotificationFeature)) - { - _currentIConnectionLifetimeNotificationFeature = feature; - } else { ExtraFeatureSet(typeof(TFeature), feature); @@ -250,10 +231,6 @@ private IEnumerable> FastEnumerable() { yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); } - if (_currentIConnectionLifetimeNotificationFeature != null) - { - yield return new KeyValuePair(IConnectionLifetimeNotificationFeatureType, _currentIConnectionLifetimeNotificationFeature); - } if (MaybeExtra != null) { diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs index 3c9ad0366d37..1aafd7fd7805 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs @@ -16,13 +16,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal public abstract partial class TransportConnection : ConnectionContext { private IDictionary _items; - protected readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); public TransportConnection() { FastReset(); - - ConnectionClosedRequested = _connectionClosingCts.Token; } public override EndPoint LocalEndPoint { get; set; } @@ -57,8 +54,6 @@ public override IDictionary Items public override CancellationToken ConnectionClosed { get; set; } - public CancellationToken ConnectionClosedRequested { get; set; } - // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause // any TransportConnection that does not override Abort or calls base.Abort // to stack overflow when IConnectionLifetimeFeature.Abort() is called. @@ -69,18 +64,5 @@ public override void Abort(ConnectionAbortedException abortReason) { Output.CancelPendingRead(); } - - public void RequestClose() - { - try - { - _connectionClosingCts.Cancel(); - } - catch (ObjectDisposedException) - { - // There's a race where the token could be disposed - // swallow the exception and no-op - } - } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 306fdd73241f..c251dbe74cf6 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -149,7 +149,6 @@ public override async ValueTask DisposeAsync() } _connectionClosedTokenSource.Dispose(); - _connectionClosingCts.Dispose(); Transport.Input.Complete(); Transport.Output.Complete(); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index eed64a8537e4..942be2863b42 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -173,7 +173,7 @@ private static async Task WaitAsync(Task task, TimeSpan timeout) return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task; } - public async ValueTask AcceptAsync() + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { if (await _acceptEnumerator.MoveNextAsync()) { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index eef843dc711b..f9e35e85c3ce 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -4,6 +4,7 @@ using System; using System.Net; using System.Net.Sockets; +using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; @@ -32,9 +33,9 @@ public ListenerContext(LibuvTransportContext transportContext) public LibuvThread Thread { get; set; } - public ValueTask AcceptAsync() + public ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - return _acceptQueue.Reader.ReadAsync(); + return _acceptQueue.Reader.ReadAsync(cancellationToken); } /// diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 3837cbb22d9b..47148e6ccc25 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal { - internal class SocketConnection : TransportConnection + internal sealed class SocketConnection : TransportConnection { private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -121,7 +121,6 @@ public override async ValueTask DisposeAsync() Transport.Output.Complete(); _connectionClosedTokenSource.Dispose(); - _connectionClosingCts.Dispose(); } private async Task DoReceive() diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 5ed031c33756..37f89405d96c 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -3,8 +3,6 @@ using System; using System.Buffers; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Net; @@ -25,10 +23,6 @@ internal sealed class SocketConnectionListener : IConnectionListener private Socket _listenSocket; private int _schedulerIndex; private readonly SocketTransportOptions _options; - private TaskCompletionSource _connectionsDrainedTcs; - private int _pendingConnections; - private int _pendingAccepts; - private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); public EndPoint EndPoint { get; private set; } @@ -96,19 +90,14 @@ internal void Bind() _listenSocket = listenSocket; } - public async ValueTask AcceptAsync() + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - Interlocked.Increment(ref _pendingAccepts); - try { var acceptSocket = await _listenSocket.AcceptAsync(); acceptSocket.NoDelay = _options.NoDelay; - int id = Interlocked.Increment(ref _pendingConnections); - - var connection = new TrackedSocketConnection(this, id, acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); - _connections.TryAdd(id, connection); + var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); connection.Start(); @@ -120,125 +109,22 @@ public async ValueTask AcceptAsync() { return null; } - finally - { - Interlocked.Decrement(ref _pendingAccepts); - } } - public async ValueTask StopAsync(CancellationToken cancellationToken) + public ValueTask StopAsync(CancellationToken cancellationToken) { - var listenSocket = Interlocked.Exchange(ref _listenSocket, null); - - if (listenSocket != null) - { - // Unbind the listen socket, so no new connections can come in - listenSocket.Dispose(); + _listenSocket?.Dispose(); + _listenSocket = null; - // Wait for all pending accepts to drain - var spin = new SpinWait(); - while (_pendingAccepts > 0) - { - spin.SpinOnce(); - } - - // No more pending accepts by the time we get here, if there any any pending connections, we create a new TCS and wait for them to - // drain. - if (_pendingConnections > 0) - { - _connectionsDrainedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - // Try to do graceful close - foreach (var pair in _connections) - { - pair.Value.RequestClose(); - } - - if (!_connectionsDrainedTcs.Task.IsCompletedSuccessfully) - { - // Wait for connections to drain or for the token to fire - var task = CancellationTokenAsTask(cancellationToken); - var result = await Task.WhenAny(_connectionsDrainedTcs.Task, task); - - if (result != _connectionsDrainedTcs.Task) - { - // If the connections don't shutdown then we need to abort them - foreach (var pair in _connections) - { - pair.Value.Abort(); - } - } - } - - // REVIEW: Should we try to wait again? - // await _connectionsDrainedTcs.Task; - } - } - - // Dispose the memory pool - _memoryPool.Dispose(); - } - - private void OnConnectionDisposed(int id) - { - // Disconnect called, wait for _gracefulShutdownTcs to be assigned - if (_listenSocket == null && _connectionsDrainedTcs == null) - { - var spin = new SpinWait(); - while (_connectionsDrainedTcs == null) - { - spin.SpinOnce(); - } - } - - // If a call to dispose is currently running then we need to wait until the _gracefulShutdownTcs - // has been assigned - var connections = Interlocked.Decrement(ref _pendingConnections); - - _connections.TryRemove(id, out _); - - if (_connectionsDrainedTcs != null && connections == 0) - { - _connectionsDrainedTcs.TrySetResult(null); - } - } - - private static Task CancellationTokenAsTask(CancellationToken token) - { - if (token.IsCancellationRequested) - { - return Task.CompletedTask; - } - - // Transports already dispatch prior to tripping ConnectionClosed - // since application code can register to this token. - var tcs = new TaskCompletionSource(); - token.Register(state => ((TaskCompletionSource)state).SetResult(null), tcs); - return tcs.Task; + return default; } public ValueTask DisposeAsync() { - return StopAsync(new CancellationTokenSource(5000).Token); - } - - private class TrackedSocketConnection : SocketConnection - { - private readonly SocketConnectionListener _listener; - private readonly int _id; - - internal TrackedSocketConnection(SocketConnectionListener listener, int id, Socket socket, MemoryPool memoryPool, PipeScheduler scheduler, ISocketsTrace trace) : base(socket, memoryPool, scheduler, trace) - { - _listener = listener; - _id = id; - } - - public override async ValueTask DisposeAsync() - { - await base.DisposeAsync(); - - _listener.OnConnectionDisposed(_id); - } + _listenSocket?.Dispose(); + // Dispose the memory pool + _memoryPool.Dispose(); + return default; } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs index 4aba82e4c907..5bc87b0ab820 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs @@ -21,9 +21,9 @@ public void AddConnection(ConnectionContext connection) _acceptQueue.Writer.TryWrite(connection); } - public async ValueTask AcceptAsync() + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - if (await _acceptQueue.Reader.WaitToReadAsync()) + if (await _acceptQueue.Reader.WaitToReadAsync(cancellationToken)) { while (_acceptQueue.Reader.TryRead(out var item)) { diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index cda25ed2f68e..68738138a696 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -15,8 +15,7 @@ public static string GenerateFile() "IConnectionTransportFeature", "IConnectionItemsFeature", "IMemoryPoolFeature", - "IConnectionLifetimeFeature", - "IConnectionLifetimeNotificationFeature", + "IConnectionLifetimeFeature" }; var usings = $@" From 2ff860305d46d8e155e9662ff42c953c4d127897 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 May 2019 23:09:49 -0700 Subject: [PATCH 19/78] More clean up - Reduce the amount of breaking changes by making the new members virtual - Removed IConnectionFactory for as part of this PR to focus on the server first. - Updated the SignalR APIs to override DisposeAsync (this is breaking) --- .../src/ConnectionContext.cs | 6 +++--- .../src/DefaultConnectionContext.cs | 8 ++++++++ .../src/IConnectionFactory.cs | 13 ------------- .../Transport.Sockets/src/SocketTransportFactory.cs | 13 +------------ .../csharp/Client/src/HttpConnectionFactory.cs | 4 ++-- .../Http.Connections.Client/src/HttpConnection.cs | 2 +- 6 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 src/Servers/Connections.Abstractions/src/IConnectionFactory.cs diff --git a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs index 5e2a0ee5339f..cecf3513c793 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs @@ -21,11 +21,11 @@ public abstract class ConnectionContext public abstract IDuplexPipe Transport { get; set; } - public abstract CancellationToken ConnectionClosed { get; set; } + public virtual CancellationToken ConnectionClosed { get; set; } - public abstract EndPoint LocalEndPoint { get; set; } + public virtual EndPoint LocalEndPoint { get; set; } - public abstract EndPoint RemoteEndPoint { get; set; } + public virtual EndPoint RemoteEndPoint { get; set; } public virtual void Abort(ConnectionAbortedException abortReason) { diff --git a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs index d0f2d94f8cc0..81a56478ba79 100644 --- a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs @@ -7,6 +7,7 @@ using System.Net; using System.Security.Claims; using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; @@ -44,6 +45,7 @@ public DefaultConnectionContext(string id) Features.Set(this); Features.Set(this); Features.Set(this); + Features.Set(this); } public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application) @@ -78,5 +80,11 @@ public void Dispose() { _connectionClosedTokenSource.Dispose(); } + + public override ValueTask DisposeAsync() + { + _connectionClosedTokenSource.Dispose(); + return base.DisposeAsync(); + } } } diff --git a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs deleted file mode 100644 index 1770171c5e98..000000000000 --- a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Connections -{ - public interface IConnectionFactory - { - ValueTask ConnectAsync(System.Net.EndPoint endpoint, CancellationToken cancellationToken = default); - } -} diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs index 8564c9394104..c26cfcc66eaf 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs @@ -2,10 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; using System.Net; -using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; @@ -15,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { - public sealed class SocketTransportFactory : IConnectionListenerFactory, IConnectionFactory + public sealed class SocketTransportFactory : IConnectionListenerFactory { private readonly SocketTransportOptions _options; private readonly SocketsTrace _trace; @@ -50,13 +47,5 @@ public ValueTask BindAsync(EndPoint endpoint) transport.Bind(); return new ValueTask(transport); } - - public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) - { - // REVIEW: How do we pick the type of socket? Is the endpoint enough? - var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); - await socket.ConnectAsync(endpoint); - return new SocketConnection(socket, _options.MemoryPoolFactory(), PipeScheduler.ThreadPool, _trace); - } } } diff --git a/src/SignalR/clients/csharp/Client/src/HttpConnectionFactory.cs b/src/SignalR/clients/csharp/Client/src/HttpConnectionFactory.cs index 99da56fbb436..3ecf4dacd085 100644 --- a/src/SignalR/clients/csharp/Client/src/HttpConnectionFactory.cs +++ b/src/SignalR/clients/csharp/Client/src/HttpConnectionFactory.cs @@ -60,7 +60,7 @@ public async Task ConnectAsync(TransferFormat transferFormat, /// public Task DisposeAsync(ConnectionContext connection) { - return ((HttpConnection)connection).DisposeAsync(); + return connection.DisposeAsync().AsTask(); } } -} \ No newline at end of file +} diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index fdbecdef6422..4953e82c92b2 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -234,7 +234,7 @@ private async Task StartAsyncCore(TransferFormat transferFormat, CancellationTok /// A connection cannot be restarted after it has stopped. To restart a connection /// a new instance should be created using the same options. /// - public async Task DisposeAsync() + public override async ValueTask DisposeAsync() { using (_logger.BeginScope(_logScope)) { From f44caae320ce3fdb9c17a374c3be81293927c52b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 May 2019 23:10:46 -0700 Subject: [PATCH 20/78] Revert making LibuvTransportFactory public --- .../src/Internal/LibuvTransportFactory.cs | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs index cd39f5a4575e..771d3aad1093 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs @@ -3,25 +3,18 @@ using System; using System.Net; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { - public class LibuvTransportFactory : IConnectionListenerFactory + internal class LibuvTransportFactory : IConnectionListenerFactory { private readonly LibuvTransportContext _baseTransportContext; - public LibuvTransportFactory() : this(Options.Create(new LibuvTransportOptions()), new DefaultHostLifetime(), NullLoggerFactory.Instance) - { - - } - public LibuvTransportFactory( IOptions options, IHostApplicationLifetime applicationLifetime, @@ -83,18 +76,5 @@ public async ValueTask BindAsync(EndPoint endpoint) await transport.BindAsync(); return transport; } - - private class DefaultHostLifetime : IHostApplicationLifetime - { - public CancellationToken ApplicationStarted => throw new NotSupportedException(); - - public CancellationToken ApplicationStopping => throw new NotSupportedException(); - - public CancellationToken ApplicationStopped => throw new NotSupportedException(); - - public void StopApplication() - { - } - } } } From 1e4fcfe7c7f2d282e68829793260b11c6416d0b2 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 May 2019 23:21:34 -0700 Subject: [PATCH 21/78] Minor clean up --- .../Transport.Libuv/src/Internal/LibuvConnection.cs | 1 - src/Servers/Kestrel/samples/PlaintextApp/Startup.cs | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index c251dbe74cf6..487e91a8e07e 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -51,7 +51,6 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); // Set the transport and connection id - // connection.ConnectionId = CorrelationIdGenerator.GetNextId(); Transport = pair.Transport; Application = pair.Application; } diff --git a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs index 4614d3f2ac63..25b1f9e4e2db 100644 --- a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs +++ b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; namespace PlaintextApp @@ -47,12 +46,9 @@ public static async Task Main(string[] args) .Build(); var hostTask = host.RunAsync(); - // var serverTask = ServerAsync(new SocketTransportFactory(), 5002); - // var serverTask2 = ServerAsync(new LibuvTransportFactory(), 5003); + var serverTask = ServerAsync(new SocketTransportFactory(), 5002); - await hostTask; - // await serverTask; - // await serverTask2; + await hostTask;; } private static async Task ServerAsync(IConnectionListenerFactory factory, int port) From 3303a6a6414e06ad32da64222b5c29f7bdc3840b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 May 2019 23:32:03 -0700 Subject: [PATCH 22/78] Shifting and deleting things --- src/Servers/Kestrel/Core/src/KestrelServer.cs | 19 +------- src/Servers/Kestrel/Core/src/ListenOptions.cs | 2 +- .../src/Internal => Core/src}/ListenType.cs | 4 +- .../Internal => Core/src}/SchedulingMode.cs | 0 .../src/FileHandleEndPoint.cs | 4 -- .../src/Internal/IEndPointInformation.cs | 46 ------------------- 6 files changed, 4 insertions(+), 71 deletions(-) rename src/Servers/Kestrel/{Transport.Abstractions/src/Internal => Core/src}/ListenType.cs (72%) rename src/Servers/Kestrel/{Transport.Abstractions/src/Internal => Core/src}/SchedulingMode.cs (100%) delete mode 100644 src/Servers/Kestrel/Transport.Abstractions/src/Internal/IEndPointInformation.cs diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 24de82209bcb..e8f70a361d55 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -78,27 +77,11 @@ private static ServiceContext CreateServiceContext(IOptions and off - // the service context once we get to https://github.com/aspnet/KestrelHttpServer/issues/1662 - PipeScheduler scheduler = null; - switch (serverOptions.ApplicationSchedulingMode) - { - case SchedulingMode.Default: - case SchedulingMode.ThreadPool: - scheduler = PipeScheduler.ThreadPool; - break; - case SchedulingMode.Inline: - scheduler = PipeScheduler.Inline; - break; - default: - throw new NotSupportedException(CoreStrings.FormatUnknownTransportMode(serverOptions.ApplicationSchedulingMode)); - } - return new ServiceContext { Log = trace, HttpParser = new HttpParser(trace.IsEnabled(LogLevel.Information)), - Scheduler = scheduler, + Scheduler = PipeScheduler.ThreadPool, SystemClock = heartbeatManager, DateHeaderValueManager = dateHeaderValueManager, ConnectionManager = connectionManager, diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 2fde7f84b465..79c17441279d 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// Describes either an , Unix domain socket path, or a file descriptor for an already open /// socket that Kestrel should bind to or open. /// - public class ListenOptions : IEndPointInformation, IConnectionBuilder + public class ListenOptions : IConnectionBuilder { private FileHandleType _handleType; internal readonly List> _middleware = new List>(); diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ListenType.cs b/src/Servers/Kestrel/Core/src/ListenType.cs similarity index 72% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/ListenType.cs rename to src/Servers/Kestrel/Core/src/ListenType.cs index 3616f1967ee5..87d7fe1421a3 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/ListenType.cs +++ b/src/Servers/Kestrel/Core/src/ListenType.cs @@ -1,10 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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. namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { /// - /// Enumerates the types. + /// Enumerates the possible endpoint types. /// public enum ListenType { diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/SchedulingMode.cs b/src/Servers/Kestrel/Core/src/SchedulingMode.cs similarity index 100% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/SchedulingMode.cs rename to src/Servers/Kestrel/Core/src/SchedulingMode.cs diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs index 4aef9eb66ecc..f38bdc9a98e1 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Net; -using System.Net.Sockets; -using System.Text; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions { diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IEndPointInformation.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IEndPointInformation.cs deleted file mode 100644 index 1b7abfa4975e..000000000000 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/IEndPointInformation.cs +++ /dev/null @@ -1,46 +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.Net; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface IEndPointInformation - { - /// - /// The type of interface being described: either an , Unix domain socket path, or a file descriptor. - /// - ListenType Type { get; } - - // IPEndPoint is mutable so port 0 can be updated to the bound port. - /// - /// The to bind to. - /// Only set if is . - /// - IPEndPoint IPEndPoint { get; set; } - - /// - /// The absolute path to a Unix domain socket to bind to. - /// Only set if is . - /// - string SocketPath { get; } - - /// - /// A file descriptor for the socket to open. - /// Only set if is . - /// - ulong FileHandle { get; } - - // HandleType is mutable so it can be re-specified later. - /// - /// The type of file descriptor being used. - /// Only set if is . - /// - FileHandleType HandleType { get; set; } - - /// - /// Set to false to enable Nagle's algorithm for all connections. - /// - bool NoDelay { get; } - } -} From 603aff3c3c4233f81559ef3f4ebee486afcfc5ce Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 22 May 2019 23:43:33 -0700 Subject: [PATCH 23/78] Make more tests compile --- .../Core/src/Internal/ConnectionDispatcher.cs | 3 +- .../Core/test/ConnectionDispatcherTests.cs | 2 +- .../Core/test/HttpConnectionManagerTests.cs | 3 +- .../Kestrel/Core/test/KestrelServerTests.cs | 6 +-- .../Kestrel/Core/test/PipeOptionsTests.cs | 39 +------------------ 5 files changed, 9 insertions(+), 44 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 87414853352c..2296b8a52e8d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -62,7 +62,8 @@ async Task AcceptConnectionsAsync() } } - private async Task OnConnection(ConnectionContext connection) + // Internal for testing + internal async Task OnConnection(ConnectionContext connection) { await using (connection) { diff --git a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs index 1d1c3b46e3bc..ad53b66ffcfc 100644 --- a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs +++ b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs @@ -30,7 +30,7 @@ public void OnConnectionCreatesLogScopeWithConnectionId() var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - dispatcher.OnConnection(connection); + _ = dispatcher.OnConnection(connection); // The scope should be created var scopeObjects = ((TestKestrelTrace)serviceContext.Log) diff --git a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs index 809c719e5bb3..a83614b8ca7f 100644 --- a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -40,7 +41,7 @@ private void UnrootedConnectionsGetRemovedFromHeartbeatInnerScope( { var mock = new Mock(); mock.Setup(m => m.ConnectionId).Returns(connectionId); - var httpConnection = new KestrelConnection(mock.Object); + var httpConnection = new KestrelConnection(mock.Object, Mock.Of()); httpConnectionManager.AddConnection(0, httpConnection); diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 3553b8f6e377..8fa6a65fb1a3 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -236,10 +236,10 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var mockTransport = new Mock(); mockTransport - .Setup(transport => transport.AcceptAsync()) + .Setup(transport => transport.AcceptAsync(It.IsAny())) .Returns(new ValueTask((ConnectionContext)null)); mockTransport - .Setup(transport => transport.StopListeningAsync(It.IsAny())) + .Setup(transport => transport.StopAsync(It.IsAny())) .Returns(async () => await stop.WaitAsync()); var mockTransportFactory = new Mock(); @@ -266,7 +266,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() await Task.WhenAll(new[] { stopTask1, stopTask2, stopTask3 }).DefaultTimeout(); - mockTransport.Verify(transport => transport.StopListeningAsync(It.IsAny()), Times.Once); + mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); } //[Fact] diff --git a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs b/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs index 1f5b3773608d..cbaf74f5a66a 100644 --- a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs +++ b/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.IO.Pipelines; @@ -12,43 +12,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class PipeOptionsTests { - [Theory] - [InlineData(10, 10, 10)] - [InlineData(0, 1, 1)] - [InlineData(null, 0, 0)] - public void OutputPipeOptionsConfiguredCorrectly(long? maxResponseBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) - { - var serviceContext = new TestServiceContext(); - serviceContext.ServerOptions.Limits.MaxResponseBufferSize = maxResponseBufferSize; - serviceContext.Scheduler = PipeScheduler.ThreadPool; - - var mockScheduler = Mock.Of(); - var outputPipeOptions = ConnectionDispatcher.GetOutputPipeOptions(serviceContext, KestrelMemoryPool.Create(), readerScheduler: mockScheduler); - - Assert.Equal(expectedMaximumSizeLow, outputPipeOptions.ResumeWriterThreshold); - Assert.Equal(expectedMaximumSizeHigh, outputPipeOptions.PauseWriterThreshold); - Assert.Same(mockScheduler, outputPipeOptions.ReaderScheduler); - Assert.Same(serviceContext.Scheduler, outputPipeOptions.WriterScheduler); - } - - [Theory] - [InlineData(10, 10, 10)] - [InlineData(null, 0, 0)] - public void InputPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) - { - var serviceContext = new TestServiceContext(); - serviceContext.ServerOptions.Limits.MaxRequestBufferSize = maxRequestBufferSize; - serviceContext.Scheduler = PipeScheduler.ThreadPool; - - var mockScheduler = Mock.Of(); - var inputPipeOptions = ConnectionDispatcher.GetInputPipeOptions(serviceContext, KestrelMemoryPool.Create(), writerScheduler: mockScheduler); - - Assert.Equal(expectedMaximumSizeLow, inputPipeOptions.ResumeWriterThreshold); - Assert.Equal(expectedMaximumSizeHigh, inputPipeOptions.PauseWriterThreshold); - Assert.Same(serviceContext.Scheduler, inputPipeOptions.ReaderScheduler); - Assert.Same(mockScheduler, inputPipeOptions.WriterScheduler); - } - [Theory] [InlineData(10, 10, 10)] [InlineData(null, 0, 0)] From 178a5c2de38e95b4877522b18a9b7f3911a6e6d8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 May 2019 00:28:26 -0700 Subject: [PATCH 24/78] Make it all compile --- src/Servers/Kestrel/Core/src/KestrelServer.cs | 4 +- src/Servers/Kestrel/Core/src/ListenOptions.cs | 8 +- .../src/Internal/ListenerContext.cs | 2 +- .../test/LibuvConnectionTests.cs | 405 +++++------ .../test/LibuvOutputConsumerTests.cs | 40 +- .../Transport.Libuv/test/LibuvThreadTests.cs | 3 +- .../test/LibuvTransportTests.cs | 30 +- .../test/ListenerPrimaryTests.cs | 684 +++++++++--------- .../TestHelpers/MockConnectionDispatcher.cs | 31 - .../TestHelpers/TestLibuvTransportContext.cs | 1 - .../InMemoryTransportBenchmark.cs | 61 +- 11 files changed, 646 insertions(+), 623 deletions(-) delete mode 100644 src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/MockConnectionDispatcher.cs diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index e8f70a361d55..568ac24bd003 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -134,10 +134,10 @@ async Task OnBind(ListenOptions options) } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); - var transport = await _transportFactory.BindAsync(options.Endpoint).ConfigureAwait(false); + var transport = await _transportFactory.BindAsync(options.EndPoint).ConfigureAwait(false); // Update the endpoint - options.Endpoint = transport.EndPoint; + options.EndPoint = transport.EndPoint; _transports.Add(transport); connectionDispatcher.StartAcceptingConnections(transport); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 79c17441279d..b62ccc3c44b7 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -28,14 +28,14 @@ internal ListenOptions(IPEndPoint endPoint) { Type = ListenType.IPEndPoint; IPEndPoint = endPoint; - Endpoint = endPoint; + EndPoint = endPoint; } internal ListenOptions(string socketPath) { Type = ListenType.SocketPath; SocketPath = socketPath; - Endpoint = new UnixDomainSocketEndPoint(socketPath); + EndPoint = new UnixDomainSocketEndPoint(socketPath); } internal ListenOptions(ulong fileHandle) @@ -45,7 +45,7 @@ internal ListenOptions(ulong fileHandle) internal ListenOptions(ulong fileHandle, FileHandleType handleType) { - Endpoint = new FileHandleEndPoint(fileHandle, handleType); + EndPoint = new FileHandleEndPoint(fileHandle, handleType); Type = ListenType.FileHandle; FileHandle = fileHandle; @@ -96,7 +96,7 @@ public FileHandleType HandleType } } - public EndPoint Endpoint { get; set; } + public EndPoint EndPoint { get; set; } // IPEndPoint is mutable so port 0 can be updated to the bound port. /// diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index f9e35e85c3ce..6d9510907ea5 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -56,7 +56,7 @@ protected UvStreamHandle CreateAcceptSocket() } } - protected async Task HandleConnectionAsync(UvStreamHandle socket) + protected internal async Task HandleConnectionAsync(UvStreamHandle socket) { try { diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs index 79345760611a..05c8c8899d1c 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -19,12 +19,11 @@ public class LibuvConnectionTests [Fact] public async Task DoesNotEndConnectionOnZeroRead() { - var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); - var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); + var transportContext = new TestLibuvTransportContext(); + await using var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); - Task connectionTask = null; + try { await thread.StartAsync(); @@ -35,216 +34,214 @@ await thread.PostAsync(_ => Thread = thread }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - connectionTask = connection.Start(); + _ = listenerContext.HandleConnectionAsync(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); }, (object)null); - var readAwaitable = mockConnectionDispatcher.Input.Reader.ReadAsync(); - Assert.False(readAwaitable.IsCompleted); - } - finally - { - mockConnectionDispatcher.Input.Reader.Complete(); - mockConnectionDispatcher.Output.Writer.Complete(); - await connectionTask; - - await thread.StopAsync(TimeSpan.FromSeconds(5)); - } - } - - [Fact] - public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied() - { - var mockConnectionDispatcher = new MockConnectionDispatcher(); - var mockLibuv = new MockLibuv(); - var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - var thread = new LibuvThread(transport); - mockConnectionDispatcher.InputOptions = pool => - new PipeOptions( - pool: pool, - pauseWriterThreshold: 3, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.Inline, - useSynchronizationContext: false); - - // We don't set the output writer scheduler here since we want to run the callback inline - - mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - - - Task connectionTask = null; - try - { - await thread.StartAsync(); - - // Write enough to make sure back pressure will be applied - await thread.PostAsync(_ => - { - var listenerContext = new ListenerContext(transportContext) - { - Thread = thread - }; - var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - connectionTask = connection.Start(); - - mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); - mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); - - }, null); - - // Now assert that we removed the callback from libuv to stop reading - Assert.Null(mockLibuv.AllocCallback); - Assert.Null(mockLibuv.ReadCallback); - - // Now complete the output writer so that the connection closes - mockConnectionDispatcher.Output.Writer.Complete(); - - await connectionTask.DefaultTimeout(); - - // Assert that we don't try to start reading - Assert.Null(mockLibuv.AllocCallback); - Assert.Null(mockLibuv.ReadCallback); - } - finally - { - mockConnectionDispatcher.Input.Reader.Complete(); - mockConnectionDispatcher.Output.Writer.Complete(); - - await thread.StopAsync(TimeSpan.FromSeconds(5)); - } - } + await using var connection = await transport.AcceptAsync(); - [Fact] - public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied() - { - var mockConnectionDispatcher = new MockConnectionDispatcher(); - var mockLibuv = new MockLibuv(); - var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - var thread = new LibuvThread(transport); - var mockScheduler = new Mock(); - Action backPressure = null; - mockScheduler.Setup(m => m.Schedule(It.IsAny>(), It.IsAny())).Callback, object>((a, o) => - { - backPressure = () => a(o); - }); - mockConnectionDispatcher.InputOptions = pool => - new PipeOptions( - pool: pool, - pauseWriterThreshold: 3, - resumeWriterThreshold: 3, - writerScheduler: mockScheduler.Object, - readerScheduler: PipeScheduler.Inline, - useSynchronizationContext: false); - - mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - - Task connectionTask = null; - try - { - await thread.StartAsync(); - - // Write enough to make sure back pressure will be applied - await thread.PostAsync(_ => - { - var listenerContext = new ListenerContext(transportContext) - { - Thread = thread - }; - var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - connectionTask = connection.Start(); - - mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); - mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); - - }, null); - - // Now assert that we removed the callback from libuv to stop reading - Assert.Null(mockLibuv.AllocCallback); - Assert.Null(mockLibuv.ReadCallback); - - // Now release backpressure by reading the input - var result = await mockConnectionDispatcher.Input.Reader.ReadAsync(); - // Calling advance will call into our custom scheduler that captures the back pressure - // callback - mockConnectionDispatcher.Input.Reader.AdvanceTo(result.Buffer.End); - - // Cancel the current pending flush - mockConnectionDispatcher.Input.Writer.CancelPendingFlush(); - - // Now release the back pressure - await thread.PostAsync(a => a(), backPressure); - - // Assert that we don't try to start reading since the write was cancelled - Assert.Null(mockLibuv.AllocCallback); - Assert.Null(mockLibuv.ReadCallback); - - // Now complete the output writer and wait for the connection to close - mockConnectionDispatcher.Output.Writer.Complete(); - - await connectionTask.DefaultTimeout(); - - // Assert that we don't try to start reading - Assert.Null(mockLibuv.AllocCallback); - Assert.Null(mockLibuv.ReadCallback); + var readAwaitable = connection.Transport.Input.ReadAsync(); + Assert.False(readAwaitable.IsCompleted); } finally { - mockConnectionDispatcher.Input.Reader.Complete(); - mockConnectionDispatcher.Output.Writer.Complete(); - await thread.StopAsync(TimeSpan.FromSeconds(5)); } } - [Fact] - public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() - { - var mockConnectionDispatcher = new MockConnectionDispatcher(); - var mockLibuv = new MockLibuv(); - var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - var thread = new LibuvThread(transport); - - Task connectionTask = null; - try - { - await thread.StartAsync(); - await thread.PostAsync(_ => - { - var listenerContext = new ListenerContext(transportContext) - { - Thread = thread - }; - var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - connectionTask = connection.Start(); - - var ignored = new LibuvFunctions.uv_buf_t(); - mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); - }, (object)null); - - var readAwaitable = await mockConnectionDispatcher.Input.Reader.ReadAsync(); - Assert.True(readAwaitable.IsCompleted); - } - finally - { - mockConnectionDispatcher.Input.Reader.Complete(); - mockConnectionDispatcher.Output.Writer.Complete(); - await connectionTask; - - await thread.StopAsync(TimeSpan.FromSeconds(5)); - } - } + // TODO: Make it work + + //[Fact] + //public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied() + //{ + // var mockConnectionDispatcher = new MockConnectionDispatcher(); + // var mockLibuv = new MockLibuv(); + // var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; + // var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); + // var thread = new LibuvThread(transport); + // mockConnectionDispatcher.InputOptions = pool => + // new PipeOptions( + // pool: pool, + // pauseWriterThreshold: 3, + // readerScheduler: PipeScheduler.Inline, + // writerScheduler: PipeScheduler.Inline, + // useSynchronizationContext: false); + + // // We don't set the output writer scheduler here since we want to run the callback inline + + // mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); + + + // Task connectionTask = null; + // try + // { + // await thread.StartAsync(); + + // // Write enough to make sure back pressure will be applied + // await thread.PostAsync(_ => + // { + // var listenerContext = new ListenerContext(transportContext) + // { + // Thread = thread + // }; + // var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); + // var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); + // listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); + // connectionTask = connection.Start(); + + // mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); + // mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); + + // }, null); + + // // Now assert that we removed the callback from libuv to stop reading + // Assert.Null(mockLibuv.AllocCallback); + // Assert.Null(mockLibuv.ReadCallback); + + // // Now complete the output writer so that the connection closes + // mockConnectionDispatcher.Output.Writer.Complete(); + + // await connectionTask.DefaultTimeout(); + + // // Assert that we don't try to start reading + // Assert.Null(mockLibuv.AllocCallback); + // Assert.Null(mockLibuv.ReadCallback); + // } + // finally + // { + // mockConnectionDispatcher.Input.Reader.Complete(); + // mockConnectionDispatcher.Output.Writer.Complete(); + + // await thread.StopAsync(TimeSpan.FromSeconds(5)); + // } + //} + + //[Fact] + //public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied() + //{ + // var mockConnectionDispatcher = new MockConnectionDispatcher(); + // var mockLibuv = new MockLibuv(); + // var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; + // var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); + // var thread = new LibuvThread(transport); + // var mockScheduler = new Mock(); + // Action backPressure = null; + // mockScheduler.Setup(m => m.Schedule(It.IsAny>(), It.IsAny())).Callback, object>((a, o) => + // { + // backPressure = () => a(o); + // }); + // mockConnectionDispatcher.InputOptions = pool => + // new PipeOptions( + // pool: pool, + // pauseWriterThreshold: 3, + // resumeWriterThreshold: 3, + // writerScheduler: mockScheduler.Object, + // readerScheduler: PipeScheduler.Inline, + // useSynchronizationContext: false); + + // mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); + + // Task connectionTask = null; + // try + // { + // await thread.StartAsync(); + + // // Write enough to make sure back pressure will be applied + // await thread.PostAsync(_ => + // { + // var listenerContext = new ListenerContext(transportContext) + // { + // Thread = thread + // }; + // var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); + // var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); + // listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); + // connectionTask = connection.Start(); + + // mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); + // mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); + + // }, null); + + // // Now assert that we removed the callback from libuv to stop reading + // Assert.Null(mockLibuv.AllocCallback); + // Assert.Null(mockLibuv.ReadCallback); + + // // Now release backpressure by reading the input + // var result = await mockConnectionDispatcher.Input.Reader.ReadAsync(); + // // Calling advance will call into our custom scheduler that captures the back pressure + // // callback + // mockConnectionDispatcher.Input.Reader.AdvanceTo(result.Buffer.End); + + // // Cancel the current pending flush + // mockConnectionDispatcher.Input.Writer.CancelPendingFlush(); + + // // Now release the back pressure + // await thread.PostAsync(a => a(), backPressure); + + // // Assert that we don't try to start reading since the write was cancelled + // Assert.Null(mockLibuv.AllocCallback); + // Assert.Null(mockLibuv.ReadCallback); + + // // Now complete the output writer and wait for the connection to close + // mockConnectionDispatcher.Output.Writer.Complete(); + + // await connectionTask.DefaultTimeout(); + + // // Assert that we don't try to start reading + // Assert.Null(mockLibuv.AllocCallback); + // Assert.Null(mockLibuv.ReadCallback); + // } + // finally + // { + // mockConnectionDispatcher.Input.Reader.Complete(); + // mockConnectionDispatcher.Output.Writer.Complete(); + + // await thread.StopAsync(TimeSpan.FromSeconds(5)); + // } + //} + + //[Fact] + //public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() + //{ + // var mockConnectionDispatcher = new MockConnectionDispatcher(); + // var mockLibuv = new MockLibuv(); + // var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; + // var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); + // var thread = new LibuvThread(transport); + + // Task connectionTask = null; + // try + // { + // await thread.StartAsync(); + // await thread.PostAsync(_ => + // { + // var listenerContext = new ListenerContext(transportContext) + // { + // Thread = thread + // }; + // var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); + // var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); + // listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); + // connectionTask = connection.Start(); + + // var ignored = new LibuvFunctions.uv_buf_t(); + // mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); + // }, (object)null); + + // var readAwaitable = await mockConnectionDispatcher.Input.Reader.ReadAsync(); + // Assert.True(readAwaitable.IsCompleted); + // } + // finally + // { + // mockConnectionDispatcher.Input.Reader.Complete(); + // mockConnectionDispatcher.Output.Writer.Complete(); + // await connectionTask; + + // await thread.StopAsync(TimeSpan.FromSeconds(5)); + // } + //} } -} \ No newline at end of file +} diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs index 1616414d95c3..c4ef983c03be 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs @@ -45,7 +45,7 @@ public LibuvOutputConsumerTests() _memoryPool = KestrelMemoryPool.Create(); _mockLibuv = new MockLibuv(); - var libuvTransport = new LibuvConnectionListener(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0)); + var libuvTransport = new LibuvConnectionListener(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0).EndPoint); _libuvThread = new LibuvThread(libuvTransport, maxLoops: 1); _libuvThread.StartAsync().Wait(); } @@ -769,5 +769,43 @@ private async Task WriteOutputAsync(LibuvOutputConsumer consumer, PipeReader out outputReader.Complete(ex); } } + + // Work around the internal type conflict (multiple assemblies have internalized this type and that fails with IVT) + private class DuplexPipe : IDuplexPipe + { + public DuplexPipe(PipeReader reader, PipeWriter writer) + { + Input = reader; + Output = writer; + } + + public PipeReader Input { get; } + + public PipeWriter Output { get; } + + public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) + { + var input = new Pipe(inputOptions); + var output = new Pipe(outputOptions); + + var transportToApplication = new DuplexPipe(output.Reader, input.Writer); + var applicationToTransport = new DuplexPipe(input.Reader, output.Writer); + + return new DuplexPipePair(applicationToTransport, transportToApplication); + } + + // This class exists to work around issues with value tuple on .NET Framework + public readonly struct DuplexPipePair + { + public IDuplexPipe Transport { get; } + public IDuplexPipe Application { get; } + + public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application) + { + Transport = transport; + Application = application; + } + } + } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs index 9c1cf34e4214..df1e1dfde24e 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs @@ -14,9 +14,8 @@ public class LibuvThreadTests [Fact] public async Task LibuvThreadDoesNotThrowIfPostingWorkAfterDispose() { - var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); - var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; + var transportContext = new TestLibuvTransportContext(); var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); var ranOne = false; diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 4f58b1677a22..5594613332fa 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -38,8 +38,7 @@ public class LibuvTransportTests public async Task TransportCanBindAndStop() { var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))); + var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); // The transport can no longer start threads without binding to an endpoint. await transport.BindAsync(); @@ -50,7 +49,7 @@ public async Task TransportCanBindAndStop() public async Task TransportCanBindUnbindAndStop() { var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))); + var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); await transport.UnbindAsync(); @@ -64,16 +63,15 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) var serviceContext = new TestServiceContext(); listenOptions.UseHttpServer(listenOptions.ConnectionAdapters, serviceContext, new DummyApplication(TestApp.EchoApp), HttpProtocols.Http1); - var transportContext = new TestLibuvTransportContext - { - ConnectionDispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()) - }; - - var transport = new LibuvConnectionListener(transportContext, listenOptions); + var transportContext = new TestLibuvTransportContext(); + var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); await transport.BindAsync(); - using (var socket = TestConnection.CreateConnectedLoopbackSocket(listenOptions.IPEndPoint.Port)) + var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); + dispatcher.StartAcceptingConnections(transport); + + using (var socket = TestConnection.CreateConnectedLoopbackSocket(((IPEndPoint)listenOptions.EndPoint).Port)) { var data = "Hello World"; socket.Send(Encoding.ASCII.GetBytes($"POST / HTTP/1.0\r\nContent-Length: 11\r\n\r\n{data}")); @@ -85,6 +83,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) } } + serviceContext.ConnectionManager.TryStartDrainingConnection(); Assert.True(await serviceContext.ConnectionManager.CloseAllConnectionsAsync(new CancellationTokenSource(TestConstants.DefaultTimeout).Token)); await transport.UnbindAsync(); await transport.StopAsync(); @@ -106,21 +105,22 @@ public async Task OneToTenThreads(int threadCount) var transportContext = new TestLibuvTransportContext { - ConnectionDispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()), Options = new LibuvTransportOptions { ThreadCount = threadCount } }; - var transport = new LibuvConnectionListener(transportContext, listenOptions); - + var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); await transport.BindAsync(); + var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); + dispatcher.StartAcceptingConnections(transport); + 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}/"); + var requestTask = client.GetStringAsync($"http://127.0.0.1:{((IPEndPoint)listenOptions.EndPoint).Port}/"); requestTasks.Add(requestTask); } @@ -132,6 +132,8 @@ public async Task OneToTenThreads(int threadCount) await transport.UnbindAsync(); + serviceContext.ConnectionManager.TryStartDrainingConnection(); + if (!await serviceContext.ConnectionManager.CloseAllConnectionsAsync(default).ConfigureAwait(false)) { await serviceContext.ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index 15a73a25b92c..f5bb5141c4cc 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -1,342 +1,342 @@ -// 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.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Connections; -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.Libuv.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; -using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Logging; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests -{ - public class ListenerPrimaryTests - { - [Fact] - public async Task ConnectionsGetRoundRobinedToSecondaryListeners() - { - var libuv = new LibuvFunctions(); - - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - - var serviceContextPrimary = new TestServiceContext(); - var transportContextPrimary = new TestLibuvTransportContext(); - var builderPrimary = new ConnectionBuilder(); - builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); - transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - - var serviceContextSecondary = new TestServiceContext(); - var builderSecondary = new ConnectionBuilder(); - builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); - var transportContextSecondary = new TestLibuvTransportContext(); - transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - - var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - - var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); - var pipeMessage = Guid.NewGuid().ToByteArray(); - - // Start primary listener - var libuvThreadPrimary = new LibuvThread(libuvTransport); - await libuvThreadPrimary.StartAsync(); - var listenerPrimary = new ListenerPrimary(transportContextPrimary); - await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - var address = GetUri(listenOptions); - - // Until a secondary listener is added, TCP connections get dispatched directly - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - var listenerCount = listenerPrimary.UvPipeCount; - // Add secondary listener - var libuvThreadSecondary = new LibuvThread(libuvTransport); - await libuvThreadSecondary.StartAsync(); - var listenerSecondary = new ListenerSecondary(transportContextSecondary); - await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); - - var maxWait = Task.Delay(TestConstants.DefaultTimeout); - // wait for ListenerPrimary.ReadCallback to add the secondary pipe - while (listenerPrimary.UvPipeCount == listenerCount) - { - var completed = await Task.WhenAny(maxWait, Task.Delay(100)); - if (ReferenceEquals(completed, maxWait)) - { - throw new TimeoutException("Timed out waiting for secondary listener to become available"); - } - } - - // Once a secondary listener is added, TCP connections start getting dispatched to it - await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); - - // TCP connections will still get round-robined to the primary listener - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - await listenerSecondary.DisposeAsync(); - await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - - await listenerPrimary.DisposeAsync(); - await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - } - - // https://github.com/aspnet/KestrelHttpServer/issues/1182 - [Fact] - public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() - { - var libuv = new LibuvFunctions(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - var logger = new TestApplicationErrorLogger(); - - var serviceContextPrimary = new TestServiceContext(); - var builderPrimary = new ConnectionBuilder(); - builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); - var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; - transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - - var serviceContextSecondary = new TestServiceContext - { - DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, - ServerOptions = serviceContextPrimary.ServerOptions, - Scheduler = serviceContextPrimary.Scheduler, - HttpParser = serviceContextPrimary.HttpParser, - }; - var builderSecondary = new ConnectionBuilder(); - builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); - var transportContextSecondary = new TestLibuvTransportContext(); - transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - - var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - - var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); - var pipeMessage = Guid.NewGuid().ToByteArray(); - - // Start primary listener - var libuvThreadPrimary = new LibuvThread(libuvTransport); - await libuvThreadPrimary.StartAsync(); - var listenerPrimary = new ListenerPrimary(transportContextPrimary); - await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - var address = GetUri(listenOptions); - - // Add secondary listener - var libuvThreadSecondary = new LibuvThread(libuvTransport); - await libuvThreadSecondary.StartAsync(); - var listenerSecondary = new ListenerSecondary(transportContextSecondary); - await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); - - // TCP Connections get round-robined - await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - // Create a pipe connection and keep it open without sending any data - var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger()); - var pipe = new UvPipeHandle(connectionTrace); - - libuvThreadPrimary.Post(_ => - { - var connectReq = new UvConnectRequest(connectionTrace); - - pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle); - connectReq.Init(libuvThreadPrimary); - - connectReq.Connect( - pipe, - pipeName, - (req, status, ex, __) => - { - req.Dispose(); - - if (ex != null) - { - connectTcs.SetException(ex); - } - else - { - connectTcs.SetResult(null); - } - }, - null); - }, (object)null); - - await connectTcs.Task; - - // TCP connections will still get round-robined between only the two listeners - Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - - await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null); - - // Wait up to 10 seconds for error to be logged - for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) - { - await Task.Delay(100); - } - - // Same for after the non-listener pipe connection is closed - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - await listenerSecondary.DisposeAsync(); - await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - - await listenerPrimary.DisposeAsync(); - await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - - Assert.Equal(0, logger.TotalErrorsLogged); - - var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); - Assert.Equal(LogLevel.Debug, logMessage.LogLevel); - } - - - [Fact] - public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() - { - var libuv = new LibuvFunctions(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - - var logger = new TestApplicationErrorLogger(); - - var serviceContextPrimary = new TestServiceContext(); - var builderPrimary = new ConnectionBuilder(); - builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); - var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; - transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - - var serviceContextSecondary = new TestServiceContext - { - DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, - ServerOptions = serviceContextPrimary.ServerOptions, - Scheduler = serviceContextPrimary.Scheduler, - HttpParser = serviceContextPrimary.HttpParser, - }; - var builderSecondary = new ConnectionBuilder(); - builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); - var transportContextSecondary = new TestLibuvTransportContext(); - transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - - var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - - var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); - var pipeMessage = Guid.NewGuid().ToByteArray(); - - // Start primary listener - var libuvThreadPrimary = new LibuvThread(libuvTransport); - await libuvThreadPrimary.StartAsync(); - var listenerPrimary = new ListenerPrimary(transportContextPrimary); - await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - var address = GetUri(listenOptions); - - // Add secondary listener with wrong pipe message - var libuvThreadSecondary = new LibuvThread(libuvTransport); - await libuvThreadSecondary.StartAsync(); - var listenerSecondary = new ListenerSecondary(transportContextSecondary); - await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary); - - // Wait up to 10 seconds for error to be logged - for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) - { - await Task.Delay(100); - } - - // TCP Connections don't get round-robined - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - await listenerSecondary.DisposeAsync(); - await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - - await listenerPrimary.DisposeAsync(); - await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - - Assert.Equal(1, logger.TotalErrorsLogged); - var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error); - Assert.IsType(errorMessage.Exception); - Assert.Contains("Bad data", errorMessage.Exception.ToString()); - } - - private static async Task AssertResponseEventually( - Uri address, - string expected, - string[] allowed = null, - int maxRetries = 100, - int retryDelay = 100) - { - for (var i = 0; i < maxRetries; i++) - { - var response = await HttpClientSlim.GetStringAsync(address); - if (response == expected) - { - return; - } - - if (allowed != null) - { - Assert.Contains(response, allowed); - } - - await Task.Delay(retryDelay); - } - - Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries."); - } - - private static Uri GetUri(ListenOptions options) - { - if (options.Type != ListenType.IPEndPoint) - { - throw new InvalidOperationException($"Could not determine a proper URI for options with Type {options.Type}"); - } - - var scheme = options.ConnectionAdapters.Any(f => f.IsHttps) - ? "https" - : "http"; - - return new Uri($"{scheme}://{options.IPEndPoint}"); - } - - private class ConnectionBuilder : IConnectionBuilder - { - private readonly List> _components = new List>(); - - public IServiceProvider ApplicationServices { get; set; } - - public IConnectionBuilder Use(Func middleware) - { - _components.Add(middleware); - return this; - } - - public ConnectionDelegate Build() - { - ConnectionDelegate app = context => - { - return Task.CompletedTask; - }; - - for (int i = _components.Count - 1; i >= 0; i--) - { - var component = _components[i]; - app = component(app); - } - - return app; - } - } - } -} +//// 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.Generic; +//using System.IO; +//using System.Linq; +//using System.Net; +//using System.Threading.Tasks; +//using Microsoft.AspNetCore.Http; +//using Microsoft.AspNetCore.Connections; +//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.Libuv.Internal; +//using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; +//using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; +//using Microsoft.AspNetCore.Testing; +//using Microsoft.Extensions.Logging; +//using Xunit; + +//namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests +//{ +// public class ListenerPrimaryTests +// { +// [Fact] +// public async Task ConnectionsGetRoundRobinedToSecondaryListeners() +// { +// var libuv = new LibuvFunctions(); + +// var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + +// var serviceContextPrimary = new TestServiceContext(); +// var transportContextPrimary = new TestLibuvTransportContext(); +// var builderPrimary = new ConnectionBuilder(); +// builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); +// transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); + +// var serviceContextSecondary = new TestServiceContext(); +// var builderSecondary = new ConnectionBuilder(); +// builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); +// var transportContextSecondary = new TestLibuvTransportContext(); +// transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); + +// var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); + +// var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); +// var pipeMessage = Guid.NewGuid().ToByteArray(); + +// // Start primary listener +// var libuvThreadPrimary = new LibuvThread(libuvTransport); +// await libuvThreadPrimary.StartAsync(); +// var listenerPrimary = new ListenerPrimary(transportContextPrimary); +// await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); +// var address = GetUri(listenOptions); + +// // Until a secondary listener is added, TCP connections get dispatched directly +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + +// var listenerCount = listenerPrimary.UvPipeCount; +// // Add secondary listener +// var libuvThreadSecondary = new LibuvThread(libuvTransport); +// await libuvThreadSecondary.StartAsync(); +// var listenerSecondary = new ListenerSecondary(transportContextSecondary); +// await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); + +// var maxWait = Task.Delay(TestConstants.DefaultTimeout); +// // wait for ListenerPrimary.ReadCallback to add the secondary pipe +// while (listenerPrimary.UvPipeCount == listenerCount) +// { +// var completed = await Task.WhenAny(maxWait, Task.Delay(100)); +// if (ReferenceEquals(completed, maxWait)) +// { +// throw new TimeoutException("Timed out waiting for secondary listener to become available"); +// } +// } + +// // Once a secondary listener is added, TCP connections start getting dispatched to it +// await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); + +// // TCP connections will still get round-robined to the primary listener +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + +// await listenerSecondary.DisposeAsync(); +// await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + +// await listenerPrimary.DisposeAsync(); +// await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); +// } + +// // https://github.com/aspnet/KestrelHttpServer/issues/1182 +// [Fact] +// public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() +// { +// var libuv = new LibuvFunctions(); +// var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); +// var logger = new TestApplicationErrorLogger(); + +// var serviceContextPrimary = new TestServiceContext(); +// var builderPrimary = new ConnectionBuilder(); +// builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); +// var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; +// transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); + +// var serviceContextSecondary = new TestServiceContext +// { +// DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, +// ServerOptions = serviceContextPrimary.ServerOptions, +// Scheduler = serviceContextPrimary.Scheduler, +// HttpParser = serviceContextPrimary.HttpParser, +// }; +// var builderSecondary = new ConnectionBuilder(); +// builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); +// var transportContextSecondary = new TestLibuvTransportContext(); +// transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); + +// var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); + +// var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); +// var pipeMessage = Guid.NewGuid().ToByteArray(); + +// // Start primary listener +// var libuvThreadPrimary = new LibuvThread(libuvTransport); +// await libuvThreadPrimary.StartAsync(); +// var listenerPrimary = new ListenerPrimary(transportContextPrimary); +// await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); +// var address = GetUri(listenOptions); + +// // Add secondary listener +// var libuvThreadSecondary = new LibuvThread(libuvTransport); +// await libuvThreadSecondary.StartAsync(); +// var listenerSecondary = new ListenerSecondary(transportContextSecondary); +// await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); + +// // TCP Connections get round-robined +// await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + +// // Create a pipe connection and keep it open without sending any data +// var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); +// var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger()); +// var pipe = new UvPipeHandle(connectionTrace); + +// libuvThreadPrimary.Post(_ => +// { +// var connectReq = new UvConnectRequest(connectionTrace); + +// pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle); +// connectReq.Init(libuvThreadPrimary); + +// connectReq.Connect( +// pipe, +// pipeName, +// (req, status, ex, __) => +// { +// req.Dispose(); + +// if (ex != null) +// { +// connectTcs.SetException(ex); +// } +// else +// { +// connectTcs.SetResult(null); +// } +// }, +// null); +// }, (object)null); + +// await connectTcs.Task; + +// // TCP connections will still get round-robined between only the two listeners +// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); + +// await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null); + +// // Wait up to 10 seconds for error to be logged +// for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) +// { +// await Task.Delay(100); +// } + +// // Same for after the non-listener pipe connection is closed +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + +// await listenerSecondary.DisposeAsync(); +// await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + +// await listenerPrimary.DisposeAsync(); +// await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + +// Assert.Equal(0, logger.TotalErrorsLogged); + +// var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); +// Assert.Equal(LogLevel.Debug, logMessage.LogLevel); +// } + + +// [Fact] +// public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() +// { +// var libuv = new LibuvFunctions(); +// var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + +// var logger = new TestApplicationErrorLogger(); + +// var serviceContextPrimary = new TestServiceContext(); +// var builderPrimary = new ConnectionBuilder(); +// builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); +// var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; +// transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); + +// var serviceContextSecondary = new TestServiceContext +// { +// DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, +// ServerOptions = serviceContextPrimary.ServerOptions, +// Scheduler = serviceContextPrimary.Scheduler, +// HttpParser = serviceContextPrimary.HttpParser, +// }; +// var builderSecondary = new ConnectionBuilder(); +// builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); +// var transportContextSecondary = new TestLibuvTransportContext(); +// transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); + +// var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); + +// var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); +// var pipeMessage = Guid.NewGuid().ToByteArray(); + +// // Start primary listener +// var libuvThreadPrimary = new LibuvThread(libuvTransport); +// await libuvThreadPrimary.StartAsync(); +// var listenerPrimary = new ListenerPrimary(transportContextPrimary); +// await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); +// var address = GetUri(listenOptions); + +// // Add secondary listener with wrong pipe message +// var libuvThreadSecondary = new LibuvThread(libuvTransport); +// await libuvThreadSecondary.StartAsync(); +// var listenerSecondary = new ListenerSecondary(transportContextSecondary); +// await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary); + +// // Wait up to 10 seconds for error to be logged +// for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) +// { +// await Task.Delay(100); +// } + +// // TCP Connections don't get round-robined +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); +// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + +// await listenerSecondary.DisposeAsync(); +// await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + +// await listenerPrimary.DisposeAsync(); +// await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + +// Assert.Equal(1, logger.TotalErrorsLogged); +// var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error); +// Assert.IsType(errorMessage.Exception); +// Assert.Contains("Bad data", errorMessage.Exception.ToString()); +// } + +// private static async Task AssertResponseEventually( +// Uri address, +// string expected, +// string[] allowed = null, +// int maxRetries = 100, +// int retryDelay = 100) +// { +// for (var i = 0; i < maxRetries; i++) +// { +// var response = await HttpClientSlim.GetStringAsync(address); +// if (response == expected) +// { +// return; +// } + +// if (allowed != null) +// { +// Assert.Contains(response, allowed); +// } + +// await Task.Delay(retryDelay); +// } + +// Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries."); +// } + +// private static Uri GetUri(ListenOptions options) +// { +// if (options.Type != ListenType.IPEndPoint) +// { +// throw new InvalidOperationException($"Could not determine a proper URI for options with Type {options.Type}"); +// } + +// var scheme = options.ConnectionAdapters.Any(f => f.IsHttps) +// ? "https" +// : "http"; + +// return new Uri($"{scheme}://{options.IPEndPoint}"); +// } + +// private class ConnectionBuilder : IConnectionBuilder +// { +// private readonly List> _components = new List>(); + +// public IServiceProvider ApplicationServices { get; set; } + +// public IConnectionBuilder Use(Func middleware) +// { +// _components.Add(middleware); +// return this; +// } + +// public ConnectionDelegate Build() +// { +// ConnectionDelegate app = context => +// { +// return Task.CompletedTask; +// }; + +// for (int i = _components.Count - 1; i >= 0; i--) +// { +// var component = _components[i]; +// app = component(app); +// } + +// return app; +// } +// } +// } +//} diff --git a/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/MockConnectionDispatcher.cs b/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/MockConnectionDispatcher.cs deleted file mode 100644 index 01e0c049d032..000000000000 --- a/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/MockConnectionDispatcher.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.IO.Pipelines; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers -{ - public class MockConnectionDispatcher : IConnectionDispatcher - { - public Func, PipeOptions> InputOptions { get; set; } = pool => new PipeOptions(pool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - public Func, PipeOptions> OutputOptions { get; set; } = pool => new PipeOptions(pool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - - public Task OnConnection(TransportConnection connection) - { - Input = new Pipe(InputOptions(connection.MemoryPool)); - Output = new Pipe(OutputOptions(connection.MemoryPool)); - - connection.Transport = new DuplexPipe(Input.Reader, Output.Writer); - connection.Application = new DuplexPipe(Output.Reader, Input.Writer); - - return Task.CompletedTask; - } - - public Pipe Input { get; private set; } - public Pipe Output { get; private set; } - } -} diff --git a/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs b/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs index 155c31b2ebcf..1bccf57d32e2 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs @@ -13,7 +13,6 @@ public TestLibuvTransportContext() var logger = new TestApplicationErrorLogger(); AppLifetime = new LifetimeNotImplemented(); - ConnectionDispatcher = new MockConnectionDispatcher(); Log = new LibuvTrace(logger); Options = new LibuvTransportOptions { ThreadCount = 1 }; } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs index d5421ab448af..347134c6bbbd 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs @@ -6,10 +6,13 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; +using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; @@ -45,7 +48,7 @@ public void GlobalSetupPlaintext() .UseKestrel() // Bind to a single non-HTTPS endpoint .UseUrls("http://127.0.0.1:5000") - .ConfigureServices(services => services.AddSingleton(transportFactory)) + .ConfigureServices(services => services.AddSingleton(transportFactory)) .Configure(app => app.UseMiddleware()) .Build(); @@ -94,21 +97,21 @@ public async Task PlaintextPipelined() await _connection.ReadResponseAsync(_plaintextPipelinedExpectedResponse.Length); } - internal class InMemoryTransportFactory : ITransportFactory + internal class InMemoryTransportFactory : IConnectionListenerFactory { private readonly int _connectionsPerEndPoint; - private readonly Dictionary> _connections = - new Dictionary>(); + private readonly Dictionary> _connections = + new Dictionary>(); - public IReadOnlyDictionary> Connections => _connections; + public IReadOnlyDictionary> Connections => _connections; public InMemoryTransportFactory(int connectionsPerEndPoint) { _connectionsPerEndPoint = connectionsPerEndPoint; } - public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher handler) + public ValueTask BindAsync(EndPoint endpoint) { var connections = new InMemoryConnection[_connectionsPerEndPoint]; for (var i = 0; i < _connectionsPerEndPoint; i++) @@ -116,46 +119,62 @@ public ITransport Create(IEndPointInformation endPointInformation, IConnectionDi connections[i] = new InMemoryConnection(); } - _connections.Add(endPointInformation, connections); + _connections.Add(endpoint, connections); - return new InMemoryTransport(handler, connections); + return new ValueTask(new InMemoryTransport(endpoint, connections)); } } - public class InMemoryTransport : ITransport + public class InMemoryTransport : IConnectionListener { - private readonly IConnectionDispatcher _dispatcher; private readonly IReadOnlyList _connections; + private readonly TaskCompletionSource _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private int _acceptedConnections; - public InMemoryTransport(IConnectionDispatcher dispatcher, IReadOnlyList connections) + public InMemoryTransport(EndPoint endpoint, IReadOnlyList connections) { - _dispatcher = dispatcher; + EndPoint = endpoint; _connections = connections; } - public Task BindAsync() + public EndPoint EndPoint { get; } + + public ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - foreach (var connection in _connections) + if (_acceptedConnections < _connections.Count) { - _dispatcher.OnConnection(connection); + return new ValueTask(_connections[_acceptedConnections++]); } - - return Task.CompletedTask; + return new ValueTask(_tcs.Task); } - public Task StopAsync() + public ValueTask DisposeAsync() { - return Task.CompletedTask; + _tcs.TrySetResult(null); + return default; } - public Task UnbindAsync() + public ValueTask StopAsync(CancellationToken cancellationToken = default) { - return Task.CompletedTask; + _tcs.TrySetResult(null); + return default; } } public class InMemoryConnection : TransportConnection { + public InMemoryConnection() + { + var inputOptions = new PipeOptions(useSynchronizationContext: false); + var outputOptions = new PipeOptions(useSynchronizationContext: false); + + var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); + + // Set the transport and connection id + Transport = pair.Transport; + Application = pair.Application; + } + public ValueTask SendRequestAsync(byte[] request) { return Input.WriteAsync(request); From e98e53445a45f0bd5b385aabfbc9c7a43bb2c896 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 May 2019 21:25:10 -0700 Subject: [PATCH 25/78] Use the scheduler in the InMemoryTransport --- .../TestTransport/InMemoryTransportConnection.cs | 4 ++-- .../test/InMemory.FunctionalTests/TestTransport/TestServer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index f3281075079b..4f8dfd6f9525 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -20,7 +20,7 @@ internal class InMemoryTransportConnection : TransportConnection private readonly ILogger _logger; private bool _isClosed; - public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger) + public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger, PipeScheduler scheduler = null) { MemoryPool = memoryPool; _logger = logger; @@ -28,7 +28,7 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger) LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 0); - var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(memoryPool), new PipeOptions(memoryPool)); + var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(memoryPool, readerScheduler: scheduler), new PipeOptions(memoryPool, writerScheduler: scheduler)); Application = pair.Application; Transport = pair.Transport; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs index 783c769f2c04..c7bab027d888 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs @@ -102,7 +102,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action Date: Thu, 23 May 2019 22:02:12 -0700 Subject: [PATCH 26/78] Use options --- .../Http2/Http2TestBase.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 4c17f3711b99..a11b9e3d3869 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -429,8 +429,8 @@ protected void CreateConnection() // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make // it easier to verify request frames are processed correctly immediately after sending the them. - var inputPipeOptions = new PipeOptions(_memoryPool, PipeScheduler.ThreadPool); - var outputPipeOptions = new PipeOptions(_memoryPool, PipeScheduler.ThreadPool); + var inputPipeOptions = GetInputPipeOptions(_serviceContext, _memoryPool, PipeScheduler.ThreadPool); + var outputPipeOptions = GetOutputPipeOptions(_serviceContext, _memoryPool, PipeScheduler.ThreadPool); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); @@ -1248,6 +1248,41 @@ protected void AdvanceClock(TimeSpan timeSpan) _timeoutControl.Tick(clock.UtcNow); } + private static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler writerScheduler) => new PipeOptions + ( + pool: memoryPool, + readerScheduler: serviceContext.Scheduler, + writerScheduler: writerScheduler, + pauseWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, + resumeWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, + useSynchronizationContext: false, + minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + ); + + private static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler readerScheduler) => new PipeOptions + ( + pool: memoryPool, + readerScheduler: readerScheduler, + writerScheduler: serviceContext.Scheduler, + pauseWriterThreshold: GetOutputResponseBufferSize(serviceContext), + resumeWriterThreshold: GetOutputResponseBufferSize(serviceContext), + useSynchronizationContext: false, + minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + ); + + private static long GetOutputResponseBufferSize(ServiceContext serviceContext) + { + var bufferSize = serviceContext.ServerOptions.Limits.MaxResponseBufferSize; + if (bufferSize == 0) + { + // 0 = no buffering so we need to configure the pipe so the writer waits on the reader directly + return 1; + } + + // null means that we have no back pressure + return bufferSize ?? 0; + } + internal class Http2FrameWithPayload : Http2Frame { public Http2FrameWithPayload() : base() From 4d85641c200c91168ae44e07b3538659fb2797c9 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 May 2019 22:53:55 -0700 Subject: [PATCH 27/78] Fixed port binding --- src/Servers/Kestrel/Core/src/KestrelServer.cs | 1 + src/Servers/Kestrel/Core/src/ListenOptions.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 568ac24bd003..25acb9379975 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -138,6 +138,7 @@ async Task OnBind(ListenOptions options) // Update the endpoint options.EndPoint = transport.EndPoint; + _transports.Add(transport); connectionDispatcher.StartAcceptingConnections(transport); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index b62ccc3c44b7..f96a834a0d6c 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -96,7 +96,7 @@ public FileHandleType HandleType } } - public EndPoint EndPoint { get; set; } + internal EndPoint EndPoint { get; set; } // IPEndPoint is mutable so port 0 can be updated to the bound port. /// @@ -161,13 +161,13 @@ internal virtual string GetDisplayName() ? "https" : "http"; - switch (Type) + switch (EndPoint) { - case ListenType.IPEndPoint: - return $"{scheme}://{IPEndPoint}"; - case ListenType.SocketPath: - return $"{scheme}://unix:{SocketPath}"; - case ListenType.FileHandle: + case IPEndPoint _: + return $"{scheme}://{EndPoint}"; + case UnixDomainSocketEndPoint _: + return $"{scheme}://unix:{EndPoint}"; + case FileHandleEndPoint _: return $"{scheme}://"; default: throw new InvalidOperationException(); From cb665fc63fbc9645dd5008415716a704c111c70a Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 May 2019 23:00:10 -0700 Subject: [PATCH 28/78] Made ListenOptinos.IPEndPoint readonly --- src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs | 4 ++-- src/Servers/Kestrel/Core/src/ListenOptions.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs b/src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs index e2319b4977c7..555dc4af1df9 100644 --- a/src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -29,7 +29,7 @@ internal override async Task BindAsync(AddressBindContext context) context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port)); // for machines that do not support IPv6 - IPEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port); + EndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port); await base.BindAsync(context).ConfigureAwait(false); } } diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index f96a834a0d6c..b3f4a72d8a5a 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -103,7 +103,7 @@ public FileHandleType HandleType /// The to bind to. /// Only set if the is . /// - public IPEndPoint IPEndPoint { get; set; } + public IPEndPoint IPEndPoint { get; } /// /// The absolute path to a Unix domain socket to bind to. From 7e8c1fef75ada16581e6f184d0c1ed0d922a8013 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 May 2019 23:53:40 -0700 Subject: [PATCH 29/78] Fix libuv binding --- .../src/Internal/LibuvConnectionListener.cs | 45 +++++++++++-------- .../Transport.Libuv/src/Internal/Listener.cs | 3 ++ .../src/Internal/ListenerContext.cs | 14 ++++-- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 942be2863b42..01de2980e53a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -92,12 +92,7 @@ public async Task BindAsync() var listener = new Listener(TransportContext); _listeners.Add(listener); await listener.StartAsync(EndPoint, Threads[0]).ConfigureAwait(false); - - if (listener.ListenSocket is UvTcpHandle handle) - { - // If requested port was "0", replace with assigned dynamic port. - EndPoint = handle.GetSockIPEndPoint(); - } + EndPoint = listener.EndPoint; } else { @@ -107,6 +102,7 @@ public async Task BindAsync() var listenerPrimary = new ListenerPrimary(TransportContext); _listeners.Add(listenerPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, EndPoint, Threads[0]).ConfigureAwait(false); + EndPoint = listenerPrimary.EndPoint; foreach (var thread in Threads.Skip(1)) { @@ -131,23 +127,37 @@ public async Task BindAsync() private async IAsyncEnumerator AcceptConnections() { - var slots = new Task<(LibuvConnection Connection, int Slot)>[_listeners.Count]; + var slots = new Task<(LibuvConnection, int)>[_listeners.Count]; + // This is the task we'll put in the slot when each listening completes. It'll prevent + // us from having to shrink the array. We'll just loop while there are active slots. + var incompleteTask = new TaskCompletionSource<(LibuvConnection, int)>().Task; + + var remainingSlots = slots.Length; // Issue parallel accepts on all listeners - for (int i = 0; i < slots.Length; i++) + for (int i = 0; i < remainingSlots; i++) { slots[i] = AcceptAsync(_listeners[i], i); } - while (true) + while (remainingSlots > 0) { // Calling GetAwaiter().GetResult() is safe because we know the task is completed (LibuvConnection connection, int slot) = (await Task.WhenAny(slots)).GetAwaiter().GetResult(); - // Fill that slot with another accept and yield the connection - slots[slot] = AcceptAsync(_listeners[slot], slot); + // If the connection is null then the listener was closed + if (connection == null) + { + remainingSlots--; + slots[slot] = incompleteTask; + } + else + { + // Fill that slot with another accept and yield the connection + slots[slot] = AcceptAsync(_listeners[slot], slot); - yield return connection; + yield return connection; + } } static async Task<(LibuvConnection, int)> AcceptAsync(ListenerContext listener, int slot) @@ -187,16 +197,13 @@ public async ValueTask AcceptAsync(CancellationToken cancella public async ValueTask StopAsync(CancellationToken cancellationToken) { await UnbindAsync().ConfigureAwait(false); - - if (_acceptEnumerator != null) - { - await _acceptEnumerator.DisposeAsync(); - } } - public ValueTask DisposeAsync() + public async ValueTask DisposeAsync() { - return StopAsync(default); + await UnbindAsync(); + + await StopAsync(); } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 6aa8cb72b5c5..1b9d88c975c8 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -70,6 +70,9 @@ private UvTcpHandle ListenTcp(bool useFileHandle) if (!useFileHandle) { socket.Bind((IPEndPoint)EndPoint); + + // If requested port was "0", replace with assigned dynamic port. + EndPoint = socket.GetSockIPEndPoint(); } else { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index 6d9510907ea5..c835318147f2 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -33,9 +33,17 @@ public ListenerContext(LibuvTransportContext transportContext) public LibuvThread Thread { get; set; } - public ValueTask AcceptAsync(CancellationToken cancellationToken = default) + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - return _acceptQueue.Reader.ReadAsync(cancellationToken); + while (await _acceptQueue.Reader.WaitToReadAsync()) + { + while (_acceptQueue.Reader.TryRead(out var item)) + { + return item; + } + } + + return null; } /// @@ -126,7 +134,7 @@ private UvPipeHandle AcceptPipe() protected void StopAcceptingConnections() { - _acceptQueue.Writer.Complete(); + _acceptQueue.Writer.TryComplete(); } private UvStreamHandle AcceptHandle() From cd12e5efac9e5b031ac4c5d9f9f219186b85c0ee Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 00:01:40 -0700 Subject: [PATCH 30/78] Make IPEndPoint work on ListenOptions --- src/Servers/Kestrel/Core/src/ListenOptions.cs | 5 ++--- .../Kestrel/Transport.Libuv/test/LibuvTransportTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index b3f4a72d8a5a..9f9b650417f2 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -27,7 +27,6 @@ public class ListenOptions : IConnectionBuilder internal ListenOptions(IPEndPoint endPoint) { Type = ListenType.IPEndPoint; - IPEndPoint = endPoint; EndPoint = endPoint; } @@ -103,7 +102,7 @@ public FileHandleType HandleType /// The to bind to. /// Only set if the is . /// - public IPEndPoint IPEndPoint { get; } + public IPEndPoint IPEndPoint => EndPoint as IPEndPoint; /// /// The absolute path to a Unix domain socket to bind to. @@ -164,7 +163,7 @@ internal virtual string GetDisplayName() switch (EndPoint) { case IPEndPoint _: - return $"{scheme}://{EndPoint}"; + return $"{scheme}://{IPEndPoint}"; case UnixDomainSocketEndPoint _: return $"{scheme}://unix:{EndPoint}"; case FileHandleEndPoint _: diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 5594613332fa..91d9353ccf35 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -71,7 +71,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); dispatcher.StartAcceptingConnections(transport); - using (var socket = TestConnection.CreateConnectedLoopbackSocket(((IPEndPoint)listenOptions.EndPoint).Port)) + using (var socket = TestConnection.CreateConnectedLoopbackSocket(listenOptions.IPEndPoint.Port)) { var data = "Hello World"; socket.Send(Encoding.ASCII.GetBytes($"POST / HTTP/1.0\r\nContent-Length: 11\r\n\r\n{data}")); @@ -120,7 +120,7 @@ public async Task OneToTenThreads(int threadCount) var requestTasks = new List>(); for (int i = 0; i < 20; i++) { - var requestTask = client.GetStringAsync($"http://127.0.0.1:{((IPEndPoint)listenOptions.EndPoint).Port}/"); + var requestTask = client.GetStringAsync($"http://127.0.0.1:{listenOptions.IPEndPoint.Port}/"); requestTasks.Add(requestTask); } From e06f099a44494948e0810ad6292ec2250b44b74b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 00:38:28 -0700 Subject: [PATCH 31/78] More tests passing --- .../Core/src/Internal/ConnectionDispatcher.cs | 4 +- .../Core/test/ConnectionDispatcherTests.cs | 36 +-- .../Core/test/HttpConnectionManagerTests.cs | 2 +- .../Kestrel/Core/test/KestrelServerTests.cs | 270 +++++++++--------- .../src/Internal/TransportConnection.cs | 3 - 5 files changed, 145 insertions(+), 170 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 2296b8a52e8d..59de19830234 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -63,7 +63,7 @@ async Task AcceptConnectionsAsync() } // Internal for testing - internal async Task OnConnection(ConnectionContext connection) + private async Task OnConnection(ConnectionContext connection) { await using (connection) { @@ -71,7 +71,7 @@ internal async Task OnConnection(ConnectionContext connection) } } - private async Task Execute(KestrelConnection connection) + internal async Task Execute(KestrelConnection connection) { var id = Interlocked.Increment(ref _lastConnectionId); var connectionContext = connection.TransportConnection; diff --git a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs index ad53b66ffcfc..302f19abfcef 100644 --- a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs +++ b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; @@ -30,7 +31,7 @@ public void OnConnectionCreatesLogScopeWithConnectionId() var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - _ = dispatcher.OnConnection(connection); + _ = dispatcher.Execute(new KestrelConnection(connection, Mock.Of())); // The scope should be created var scopeObjects = ((TestKestrelTrace)serviceContext.Log) @@ -50,28 +51,6 @@ public void OnConnectionCreatesLogScopeWithConnectionId() Assert.True(((TestKestrelTrace)serviceContext.Log).Logger.Scopes.IsEmpty); } - [Fact] - public async Task OnConnectionCompletesTransportPipesAfterReturning() - { - var serviceContext = new TestServiceContext(); - var dispatcher = new ConnectionDispatcher(serviceContext, _ => Task.CompletedTask); - - var mockConnection = new Mock { CallBase = true }; - mockConnection.Object.ConnectionClosed = new CancellationToken(canceled: true); - var mockPipeReader = new Mock(); - var mockPipeWriter = new Mock(); - var mockPipe = new Mock(); - mockPipe.Setup(m => m.Input).Returns(mockPipeReader.Object); - mockPipe.Setup(m => m.Output).Returns(mockPipeWriter.Object); - mockConnection.Setup(m => m.Transport).Returns(mockPipe.Object); - var connection = mockConnection.Object; - - await dispatcher.OnConnection(connection); - - mockPipeWriter.Verify(m => m.Complete(It.IsAny()), Times.Once()); - mockPipeReader.Verify(m => m.Complete(It.IsAny()), Times.Once()); - } - [Fact] public async Task OnConnectionFiresOnCompleted() { @@ -80,14 +59,15 @@ public async Task OnConnectionFiresOnCompleted() var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var completeFeature = connection.Features.Get(); + var kestrelConnection = new KestrelConnection(connection, Mock.Of()); + var completeFeature = kestrelConnection.TransportConnection.Features.Get(); Assert.NotNull(completeFeature); object stateObject = new object(); object callbackState = null; completeFeature.OnCompleted(state => { callbackState = state; return Task.CompletedTask; }, stateObject); - await dispatcher.OnConnection(connection); + await dispatcher.Execute(kestrelConnection); Assert.Equal(stateObject, callbackState); } @@ -100,16 +80,16 @@ public async Task OnConnectionOnCompletedExceptionCaught() var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var completeFeature = connection.Features.Get(); var mockLogger = new Mock(); - connection.Logger = mockLogger.Object; + var kestrelConnection = new KestrelConnection(connection, mockLogger.Object); + var completeFeature = kestrelConnection.TransportConnection.Features.Get(); Assert.NotNull(completeFeature); object stateObject = new object(); object callbackState = null; completeFeature.OnCompleted(state => { callbackState = state; throw new InvalidTimeZoneException(); }, stateObject); - await dispatcher.OnConnection(connection); + await dispatcher.Execute(kestrelConnection); Assert.Equal(stateObject, callbackState); var log = mockLogger.Invocations.First(); diff --git a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs index a83614b8ca7f..34f6d210d70b 100644 --- a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs @@ -39,7 +39,7 @@ private void UnrootedConnectionsGetRemovedFromHeartbeatInnerScope( ConnectionManager httpConnectionManager, Mock trace) { - var mock = new Mock(); + var mock = new Mock() { CallBase = true }; mock.Setup(m => m.ConnectionId).Returns(connectionId); var httpConnection = new KestrelConnection(mock.Object, Mock.Of()); diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 8fa6a65fb1a3..5128fd617eda 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -235,17 +235,22 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var stop = new SemaphoreSlim(0); var mockTransport = new Mock(); - mockTransport - .Setup(transport => transport.AcceptAsync(It.IsAny())) - .Returns(new ValueTask((ConnectionContext)null)); - mockTransport - .Setup(transport => transport.StopAsync(It.IsAny())) - .Returns(async () => await stop.WaitAsync()); - var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) - .Returns(new ValueTask(mockTransport.Object)); + .Returns(e => + { + mockTransport + .Setup(transport => transport.AcceptAsync(It.IsAny())) + .Returns(new ValueTask((ConnectionContext)null)); + mockTransport + .Setup(transport => transport.StopAsync(It.IsAny())) + .Returns(async () => await stop.WaitAsync()); + mockTransport + .Setup(transport => transport.EndPoint).Returns(e); + + return new ValueTask(mockTransport.Object); + }); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); @@ -269,133 +274,124 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); } - //[Fact] - //public async Task StopAsyncCallsCompleteWithThrownException() - //{ - // var options = new KestrelServerOptions - // { - // ListenOptions = - // { - // new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - // } - // }; - - // var unbind = new SemaphoreSlim(0); - // var unbindException = new InvalidOperationException(); - - // var mockTransport = new Mock(); - // mockTransport - // .Setup(transport => transport.BindAsync()) - // .Returns(Task.CompletedTask); - // mockTransport - // .Setup(transport => transport.UnbindAsync()) - // .Returns(async () => - // { - // await unbind.WaitAsync(); - // throw unbindException; - // }); - // mockTransport - // .Setup(transport => transport.StopAsync()) - // .Returns(Task.CompletedTask); - - // var mockTransportFactory = new Mock(); - // mockTransportFactory - // .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) - // .Returns(mockTransport.Object); - - // var mockLoggerFactory = new Mock(); - // var mockLogger = new Mock(); - // mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - // var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); - // await server.StartAsync(new DummyApplication(), CancellationToken.None); - - // var stopTask1 = server.StopAsync(default); - // var stopTask2 = server.StopAsync(default); - // var stopTask3 = server.StopAsync(default); - - // Assert.False(stopTask1.IsCompleted); - // Assert.False(stopTask2.IsCompleted); - // Assert.False(stopTask3.IsCompleted); - - // unbind.Release(); - - // var timeout = TestConstants.DefaultTimeout; - // Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask1.TimeoutAfter(timeout))); - // Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask2.TimeoutAfter(timeout))); - // Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask3.TimeoutAfter(timeout))); - - // mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); - //} - - //[Fact] - //public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() - //{ - // var options = new KestrelServerOptions - // { - // ListenOptions = - // { - // new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - // } - // }; - - // var unbindTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - // //var mockTransport = new Mock(); - // //mockTransport - // // .Setup(transport => transport.BindAsync()) - // // .Returns(Task.CompletedTask); - // //mockTransport - // // .Setup(transport => transport.UnbindAsync()) - // // .Returns(unbindTcs.Task); - // //mockTransport - // // .Setup(transport => transport.StopAsync()) - // // .Returns(Task.CompletedTask); - - // //var mockTransportFactory = new Mock(); - // //mockTransportFactory - // // .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) - // // .Returns(mockTransport.Object); - - // var mockTransport = new Mock(); - // mockTransport - // .Setup(transport => transport.AcceptAsync()) - // .Returns(new ValueTask((ConnectionContext)null)); - // mockTransport - // .Setup(transport => transport.StopAsync(It.IsAny())) - // .Returns(unbindTcs.Task); - - // var mockTransportFactory = new Mock(); - // mockTransportFactory - // .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) - // .Returns(new ValueTask(mockTransport.Object)); - - // var mockLoggerFactory = new Mock(); - // var mockLogger = new Mock(); - // mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - // var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); - // await server.StartAsync(new DummyApplication(), default); - - // var stopTask1 = server.StopAsync(default); - // var stopTask2 = server.StopAsync(default); - - // Assert.False(stopTask1.IsCompleted); - // Assert.False(stopTask2.IsCompleted); - - // var continuationTask = Task.Run(async () => - // { - // await stopTask2; - // stopTask1.Wait(); - // }); - - // unbindTcs.SetResult(null); - - // // If stopTask2 is completed inline by the first call to StopAsync, stopTask1 will never complete. - // await stopTask1.DefaultTimeout(); - // await stopTask2.DefaultTimeout(); - // await continuationTask.DefaultTimeout(); - - // mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); - //} + [Fact] + public async Task StopAsyncCallsCompleteWithThrownException() + { + var options = new KestrelServerOptions + { + ListenOptions = + { + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + } + }; + + var unbind = new SemaphoreSlim(0); + var unbindException = new InvalidOperationException(); + + var mockTransport = new Mock(); + var mockTransportFactory = new Mock(); + mockTransportFactory + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) + .Returns(e => + { + mockTransport + .Setup(transport => transport.AcceptAsync(It.IsAny())) + .Returns(new ValueTask((ConnectionContext)null)); + mockTransport + .Setup(transport => transport.StopAsync(It.IsAny())) + .Returns(async () => + { + await unbind.WaitAsync(); + throw unbindException; + }); + mockTransport + .Setup(transport => transport.EndPoint).Returns(e); + + return new ValueTask(mockTransport.Object); + }); + + var mockLoggerFactory = new Mock(); + var mockLogger = new Mock(); + mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); + var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + await server.StartAsync(new DummyApplication(), CancellationToken.None); + + var stopTask1 = server.StopAsync(default); + var stopTask2 = server.StopAsync(default); + var stopTask3 = server.StopAsync(default); + + Assert.False(stopTask1.IsCompleted); + Assert.False(stopTask2.IsCompleted); + Assert.False(stopTask3.IsCompleted); + + unbind.Release(); + + var timeout = TestConstants.DefaultTimeout; + Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask1.TimeoutAfter(timeout))); + Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask2.TimeoutAfter(timeout))); + Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask3.TimeoutAfter(timeout))); + + mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() + { + var options = new KestrelServerOptions + { + ListenOptions = + { + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + } + }; + + var unbindTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var mockTransport = new Mock(); + var mockTransportFactory = new Mock(); + mockTransportFactory + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) + .Returns(e => + { + mockTransport + .Setup(transport => transport.AcceptAsync(It.IsAny())) + .Returns(new ValueTask((ConnectionContext)null)); + mockTransport + .Setup(transport => transport.StopAsync(It.IsAny())) + .Returns( new ValueTask(unbindTcs.Task)); + mockTransport + .Setup(transport => transport.EndPoint).Returns(e); + + return new ValueTask(mockTransport.Object); + }); + + var mockLoggerFactory = new Mock(); + var mockLogger = new Mock(); + mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); + var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + await server.StartAsync(new DummyApplication(), default); + + var stopTask1 = server.StopAsync(default); + var stopTask2 = server.StopAsync(default); + + Assert.False(stopTask1.IsCompleted); + Assert.False(stopTask2.IsCompleted); + + var continuationTask = Task.Run(async () => + { + await stopTask2; + stopTask1.Wait(); + }); + + unbindTcs.SetResult(null); + + // If stopTask2 is completed inline by the first call to StopAsync, stopTask1 will never complete. + await stopTask1.DefaultTimeout(); + await stopTask2.DefaultTimeout(); + await continuationTask.DefaultTimeout(); + + mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); + } [Fact] public void StartingServerInitializesHeartbeat() @@ -452,7 +448,9 @@ private class MockTransportFactory : IConnectionListenerFactory { public ValueTask BindAsync(EndPoint endpoint) { - return new ValueTask(Mock.Of()); + var mock = new Mock(); + mock.Setup(m => m.EndPoint).Returns(endpoint); + return new ValueTask(mock.Object); } } } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs index 1aafd7fd7805..cbebc1ee6161 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs @@ -9,7 +9,6 @@ using System.Threading; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { @@ -29,8 +28,6 @@ public TransportConnection() public override IFeatureCollection Features => this; - protected internal virtual ILogger Logger { get; set; } - public virtual MemoryPool MemoryPool { get; } public override IDuplexPipe Transport { get; set; } From 9990930c3476569ab3c90cc5ea8e1f45420e1f43 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 00:53:00 -0700 Subject: [PATCH 32/78] EndPoint is the source of truth for binding --- src/Servers/Kestrel/Core/src/ListenOptions.cs | 44 ++----------------- .../Core/src/LocalhostListenOptions.cs | 4 +- .../src/FileHandleEndPoint.cs | 11 +++++ .../src/Internal/LibuvConnection.cs | 1 - .../Transport.Libuv/src/Internal/Listener.cs | 12 +++-- .../src/Internal/SocketConnection.cs | 1 - 6 files changed, 24 insertions(+), 49 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 9f9b650417f2..4a83955e2cfe 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -21,7 +21,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// public class ListenOptions : IConnectionBuilder { - private FileHandleType _handleType; internal readonly List> _middleware = new List>(); internal ListenOptions(IPEndPoint endPoint) @@ -33,7 +32,6 @@ internal ListenOptions(IPEndPoint endPoint) internal ListenOptions(string socketPath) { Type = ListenType.SocketPath; - SocketPath = socketPath; EndPoint = new UnixDomainSocketEndPoint(socketPath); } @@ -45,19 +43,7 @@ internal ListenOptions(ulong fileHandle) internal ListenOptions(ulong fileHandle, FileHandleType handleType) { EndPoint = new FileHandleEndPoint(fileHandle, handleType); - Type = ListenType.FileHandle; - FileHandle = fileHandle; - switch (handleType) - { - case FileHandleType.Auto: - case FileHandleType.Tcp: - case FileHandleType.Pipe: - _handleType = handleType; - break; - default: - throw new NotSupportedException(); - } } /// @@ -68,32 +54,8 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) #pragma warning restore PUB0001 // Pubternal type in public API #pragma warning disable PUB0001 // Pubternal type in public API - public FileHandleType HandleType + public FileHandleType HandleType => (EndPoint as FileHandleEndPoint)?.FileHandleType ?? FileHandleType.Auto; #pragma warning restore PUB0001 // Pubternal type in public API - { - get => _handleType; - set - { - if (value == _handleType) - { - return; - } - if (Type != ListenType.FileHandle || _handleType != FileHandleType.Auto) - { - throw new InvalidOperationException(); - } - - switch (value) - { - case FileHandleType.Tcp: - case FileHandleType.Pipe: - _handleType = value; - break; - default: - throw new ArgumentException(nameof(HandleType)); - } - } - } internal EndPoint EndPoint { get; set; } @@ -108,13 +70,13 @@ public FileHandleType HandleType /// The absolute path to a Unix domain socket to bind to. /// Only set if the is . /// - public string SocketPath { get; } + public string SocketPath => (EndPoint as UnixDomainSocketEndPoint)?.ToString(); /// /// A file descriptor for the socket to open. /// Only set if the is . /// - public ulong FileHandle { get; } + public ulong FileHandle => (EndPoint as FileHandleEndPoint)?.FileHandle ?? 0; /// /// Enables an to resolve and use services registered by the application during startup. diff --git a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs index 80e008c78be8..0766c7d83bf8 100644 --- a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -76,7 +76,7 @@ internal ListenOptions Clone(IPAddress address) { var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port)) { - HandleType = HandleType, + EndPoint = EndPoint, KestrelServerOptions = KestrelServerOptions, NoDelay = NoDelay, Protocols = Protocols, diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs index f38bdc9a98e1..65e90c0b91fc 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs @@ -1,3 +1,4 @@ +using System; using System.Net; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions @@ -8,6 +9,16 @@ public FileHandleEndPoint(ulong fileHandle, FileHandleType fileHandleType) { FileHandle = fileHandle; FileHandleType = fileHandleType; + + switch (fileHandleType) + { + case FileHandleType.Auto: + case FileHandleType.Tcp: + case FileHandleType.Pipe: + break; + default: + throw new NotSupportedException(); + } } public ulong FileHandle { get; } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 487e91a8e07e..f4f08104ae8f 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -41,7 +41,6 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa RemoteEndPoint = remoteEndPoint; ConnectionClosed = _connectionClosedTokenSource.Token; - Logger = log; Log = log; Thread = thread; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 1b9d88c975c8..a26cd832a228 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -117,13 +117,15 @@ private UvPipeHandle ListenPipe(bool useFileHandle) private UvStreamHandle ListenHandle() { - switch (EndPoint) + var handleEndPoint = (FileHandleEndPoint)EndPoint; + + switch (handleEndPoint.FileHandleType) { - case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Auto: + case FileHandleType.Auto: break; - case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Tcp: + case FileHandleType.Tcp: return ListenTcp(useFileHandle: true); - case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Pipe: + case FileHandleType.Pipe: return ListenPipe(useFileHandle: true); default: throw new NotSupportedException(); @@ -133,6 +135,7 @@ private UvStreamHandle ListenHandle() try { handle = ListenTcp(useFileHandle: true); + EndPoint = new FileHandleEndPoint(handleEndPoint.FileHandle, FileHandleType.Tcp); return handle; } catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP) @@ -141,6 +144,7 @@ private UvStreamHandle ListenHandle() } handle = ListenPipe(useFileHandle: true); + EndPoint = new FileHandleEndPoint(handleEndPoint.FileHandle, FileHandleType.Pipe); return handle; } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 47148e6ccc25..6aacc5e62ae2 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -43,7 +43,6 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu _socket = socket; MemoryPool = memoryPool; _scheduler = scheduler; - Logger = trace; _trace = trace; LocalEndPoint = _socket.LocalEndPoint; From cdf5213956c9084abe5b9b3e676e89f16af0ae74 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 00:58:02 -0700 Subject: [PATCH 33/78] Fixed test --- src/Servers/Kestrel/Core/test/KestrelServerTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 5128fd617eda..92fe4d63cebb 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -245,7 +245,10 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() .Returns(new ValueTask((ConnectionContext)null)); mockTransport .Setup(transport => transport.StopAsync(It.IsAny())) - .Returns(async () => await stop.WaitAsync()); + .Returns(() => new ValueTask(unbind.WaitAsync())); + mockTransport + .Setup(transport => transport.DisposeAsync()) + .Returns(() => new ValueTask(stop.WaitAsync())); mockTransport .Setup(transport => transport.EndPoint).Returns(e); From ec6ae60b34ca41997752c04b13bbc8bef0508c0f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 01:01:54 -0700 Subject: [PATCH 34/78] Minor clean up --- .../Kestrel/Transport.Libuv/src/Internal/Listener.cs | 2 +- .../Transport.Libuv/src/Internal/ListenerContext.cs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index a26cd832a228..44c9ca508704 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -22,7 +22,7 @@ public Listener(LibuvTransportContext transportContext) : base(transportContext) { } - public UvStreamHandle ListenSocket { get; private set; } + protected UvStreamHandle ListenSocket { get; private set; } public ILibuvTrace Log => TransportContext.Log; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index c835318147f2..ee7151a78137 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -139,13 +139,15 @@ protected void StopAcceptingConnections() private UvStreamHandle AcceptHandle() { - switch (EndPoint) + var fileHandleEndPoint = (FileHandleEndPoint)EndPoint; + + switch (fileHandleEndPoint.FileHandleType) { - case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Auto: + case FileHandleType.Auto: throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first."); - case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Tcp: + case FileHandleType.Tcp: return AcceptTcp(); - case FileHandleEndPoint ep when ep.FileHandleType == FileHandleType.Pipe: + case FileHandleType.Pipe: return AcceptPipe(); default: throw new NotSupportedException(); From a5838f2273d8cebe285da2759ff304c85aca8d81 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 01:27:34 -0700 Subject: [PATCH 35/78] Fixed cloning listen options --- src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs index 0766c7d83bf8..d3edacaeeb3f 100644 --- a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs @@ -76,7 +76,6 @@ internal ListenOptions Clone(IPAddress address) { var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port)) { - EndPoint = EndPoint, KestrelServerOptions = KestrelServerOptions, NoDelay = NoDelay, Protocols = Protocols, From fcdf0721991cf59aced48a3f935e03060380ad17 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 09:55:31 -0700 Subject: [PATCH 36/78] Wait for the accept loop to end --- .../Core/src/Internal/ConnectionDispatcher.cs | 8 +++++++- src/Servers/Kestrel/Core/src/KestrelServer.cs | 13 +++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 59de19830234..c4600b799851 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -16,6 +16,7 @@ internal class ConnectionDispatcher private readonly ServiceContext _serviceContext; private readonly ConnectionDelegate _connectionDelegate; + private readonly TaskCompletionSource _acceptLoopTcs = new TaskCompletionSource(); public ConnectionDispatcher(ServiceContext serviceContext, ConnectionDelegate connectionDelegate) { @@ -25,9 +26,10 @@ public ConnectionDispatcher(ServiceContext serviceContext, ConnectionDelegate co private IKestrelTrace Log => _serviceContext.Log; - public void StartAcceptingConnections(IConnectionListener listener) + public Task StartAcceptingConnections(IConnectionListener listener) { ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false); + return _acceptLoopTcs.Task; } private void StartAcceptingConnectionsCore(IConnectionListener listener) @@ -59,6 +61,10 @@ async Task AcceptConnectionsAsync() { // REVIEW: Today the only way to end the accept loop is an exception } + finally + { + _acceptLoopTcs.TrySetResult(null); + } } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 25acb9379975..aa8b6b76d45b 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core { public class KestrelServer : IServer { - private readonly List _transports = new List(); + private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>(); private readonly IServerAddressesFeature _serverAddresses; private readonly IConnectionListenerFactory _transportFactory; @@ -138,10 +138,9 @@ async Task OnBind(ListenOptions options) // Update the endpoint options.EndPoint = transport.EndPoint; + var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); - _transports.Add(transport); - - connectionDispatcher.StartAcceptingConnections(transport); + _transports.Add((transport, acceptLoopTask)); } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); @@ -168,7 +167,8 @@ public async Task StopAsync(CancellationToken cancellationToken) var tasks = new Task[_transports.Count]; for (int i = 0; i < _transports.Count; i++) { - tasks[i] = _transports[i].StopAsync(cancellationToken).AsTask(); + (IConnectionListener listener, Task acceptLoop) = _transports[i]; + tasks[i] = Task.WhenAll(listener.StopAsync(cancellationToken).AsTask(), acceptLoop); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -188,7 +188,8 @@ public async Task StopAsync(CancellationToken cancellationToken) for (int i = 0; i < _transports.Count; i++) { - tasks[i] = _transports[i].DisposeAsync().AsTask(); + (IConnectionListener listener, Task acceptLoop) = _transports[i]; + tasks[i] = listener.DisposeAsync().AsTask(); } await Task.WhenAll(tasks).ConfigureAwait(false); From 1b9f718d4d7a694c80df872887506d650ff38ac8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 10:41:08 -0700 Subject: [PATCH 37/78] Clean up ConnectionManager --- .../Infrastructure/ConnectionManager.cs | 79 +++++++++++++------ .../ConnectionManagerShutdownExtensions.cs | 47 ----------- src/Servers/Kestrel/Core/src/KestrelServer.cs | 13 ++- .../test/LibuvTransportTests.cs | 12 +-- 4 files changed, 68 insertions(+), 83 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs index 7c40b3bc8c53..a098c6853f14 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { @@ -54,28 +55,6 @@ public void RemoveConnection(long id) } } - // This method should be called when no new connections can be added - public bool TryStartDrainingConnection() - { - if (_connectionCount == 0) - { - return false; - } - - _connectionDrainedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - return true; - } - - public Task WaitForConnectionDrainAsync() - { - if (_connectionDrainedTcs == null) - { - throw new InvalidOperationException("TryStartDrainingConnection must be called before WaitForConnectionDrainAsync()"); - } - - return _connectionDrainedTcs.Task; - } - public void Walk(Action callback) { foreach (var kvp in _connectionReferences) @@ -97,6 +76,62 @@ public void Walk(Action callback) } } + public async Task CloseAllConnectionsAsync(CancellationToken token) + { + if (!TryStartDrainingConnection()) + { + return false; + } + + Walk(connection => + { + connection.RequestClose(); + }); + + var allClosedTask = _connectionDrainedTcs.Task; + return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; + } + + public async Task AbortAllConnectionsAsync() + { + if (!TryStartDrainingConnection()) + { + return false; + } + + Walk(connection => + { + connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); + }); + + var allAbortedTask = _connectionDrainedTcs.Task; + return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; + } + + // This method should be called when no new connections can be added + private bool TryStartDrainingConnection() + { + if (_connectionCount == 0) + { + return false; + } + + _connectionDrainedTcs ??= new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + return true; + } + + private static Task CancellationTokenAsTask(CancellationToken token) + { + if (token.IsCancellationRequested) + { + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + token.Register(() => tcs.SetResult(null)); + return tcs.Task; + } + private static ResourceCounter GetCounter(long? number) => number.HasValue ? ResourceCounter.Quota(number.Value) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs deleted file mode 100644 index 720ff06af551..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs +++ /dev/null @@ -1,47 +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.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal static class ConnectionManagerShutdownExtensions - { - public static async Task CloseAllConnectionsAsync(this ConnectionManager connectionManager, CancellationToken token) - { - connectionManager.Walk(connection => - { - connection.RequestClose(); - }); - - var allClosedTask = connectionManager.WaitForConnectionDrainAsync(); - return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; - } - - public static async Task AbortAllConnectionsAsync(this ConnectionManager connectionManager) - { - connectionManager.Walk(connection => - { - connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); - }); - - var allAbortedTask = connectionManager.WaitForConnectionDrainAsync(); - return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; - } - - private static Task CancellationTokenAsTask(CancellationToken token) - { - if (token.IsCancellationRequested) - { - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - token.Register(() => tcs.SetResult(null)); - return tcs.Task; - } - } -} diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index aa8b6b76d45b..ee6648331798 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -173,16 +173,13 @@ public async Task StopAsync(CancellationToken cancellationToken) await Task.WhenAll(tasks).ConfigureAwait(false); - if (ConnectionManager.TryStartDrainingConnection()) + if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) { - if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) - { - Trace.NotAllConnectionsClosedGracefully(); + Trace.NotAllConnectionsClosedGracefully(); - if (!await ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false)) - { - Trace.NotAllConnectionsAborted(); - } + if (!await ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false)) + { + Trace.NotAllConnectionsAborted(); } } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 91d9353ccf35..9f8b8fd9af49 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -69,7 +69,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) await transport.BindAsync(); var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); - dispatcher.StartAcceptingConnections(transport); + _ = dispatcher.StartAcceptingConnections(transport); using (var socket = TestConnection.CreateConnectedLoopbackSocket(listenOptions.IPEndPoint.Port)) { @@ -83,7 +83,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) } } - serviceContext.ConnectionManager.TryStartDrainingConnection(); + Assert.True(await serviceContext.ConnectionManager.CloseAllConnectionsAsync(new CancellationTokenSource(TestConstants.DefaultTimeout).Token)); await transport.UnbindAsync(); await transport.StopAsync(); @@ -112,7 +112,7 @@ public async Task OneToTenThreads(int threadCount) await transport.BindAsync(); var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); - dispatcher.StartAcceptingConnections(transport); + var acceptTask = dispatcher.StartAcceptingConnections(transport); using (var client = new HttpClient()) { @@ -130,16 +130,16 @@ public async Task OneToTenThreads(int threadCount) } } - await transport.UnbindAsync(); + await transport.UnbindAsync().ConfigureAwait(false); - serviceContext.ConnectionManager.TryStartDrainingConnection(); + await acceptTask.ConfigureAwait(false); if (!await serviceContext.ConnectionManager.CloseAllConnectionsAsync(default).ConfigureAwait(false)) { await serviceContext.ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false); } - await transport.StopAsync(); + await transport.StopAsync().ConfigureAwait(false); } } } From 5d2d82e6208df0826433c9efdfec781604208300 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 18:03:17 -0700 Subject: [PATCH 38/78] PR feedback --- .../src/IConnectionListener.cs | 1 + .../src/IConnectionListenerFactory.cs | 2 + .../Core/src/Internal/ConnectionDispatcher.cs | 1 + .../Infrastructure/ConnectionManager.cs | 39 +++++-------------- .../Infrastructure/KestrelConnection.cs | 7 ++++ src/Servers/Kestrel/Core/src/ListenOptions.cs | 21 ++-------- src/Servers/Kestrel/Core/src/ListenType.cs | 15 ------- .../Kestrel/Core/test/AddressBinderTests.cs | 13 +++---- .../src/Internal/LibuvConnection.cs | 10 ++--- .../src/Internal/LibuvConnectionListener.cs | 20 ++++++++-- .../Transport.Libuv/src/Internal/Listener.cs | 3 +- .../src/Internal/ListenerContext.cs | 15 +++---- .../src/Internal/ListenerPrimary.cs | 1 - .../src/Internal/ListenerSecondary.cs | 2 +- .../test/LibuvConnectionTests.cs | 14 +++---- .../test/LibuvTransportTests.cs | 10 +++-- .../src/Internal/SocketConnection.cs | 6 +-- .../src/SocketConnectionListener.cs | 3 ++ .../ChunkedRequestTests.cs | 2 +- .../TestTransport/InMemoryTransportFactory.cs | 4 +- 20 files changed, 82 insertions(+), 107 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/ListenType.cs diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 2a9b2bedb67e..6b8cab82a1af 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -13,6 +13,7 @@ public interface IConnectionListener ValueTask AcceptAsync(CancellationToken cancellationToken = default); + // Rename to UnbindAsync maybe... or StopListeningAsync? ValueTask StopAsync(CancellationToken cancellationToken = default); ValueTask DisposeAsync(); diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs index 60ad5053a087..ef8cf9aab296 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Connections { public interface IConnectionListenerFactory { + // Add CancellationToken cancellationToken = default ValueTask BindAsync(EndPoint endpoint); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index c4600b799851..58911ced4ac4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -100,6 +100,7 @@ internal async Task Execute(KestrelConnection connection) Log.LogCritical(0, ex, $"{nameof(ConnectionDispatcher)}.{nameof(Execute)}() {connectionContext.ConnectionId}"); } + // TODO: Move this into the transport and have it wait for all connections to be disposed // Wait for the transport to close await CancellationTokenAsTask(connectionContext.ConnectionClosed); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs index a098c6853f14..05bb0f0726a5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -13,8 +14,6 @@ internal class ConnectionManager { private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); private readonly IKestrelTrace _trace; - private TaskCompletionSource _connectionDrainedTcs; - private int _connectionCount; public ConnectionManager(IKestrelTrace trace, long? upgradedConnectionLimit) : this(trace, GetCounter(upgradedConnectionLimit)) @@ -34,7 +33,6 @@ public ConnectionManager(IKestrelTrace trace, ResourceCounter upgradedConnection public void AddConnection(long id, KestrelConnection connection) { - Interlocked.Increment(ref _connectionCount); if (!_connectionReferences.TryAdd(id, new ConnectionReference(connection))) { throw new ArgumentException(nameof(id)); @@ -43,15 +41,14 @@ public void AddConnection(long id, KestrelConnection connection) public void RemoveConnection(long id) { - if (!_connectionReferences.TryRemove(id, out _)) + if (!_connectionReferences.TryRemove(id, out var reference)) { throw new ArgumentException(nameof(id)); } - var count = Interlocked.Decrement(ref _connectionCount); - if (count == 0 && _connectionDrainedTcs != null) + if (reference.TryGetConnection(out var connection)) { - _connectionDrainedTcs.TrySetResult(null); + connection.Complete(); } } @@ -78,48 +75,32 @@ public void Walk(Action callback) public async Task CloseAllConnectionsAsync(CancellationToken token) { - if (!TryStartDrainingConnection()) - { - return false; - } + var closeTasks = new List(); Walk(connection => { connection.RequestClose(); + closeTasks.Add(connection.ExecutionTask); }); - var allClosedTask = _connectionDrainedTcs.Task; + var allClosedTask = Task.WhenAll(closeTasks.ToArray()); return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; } public async Task AbortAllConnectionsAsync() { - if (!TryStartDrainingConnection()) - { - return false; - } + var abortTasks = new List(); Walk(connection => { connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); + abortTasks.Add(connection.ExecutionTask); }); - var allAbortedTask = _connectionDrainedTcs.Task; + var allAbortedTask = Task.WhenAll(abortTasks.ToArray()); return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; } - // This method should be called when no new connections can be added - private bool TryStartDrainingConnection() - { - if (_connectionCount == 0) - { - return false; - } - - _connectionDrainedTcs ??= new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - return true; - } - private static Task CancellationTokenAsTask(CancellationToken token) { if (token.IsCancellationRequested) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index b89a7d74c526..ebbb6ab38e96 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -20,6 +20,7 @@ internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompl private bool _completed; private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); + private readonly TaskCompletionSource _completionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public KestrelConnection(ConnectionContext connectionContext, ILogger logger) { @@ -35,6 +36,12 @@ public KestrelConnection(ConnectionContext connectionContext, ILogger logger) public ConnectionContext TransportConnection { get; set; } public CancellationToken ConnectionClosedRequested { get; set; } + public Task ExecutionTask => _completionTcs.Task; + + public void Complete() + { + _completionTcs.TrySetResult(null); + } public void TickHeartbeat() { diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 4a83955e2cfe..b9088ec014a8 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core { @@ -25,13 +24,11 @@ public class ListenOptions : IConnectionBuilder internal ListenOptions(IPEndPoint endPoint) { - Type = ListenType.IPEndPoint; EndPoint = endPoint; } internal ListenOptions(string socketPath) { - Type = ListenType.SocketPath; EndPoint = new UnixDomainSocketEndPoint(socketPath); } @@ -43,38 +40,26 @@ internal ListenOptions(ulong fileHandle) internal ListenOptions(ulong fileHandle, FileHandleType handleType) { EndPoint = new FileHandleEndPoint(fileHandle, handleType); - Type = ListenType.FileHandle; } - /// - /// The type of interface being described: either an , Unix domain socket path, or a file descriptor. - /// -#pragma warning disable PUB0001 // Pubternal type in public API - public ListenType Type { get; } -#pragma warning restore PUB0001 // Pubternal type in public API - -#pragma warning disable PUB0001 // Pubternal type in public API - public FileHandleType HandleType => (EndPoint as FileHandleEndPoint)?.FileHandleType ?? FileHandleType.Auto; -#pragma warning restore PUB0001 // Pubternal type in public API - internal EndPoint EndPoint { get; set; } // IPEndPoint is mutable so port 0 can be updated to the bound port. /// /// The to bind to. - /// Only set if the is . + /// Only set if the is . /// public IPEndPoint IPEndPoint => EndPoint as IPEndPoint; /// /// The absolute path to a Unix domain socket to bind to. - /// Only set if the is . + /// Only set if the is . /// public string SocketPath => (EndPoint as UnixDomainSocketEndPoint)?.ToString(); /// /// A file descriptor for the socket to open. - /// Only set if the is . + /// Only set if the is . /// public ulong FileHandle => (EndPoint as FileHandleEndPoint)?.FileHandle ?? 0; diff --git a/src/Servers/Kestrel/Core/src/ListenType.cs b/src/Servers/Kestrel/Core/src/ListenType.cs deleted file mode 100644 index 87d7fe1421a3..000000000000 --- a/src/Servers/Kestrel/Core/src/ListenType.cs +++ /dev/null @@ -1,15 +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. - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - /// - /// Enumerates the possible endpoint types. - /// - public enum ListenType - { - IPEndPoint, - SocketPath, - FileHandle - } -} diff --git a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs index 74b067cae3a2..00d0102f6b08 100644 --- a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs +++ b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs @@ -4,11 +4,11 @@ using System; using System.IO; using System.Net; +using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Xunit; @@ -53,10 +53,9 @@ public void DoesNotCreateIPEndPointOnInvalidIPAddress(string address) [InlineData("contoso.com")] public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host) { - var options = new KestrelServerOptions(); var listenOptions = AddressBinder.ParseAddress($"http://{host}", out var https); Assert.IsType(listenOptions); - Assert.Equal(ListenType.IPEndPoint, listenOptions.Type); + Assert.IsType(listenOptions.EndPoint); Assert.Equal(IPAddress.IPv6Any, listenOptions.IPEndPoint.Address); Assert.Equal(80, listenOptions.IPEndPoint.Port); Assert.False(https); @@ -65,10 +64,9 @@ public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host) [Fact] public void ParseAddressLocalhost() { - var options = new KestrelServerOptions(); var listenOptions = AddressBinder.ParseAddress("http://localhost", out var https); Assert.IsType(listenOptions); - Assert.Equal(ListenType.IPEndPoint, listenOptions.Type); + Assert.IsType(listenOptions.EndPoint); Assert.Equal(IPAddress.Loopback, listenOptions.IPEndPoint.Address); Assert.Equal(80, listenOptions.IPEndPoint.Port); Assert.False(https); @@ -77,9 +75,8 @@ public void ParseAddressLocalhost() [Fact] public void ParseAddressUnixPipe() { - var options = new KestrelServerOptions(); var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https); - Assert.Equal(ListenType.SocketPath, listenOptions.Type); + Assert.IsType(listenOptions.EndPoint); Assert.Equal("/tmp/kestrel-test.sock", listenOptions.SocketPath); Assert.False(https); } @@ -94,7 +91,7 @@ public void ParseAddressIP(string address, string ip, int port, bool isHttps) { var options = new KestrelServerOptions(); var listenOptions = AddressBinder.ParseAddress(address, out var https); - Assert.Equal(ListenType.IPEndPoint, listenOptions.Type); + Assert.IsType(listenOptions.EndPoint); Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address); Assert.Equal(port, listenOptions.IPEndPoint.Port); Assert.Equal(isHttps, https); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index f4f08104ae8f..a410bebf4e5a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -104,7 +104,7 @@ private async Task StartCore() } finally { - inputError = inputError ?? _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully."); + inputError ??= _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully."); // Now, complete the input so that no more reads can happen Input.Complete(inputError); @@ -131,7 +131,7 @@ private async Task StartCore() public override void Abort(ConnectionAbortedException abortReason) { _abortReason = abortReason; - + // Cancel WriteOutputAsync loop after setting _abortReason. Output.CancelPendingRead(); @@ -141,15 +141,15 @@ public override void Abort(ConnectionAbortedException abortReason) public override async ValueTask DisposeAsync() { + Transport.Input.Complete(); + Transport.Output.Complete(); + if (_task != null) { await _task; } _connectionClosedTokenSource.Dispose(); - - Transport.Input.Complete(); - Transport.Output.Complete(); } // Called on Libuv thread diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 01de2980e53a..86b343912d2c 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -43,7 +43,7 @@ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, public EndPoint EndPoint { get; set; } - public async Task StopAsync() + public async Task StopThreadsAsync() { try { @@ -201,9 +201,23 @@ public async ValueTask StopAsync(CancellationToken cancellationToken) public async ValueTask DisposeAsync() { - await UnbindAsync(); + // TODO: ConfigureAwait + await UnbindAsync().ConfigureAwait(false); + + if (_acceptEnumerator != null) + { + // TODO: Log how many connections were unaccepted + if (await _acceptEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + // Abort the connection + _acceptEnumerator.Current.Abort(); + } + + // Dispose the enumerator + await _acceptEnumerator.DisposeAsync().ConfigureAwait(false); + } - await StopAsync(); + await StopThreadsAsync().ConfigureAwait(false); } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 44c9ca508704..9738c53bca17 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal /// internal class Listener : ListenerContext, IAsyncDisposable { + // REVIEW: This needs to be bounded and we need a strategy for what to do when the queue is full private bool _closed; public Listener(LibuvTransportContext transportContext) : base(transportContext) @@ -191,7 +192,7 @@ private void OnConnection(UvStreamHandle listenSocket, int status) protected virtual void DispatchConnection(UvStreamHandle socket) { - _ = HandleConnectionAsync(socket); + HandleConnectionAsync(socket); } public virtual async Task DisposeAsync() diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index ee7151a78137..b743852f9ab6 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -15,12 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal class ListenerContext { - // REVIEW: This needs to be bounded and we need a strategy for what to do when the queue is full - private readonly Channel _acceptQueue = Channel.CreateBounded(new BoundedChannelOptions(512) - { - // REVIEW: Not sure if this is right as nothing is stopping the libuv callback today - FullMode = BoundedChannelFullMode.Wait - }); + private readonly Channel _acceptQueue = Channel.CreateUnbounded(); public ListenerContext(LibuvTransportContext transportContext) { @@ -37,9 +32,9 @@ public async ValueTask AcceptAsync(CancellationToken cancellati { while (await _acceptQueue.Reader.WaitToReadAsync()) { - while (_acceptQueue.Reader.TryRead(out var item)) + while (_acceptQueue.Reader.TryRead(out var connection)) { - return item; + return connection; } } @@ -64,7 +59,7 @@ protected UvStreamHandle CreateAcceptSocket() } } - protected internal async Task HandleConnectionAsync(UvStreamHandle socket) + protected internal void HandleConnectionAsync(UvStreamHandle socket) { try { @@ -89,7 +84,7 @@ protected internal async Task HandleConnectionAsync(UvStreamHandle socket) var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint); connection.Start(); - await _acceptQueue.Writer.WriteAsync(connection); + _acceptQueue.Writer.TryWrite(connection); } catch (Exception ex) { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs index c6051198a07f..acbc356294d3 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs @@ -7,7 +7,6 @@ using System.Net; using System.Runtime.InteropServices; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs index fab9a4e43614..bcb432449176 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs @@ -153,7 +153,7 @@ private void ReadStartCallback(UvStreamHandle handle, int status) { DispatchPipe.Accept(acceptSocket); - _ = HandleConnectionAsync(acceptSocket); + HandleConnectionAsync(acceptSocket); } catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode)) { diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs index 05c8c8899d1c..c9d456a1df7e 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs @@ -23,24 +23,24 @@ public async Task DoesNotEndConnectionOnZeroRead() var transportContext = new TestLibuvTransportContext(); await using var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); + var listenerContext = new ListenerContext(transportContext) + { + Thread = thread + }; try { await thread.StartAsync(); await thread.PostAsync(_ => - { - var listenerContext = new ListenerContext(transportContext) - { - Thread = thread - }; + { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - _ = listenerContext.HandleConnectionAsync(socket); + listenerContext.HandleConnectionAsync(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); }, (object)null); - await using var connection = await transport.AcceptAsync(); + await using var connection = await listenerContext.AcceptAsync(); var readAwaitable = connection.Transport.Input.ReadAsync(); Assert.False(readAwaitable.IsCompleted); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 9f8b8fd9af49..8da288eb7094 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -42,7 +42,7 @@ public async Task TransportCanBindAndStop() // The transport can no longer start threads without binding to an endpoint. await transport.BindAsync(); - await transport.StopAsync(); + await transport.StopThreadsAsync(); } [Fact] @@ -53,7 +53,7 @@ public async Task TransportCanBindUnbindAndStop() await transport.BindAsync(); await transport.UnbindAsync(); - await transport.StopAsync(); + await transport.StopThreadsAsync(); } [Theory] @@ -67,6 +67,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); await transport.BindAsync(); + listenOptions.EndPoint = transport.EndPoint; var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); _ = dispatcher.StartAcceptingConnections(transport); @@ -86,7 +87,7 @@ public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) Assert.True(await serviceContext.ConnectionManager.CloseAllConnectionsAsync(new CancellationTokenSource(TestConstants.DefaultTimeout).Token)); await transport.UnbindAsync(); - await transport.StopAsync(); + await transport.StopThreadsAsync(); } [ConditionalTheory] @@ -110,6 +111,7 @@ public async Task OneToTenThreads(int threadCount) var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); await transport.BindAsync(); + listenOptions.EndPoint = transport.EndPoint; var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); var acceptTask = dispatcher.StartAcceptingConnections(transport); @@ -139,7 +141,7 @@ public async Task OneToTenThreads(int threadCount) await serviceContext.ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false); } - await transport.StopAsync().ConfigureAwait(false); + await transport.StopThreadsAsync().ConfigureAwait(false); } } } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 6aacc5e62ae2..aa857874d09d 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -111,14 +111,14 @@ public override void Abort(ConnectionAbortedException abortReason) // Only called after connection middleware is complete which means the ConnectionClosed token has fired. public override async ValueTask DisposeAsync() { + Transport.Input.Complete(); + Transport.Output.Complete(); + if (_task != null) { await _task; } - Transport.Input.Complete(); - Transport.Output.Complete(); - _connectionClosedTokenSource.Dispose(); } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 37f89405d96c..897300ba6846 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -66,6 +66,8 @@ internal void Bind() throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } + // TODO: Add support for UnixDomainSocket + var listenSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 @@ -92,6 +94,7 @@ internal void Bind() public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { + // TODO: throw for overlapping accepts try { var acceptSocket = await _listenSocket.AcceptAsync(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs index 2f3f01a941c1..12ac5791ff6e 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs @@ -127,7 +127,7 @@ await connection.ReceiveEnd( [Fact] public async Task Http10KeepAliveTransferEncoding() { - var testContext = new TestServiceContext(); + var testContext = new TestServiceContext(LoggerFactory); await using (var server = new TestServer(AppChunked, testContext)) { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs index 5bc87b0ab820..776b94d20ec1 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs @@ -14,7 +14,7 @@ internal class InMemoryTransportFactory : IConnectionListenerFactory, IConnectio { private Channel _acceptQueue = Channel.CreateUnbounded(); - public EndPoint EndPoint { get; } + public EndPoint EndPoint { get; set; } public void AddConnection(ConnectionContext connection) { @@ -37,6 +37,8 @@ public async ValueTask AcceptAsync(CancellationToken cancella public ValueTask BindAsync(EndPoint endpoint) { + EndPoint = endpoint; + // The endpoint isn't important return new ValueTask(this); } From 34dc284af81068bb1f31a6c18b9b3a510e0b1102 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 19:16:15 -0700 Subject: [PATCH 39/78] Fixed timeout tests --- .../KeepAliveTimeoutTests.cs | 15 +++ .../RequestHeadersTimeoutTests.cs | 8 ++ .../InMemoryTransportConnection.cs | 127 +++++++++++++++++- 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs index 422520f5b8db..34f3932129cd 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO.Pipelines; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -33,6 +34,8 @@ public async Task ConnectionClosedWhenKeepAliveTimeoutExpires() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send( "GET / HTTP/1.1", "Host:", @@ -59,6 +62,8 @@ public async Task ConnectionKeptAliveBetweenRequests() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + for (var i = 0; i < 10; i++) { await connection.Send( @@ -86,6 +91,8 @@ public async Task ConnectionNotTimedOutWhileRequestBeingSent() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send( "POST /consume HTTP/1.1", "Host:", @@ -126,6 +133,8 @@ private async Task ConnectionNotTimedOutWhileAppIsRunning() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send( "GET /longrunning HTTP/1.1", "Host:", @@ -164,6 +173,8 @@ private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + // Min amount of time between requests that triggers a keep-alive timeout. testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); @@ -184,6 +195,8 @@ private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send( "GET /upgrade HTTP/1.1", "Host:", @@ -212,6 +225,8 @@ await connection.Receive( private TestServer CreateServer(TestServiceContext context, CancellationToken longRunningCt = default, CancellationToken upgradeCt = default) { + // Ensure request headers timeout is started as soon as the tests send requests. + context.Scheduler = PipeScheduler.Inline; context.ServerOptions.AddServerHeader = false; context.ServerOptions.Limits.KeepAliveTimeout = _keepAliveTimeout; context.ServerOptions.Limits.MinRequestBodyDataRate = null; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs index 65448bd6d231..e217ece3f0db 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs @@ -32,6 +32,8 @@ public async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(string he { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send( "GET / HTTP/1.1", headers); @@ -55,6 +57,8 @@ public async Task RequestHeadersTimeoutCanceledAfterHeadersReceived() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send( "POST / HTTP/1.1", "Host:", @@ -86,6 +90,8 @@ public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string reque { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + await connection.Send(requestLine); // Min amount of time between requests that triggers a request headers timeout. @@ -110,6 +116,8 @@ public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { using (var connection = server.CreateConnection()) { + await connection.TransportConnection.WaitForReadTask; + foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n") { await connection.Send(ch.ToString()); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index 4f8dfd6f9525..3315eb8c64f0 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -3,10 +3,12 @@ using System; using System.Buffers; +using System.Diagnostics; using System.IO.Pipelines; using System.Net; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; @@ -30,11 +32,15 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger, var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(memoryPool, readerScheduler: scheduler), new PipeOptions(memoryPool, writerScheduler: scheduler)); Application = pair.Application; - Transport = pair.Transport; + var wrapper = new ObservableDuplexPipe(pair.Transport); + Transport = wrapper; + WaitForReadTask = wrapper.WaitForReadTask; ConnectionClosed = _connectionClosedTokenSource.Token; } + public Task WaitForReadTask { get; } + public override MemoryPool MemoryPool { get; } public ConnectionAbortedException AbortReason { get; private set; } @@ -73,5 +79,124 @@ public override ValueTask DisposeAsync() return base.DisposeAsync(); } + + // This piece of code allows us to wait until the PipeReader has been awaited on. + // We need to wrap lots of layers (including the ValueTask) to gain visiblity into when + // the machinery for the await happens + private class ObservableDuplexPipe : IDuplexPipe + { + private readonly ObservablePipeReader _reader; + + public ObservableDuplexPipe(IDuplexPipe duplexPipe) + { + _reader = new ObservablePipeReader(duplexPipe.Input); + + Input = _reader; + Output = duplexPipe.Output; + + } + + public Task WaitForReadTask => _reader.WaitForReadTask; + + public PipeReader Input { get; } + + public PipeWriter Output { get; } + + private class ObservablePipeReader : PipeReader + { + private readonly PipeReader _reader; + private readonly TaskCompletionSource _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + public Task WaitForReadTask => _tcs.Task; + + public ObservablePipeReader(PipeReader reader) + { + _reader = reader; + } + + public override void AdvanceTo(SequencePosition consumed) + { + _reader.AdvanceTo(consumed); + } + + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) + { + _reader.AdvanceTo(consumed, examined); + } + + public override void CancelPendingRead() + { + _reader.CancelPendingRead(); + } + + public override void Complete(Exception exception = null) + { + _reader.Complete(exception); + } + + public override void OnWriterCompleted(Action callback, object state) + { + _reader.OnWriterCompleted(callback, state); + } + + public override ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + var task = _reader.ReadAsync(cancellationToken); + + if (_tcs.Task.IsCompleted) + { + return task; + } + + return new ValueTask(new ObservableValueTask(task, _tcs), 0); + } + + public override bool TryRead(out ReadResult result) + { + return _reader.TryRead(out result); + } + + private class ObservableValueTask : IValueTaskSource + { + private readonly ValueTask _task; + private readonly TaskCompletionSource _tcs; + + public ObservableValueTask(ValueTask task, TaskCompletionSource tcs) + { + _task = task; + _tcs = tcs; + } + + public T GetResult(short token) + { + return _task.GetAwaiter().GetResult(); + } + + public ValueTaskSourceStatus GetStatus(short token) + { + if (_task.IsCanceled) + { + return ValueTaskSourceStatus.Canceled; + } + if (_task.IsFaulted) + { + return ValueTaskSourceStatus.Faulted; + } + if (_task.IsCompleted) + { + return ValueTaskSourceStatus.Succeeded; + } + return ValueTaskSourceStatus.Pending; + } + + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + { + _task.GetAwaiter().UnsafeOnCompleted(() => continuation(state)); + + _tcs.TrySetResult(null); + } + } + } + } } } From 1a5652ef2dc23ac49107a6c4410a59d82e7118e9 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 24 May 2019 22:45:32 -0700 Subject: [PATCH 40/78] Made more libuv tests pass - Decouple LibuvThread from LibuvTransport --- .../src/Internal/LibuvConnection.cs | 12 +- .../src/Internal/LibuvConnectionListener.cs | 2 +- .../src/Internal/LibuvThread.cs | 31 +- .../src/Internal/ListenerContext.cs | 7 +- .../test/LibuvConnectionTests.cs | 365 ++++++++---------- .../test/LibuvOutputConsumerTests.cs | 4 +- .../Transport.Libuv/test/LibuvThreadTests.cs | 3 +- 7 files changed, 205 insertions(+), 219 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index a410bebf4e5a..5ca906d41c0a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -33,7 +33,13 @@ internal partial class LibuvConnection : TransportConnection private MemoryHandle _bufferHandle; private Task _task; - public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread thread, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint) + public LibuvConnection(UvStreamHandle socket, + ILibuvTrace log, + LibuvThread thread, + IPEndPoint remoteEndPoint, + IPEndPoint localEndPoint, + PipeOptions inputOptions = null, + PipeOptions outputOptions = null) { _socket = socket; @@ -44,8 +50,8 @@ public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread threa Log = log; Thread = thread; - var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, Thread, useSynchronizationContext: false); - var outputOptions = new PipeOptions(MemoryPool, Thread, PipeScheduler.ThreadPool, useSynchronizationContext: false); + inputOptions ??= new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, Thread, useSynchronizationContext: false); + outputOptions ??= new PipeOptions(MemoryPool, Thread, PipeScheduler.ThreadPool, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 86b343912d2c..8ca9cf640587 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -77,7 +77,7 @@ public async Task BindAsync() // TODO: Split endpoint management from thread management for (var index = 0; index < TransportOptions.ThreadCount; index++) { - Threads.Add(new LibuvThread(this)); + Threads.Add(new LibuvThread(Libuv, TransportContext)); } foreach (var thread in Threads) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs index e151b6ab17c6..b5512a27dfc9 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs @@ -20,9 +20,9 @@ internal class LibuvThread : PipeScheduler // maximum times the work queues swapped and are processed in a single pass // as completing a task may immediately have write data to put on the network // otherwise it needs to wait till the next pass of the libuv loop - private readonly int _maxLoops = 8; + private readonly int _maxLoops; - private readonly LibuvConnectionListener _transport; + private readonly LibuvFunctions _libuv; private readonly IHostApplicationLifetime _appLifetime; private readonly Thread _thread; private readonly TaskCompletionSource _threadTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -40,13 +40,19 @@ internal class LibuvThread : PipeScheduler private Exception _closeError; private readonly ILibuvTrace _log; - public LibuvThread(LibuvConnectionListener transport) + public LibuvThread(LibuvFunctions libuv, LibuvTransportContext libuvTransportContext, int maxLoops = 8) + : this(libuv, libuvTransportContext.AppLifetime, libuvTransportContext.Options.MemoryPoolFactory(), libuvTransportContext.Log, maxLoops) { - _transport = transport; - _appLifetime = transport.AppLifetime; - _log = transport.Log; + } + + public LibuvThread(LibuvFunctions libuv, IHostApplicationLifetime appLifetime, MemoryPool pool, ILibuvTrace log, int maxLoops = 8) + { + _libuv = libuv; + _appLifetime = appLifetime; + _log = log; _loop = new UvLoopHandle(_log); _post = new UvAsyncHandle(_log); + _maxLoops = maxLoops; _thread = new Thread(ThreadStart); #if !INNER_LOOP @@ -60,17 +66,10 @@ public LibuvThread(LibuvConnectionListener transport) #endif QueueCloseHandle = PostCloseHandle; QueueCloseAsyncHandle = EnqueueCloseHandle; - MemoryPool = transport.TransportOptions.MemoryPoolFactory(); + MemoryPool = pool; WriteReqPool = new WriteReqPool(this, _log); } - // For testing - public LibuvThread(LibuvConnectionListener transport, int maxLoops) - : this(transport) - { - _maxLoops = maxLoops; - } - public UvLoopHandle Loop { get { return _loop; } } public MemoryPool MemoryPool { get; } @@ -253,7 +252,7 @@ public void Walk(Action callback) private void Walk(LibuvFunctions.uv_walk_cb callback, IntPtr arg) { - _transport.Libuv.walk( + _libuv.walk( _loop, callback, arg @@ -282,7 +281,7 @@ private void ThreadStart(object parameter) var tcs = (TaskCompletionSource)parameter; try { - _loop.Init(_transport.Libuv); + _loop.Init(_libuv); _post.Init(_loop, OnPost, EnqueueCloseHandle); _initCompleted = true; tcs.SetResult(0); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index b743852f9ab6..e1ade4ee2883 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO.Pipelines; using System.Net; using System.Net.Sockets; using System.Threading; @@ -28,6 +29,10 @@ public ListenerContext(LibuvTransportContext transportContext) public LibuvThread Thread { get; set; } + public PipeOptions InputOptions { get; set; } + + public PipeOptions OutputOptions { get; set; } + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { while (await _acceptQueue.Reader.WaitToReadAsync()) @@ -81,7 +86,7 @@ protected internal void HandleConnectionAsync(UvStreamHandle socket) } } - var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint); + var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions); connection.Start(); _acceptQueue.Writer.TryWrite(connection); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs index c9d456a1df7e..3cb5ea902c05 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs @@ -21,8 +21,7 @@ public async Task DoesNotEndConnectionOnZeroRead() { var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext(); - await using var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - var thread = new LibuvThread(transport); + var thread = new LibuvThread(mockLibuv, transportContext); var listenerContext = new ListenerContext(transportContext) { Thread = thread @@ -51,197 +50,175 @@ await thread.PostAsync(_ => } } - // TODO: Make it work - - //[Fact] - //public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied() - //{ - // var mockConnectionDispatcher = new MockConnectionDispatcher(); - // var mockLibuv = new MockLibuv(); - // var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - // var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - // var thread = new LibuvThread(transport); - // mockConnectionDispatcher.InputOptions = pool => - // new PipeOptions( - // pool: pool, - // pauseWriterThreshold: 3, - // readerScheduler: PipeScheduler.Inline, - // writerScheduler: PipeScheduler.Inline, - // useSynchronizationContext: false); - - // // We don't set the output writer scheduler here since we want to run the callback inline - - // mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - - - // Task connectionTask = null; - // try - // { - // await thread.StartAsync(); - - // // Write enough to make sure back pressure will be applied - // await thread.PostAsync(_ => - // { - // var listenerContext = new ListenerContext(transportContext) - // { - // Thread = thread - // }; - // var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - // var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - // listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - // connectionTask = connection.Start(); - - // mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); - // mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); - - // }, null); - - // // Now assert that we removed the callback from libuv to stop reading - // Assert.Null(mockLibuv.AllocCallback); - // Assert.Null(mockLibuv.ReadCallback); - - // // Now complete the output writer so that the connection closes - // mockConnectionDispatcher.Output.Writer.Complete(); - - // await connectionTask.DefaultTimeout(); - - // // Assert that we don't try to start reading - // Assert.Null(mockLibuv.AllocCallback); - // Assert.Null(mockLibuv.ReadCallback); - // } - // finally - // { - // mockConnectionDispatcher.Input.Reader.Complete(); - // mockConnectionDispatcher.Output.Writer.Complete(); - - // await thread.StopAsync(TimeSpan.FromSeconds(5)); - // } - //} - - //[Fact] - //public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied() - //{ - // var mockConnectionDispatcher = new MockConnectionDispatcher(); - // var mockLibuv = new MockLibuv(); - // var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - // var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - // var thread = new LibuvThread(transport); - // var mockScheduler = new Mock(); - // Action backPressure = null; - // mockScheduler.Setup(m => m.Schedule(It.IsAny>(), It.IsAny())).Callback, object>((a, o) => - // { - // backPressure = () => a(o); - // }); - // mockConnectionDispatcher.InputOptions = pool => - // new PipeOptions( - // pool: pool, - // pauseWriterThreshold: 3, - // resumeWriterThreshold: 3, - // writerScheduler: mockScheduler.Object, - // readerScheduler: PipeScheduler.Inline, - // useSynchronizationContext: false); - - // mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - - // Task connectionTask = null; - // try - // { - // await thread.StartAsync(); - - // // Write enough to make sure back pressure will be applied - // await thread.PostAsync(_ => - // { - // var listenerContext = new ListenerContext(transportContext) - // { - // Thread = thread - // }; - // var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - // var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - // listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - // connectionTask = connection.Start(); - - // mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); - // mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); - - // }, null); - - // // Now assert that we removed the callback from libuv to stop reading - // Assert.Null(mockLibuv.AllocCallback); - // Assert.Null(mockLibuv.ReadCallback); - - // // Now release backpressure by reading the input - // var result = await mockConnectionDispatcher.Input.Reader.ReadAsync(); - // // Calling advance will call into our custom scheduler that captures the back pressure - // // callback - // mockConnectionDispatcher.Input.Reader.AdvanceTo(result.Buffer.End); - - // // Cancel the current pending flush - // mockConnectionDispatcher.Input.Writer.CancelPendingFlush(); - - // // Now release the back pressure - // await thread.PostAsync(a => a(), backPressure); - - // // Assert that we don't try to start reading since the write was cancelled - // Assert.Null(mockLibuv.AllocCallback); - // Assert.Null(mockLibuv.ReadCallback); - - // // Now complete the output writer and wait for the connection to close - // mockConnectionDispatcher.Output.Writer.Complete(); - - // await connectionTask.DefaultTimeout(); - - // // Assert that we don't try to start reading - // Assert.Null(mockLibuv.AllocCallback); - // Assert.Null(mockLibuv.ReadCallback); - // } - // finally - // { - // mockConnectionDispatcher.Input.Reader.Complete(); - // mockConnectionDispatcher.Output.Writer.Complete(); - - // await thread.StopAsync(TimeSpan.FromSeconds(5)); - // } - //} - - //[Fact] - //public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() - //{ - // var mockConnectionDispatcher = new MockConnectionDispatcher(); - // var mockLibuv = new MockLibuv(); - // var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher }; - // var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - // var thread = new LibuvThread(transport); - - // Task connectionTask = null; - // try - // { - // await thread.StartAsync(); - // await thread.PostAsync(_ => - // { - // var listenerContext = new ListenerContext(transportContext) - // { - // Thread = thread - // }; - // var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - // var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); - // listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); - // connectionTask = connection.Start(); - - // var ignored = new LibuvFunctions.uv_buf_t(); - // mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); - // }, (object)null); - - // var readAwaitable = await mockConnectionDispatcher.Input.Reader.ReadAsync(); - // Assert.True(readAwaitable.IsCompleted); - // } - // finally - // { - // mockConnectionDispatcher.Input.Reader.Complete(); - // mockConnectionDispatcher.Output.Writer.Complete(); - // await connectionTask; - - // await thread.StopAsync(TimeSpan.FromSeconds(5)); - // } - //} + [Fact] + public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied() + { + var mockLibuv = new MockLibuv(); + var transportContext = new TestLibuvTransportContext(); + var thread = new LibuvThread(mockLibuv, transportContext); + var listenerContext = new ListenerContext(transportContext) + { + Thread = thread, + InputOptions = new PipeOptions( + pool: thread.MemoryPool, + pauseWriterThreshold: 3, + readerScheduler: PipeScheduler.Inline, + writerScheduler: PipeScheduler.Inline, + useSynchronizationContext: false), + + // We don't set the output writer scheduler here since we want to run the callback inline + OutputOptions = new PipeOptions( + pool: thread.MemoryPool, + readerScheduler: thread, + writerScheduler: PipeScheduler.Inline, + useSynchronizationContext: false) + }; + + try + { + await thread.StartAsync(); + + // Write enough to make sure back pressure will be applied + await thread.PostAsync(_ => + { + var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); + listenerContext.HandleConnectionAsync(socket); + + mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); + mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); + + }, null); + + var connection = await listenerContext.AcceptAsync(); + + // Now assert that we removed the callback from libuv to stop reading + Assert.Null(mockLibuv.AllocCallback); + Assert.Null(mockLibuv.ReadCallback); + + // Now complete the output writer so that the connection closes + await connection.DisposeAsync(); + + // Assert that we don't try to start reading + Assert.Null(mockLibuv.AllocCallback); + Assert.Null(mockLibuv.ReadCallback); + } + finally + { + await thread.StopAsync(TimeSpan.FromSeconds(5)); + } + } + + [Fact] + public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied() + { + var mockLibuv = new MockLibuv(); + var transportContext = new TestLibuvTransportContext(); + var thread = new LibuvThread(mockLibuv, transportContext); + var mockScheduler = new Mock(); + Action backPressure = null; + mockScheduler.Setup(m => m.Schedule(It.IsAny>(), It.IsAny())).Callback, object>((a, o) => + { + backPressure = () => a(o); + }); + var listenerContext = new ListenerContext(transportContext) + { + Thread = thread, + InputOptions = new PipeOptions( + pool: thread.MemoryPool, + pauseWriterThreshold: 3, + resumeWriterThreshold: 3, + writerScheduler: mockScheduler.Object, + readerScheduler: PipeScheduler.Inline, + useSynchronizationContext: false), + OutputOptions = new PipeOptions( + pool: thread.MemoryPool, + readerScheduler: thread, + writerScheduler: PipeScheduler.Inline, + useSynchronizationContext: false) + }; + + try + { + await thread.StartAsync(); + + // Write enough to make sure back pressure will be applied + await thread.PostAsync(_ => + { + var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); + listenerContext.HandleConnectionAsync(socket); + + mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); + mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); + + }, null); + + var connection = await listenerContext.AcceptAsync(); + + // Now assert that we removed the callback from libuv to stop reading + Assert.Null(mockLibuv.AllocCallback); + Assert.Null(mockLibuv.ReadCallback); + + // Now release backpressure by reading the input + var result = await connection.Transport.Input.ReadAsync(); + // Calling advance will call into our custom scheduler that captures the back pressure + // callback + connection.Transport.Input.AdvanceTo(result.Buffer.End); + + // Cancel the current pending flush + connection.Application.Output.CancelPendingFlush(); + + // Now release the back pressure + await thread.PostAsync(a => a(), backPressure); + + // Assert that we don't try to start reading since the write was cancelled + Assert.Null(mockLibuv.AllocCallback); + Assert.Null(mockLibuv.ReadCallback); + + // Now complete the output writer and wait for the connection to close + await connection.DisposeAsync(); + + // Assert that we don't try to start reading + Assert.Null(mockLibuv.AllocCallback); + Assert.Null(mockLibuv.ReadCallback); + } + finally + { + await thread.StopAsync(TimeSpan.FromSeconds(5)); + } + } + + [Fact] + public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() + { + var mockLibuv = new MockLibuv(); + var transportContext = new TestLibuvTransportContext(); + var thread = new LibuvThread(mockLibuv, transportContext); + var listenerContext = new ListenerContext(transportContext) + { + Thread = thread + }; + + try + { + await thread.StartAsync(); + await thread.PostAsync(_ => + { + var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); + listenerContext.HandleConnectionAsync(socket); + + var ignored = new LibuvFunctions.uv_buf_t(); + mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); + }, (object)null); + + await using var connection = await listenerContext.AcceptAsync(); + + var readAwaitable = await connection.Transport.Input.ReadAsync(); + Assert.True(readAwaitable.IsCompleted); + } + finally + { + await thread.StopAsync(TimeSpan.FromSeconds(5)); + } + } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs index c4ef983c03be..8ce785997ec6 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs @@ -45,8 +45,8 @@ public LibuvOutputConsumerTests() _memoryPool = KestrelMemoryPool.Create(); _mockLibuv = new MockLibuv(); - var libuvTransport = new LibuvConnectionListener(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0).EndPoint); - _libuvThread = new LibuvThread(libuvTransport, maxLoops: 1); + var context = new TestLibuvTransportContext(); + _libuvThread = new LibuvThread(_mockLibuv, context, maxLoops: 1); _libuvThread.StartAsync().Wait(); } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs index df1e1dfde24e..7bfd5280ef6b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs @@ -16,8 +16,7 @@ public async Task LibuvThreadDoesNotThrowIfPostingWorkAfterDispose() { var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(mockLibuv, transportContext, null); - var thread = new LibuvThread(transport); + var thread = new LibuvThread(mockLibuv, transportContext); var ranOne = false; var ranTwo = false; var ranThree = false; From a105f8b89cc2cda86db8bd5e4c8c9b153481da0f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 25 May 2019 01:50:11 -0700 Subject: [PATCH 41/78] Fixed one more test --- src/Servers/Kestrel/Core/src/ListenOptions.cs | 2 +- .../Transport.Libuv/src/Internal/Listener.cs | 2 +- .../src/Internal/ListenerContext.cs | 4 +- .../src/Internal/ListenerSecondary.cs | 3 +- .../test/LibuvConnectionTests.cs | 8 +- .../test/ListenerPrimaryTests.cs | 761 ++++++++++-------- 6 files changed, 428 insertions(+), 352 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index b9088ec014a8..1ee395856d04 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -42,7 +42,7 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) EndPoint = new FileHandleEndPoint(fileHandle, handleType); } - internal EndPoint EndPoint { get; set; } + public EndPoint EndPoint { get; internal set; } // IPEndPoint is mutable so port 0 can be updated to the bound port. /// diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 9738c53bca17..f88b7b23b050 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -192,7 +192,7 @@ private void OnConnection(UvStreamHandle listenSocket, int status) protected virtual void DispatchConnection(UvStreamHandle socket) { - HandleConnectionAsync(socket); + HandleConnection(socket); } public virtual async Task DisposeAsync() diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index e1ade4ee2883..96863f13ebcb 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -64,7 +64,7 @@ protected UvStreamHandle CreateAcceptSocket() } } - protected internal void HandleConnectionAsync(UvStreamHandle socket) + protected internal void HandleConnection(UvStreamHandle socket) { try { @@ -93,7 +93,7 @@ protected internal void HandleConnectionAsync(UvStreamHandle socket) } catch (Exception ex) { - TransportContext.Log.LogCritical(ex, $"Unexpected exception in {nameof(ListenerContext)}.{nameof(HandleConnectionAsync)}."); + TransportContext.Log.LogCritical(ex, $"Unexpected exception in {nameof(ListenerContext)}.{nameof(HandleConnection)}."); } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs index bcb432449176..4fa1537918d5 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -153,7 +152,7 @@ private void ReadStartCallback(UvStreamHandle handle, int status) { DispatchPipe.Accept(acceptSocket); - HandleConnectionAsync(acceptSocket); + HandleConnection(acceptSocket); } catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode)) { diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs index 3cb5ea902c05..d1ff9b380c4e 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs @@ -33,7 +33,7 @@ public async Task DoesNotEndConnectionOnZeroRead() await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - listenerContext.HandleConnectionAsync(socket); + listenerContext.HandleConnection(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); @@ -82,7 +82,7 @@ public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - listenerContext.HandleConnectionAsync(socket); + listenerContext.HandleConnection(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); @@ -145,7 +145,7 @@ public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketClos await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - listenerContext.HandleConnectionAsync(socket); + listenerContext.HandleConnection(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); @@ -204,7 +204,7 @@ public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCa await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); - listenerContext.HandleConnectionAsync(socket); + listenerContext.HandleConnection(socket); var ignored = new LibuvFunctions.uv_buf_t(); mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index f5bb5141c4cc..1fb9d73d342c 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -1,342 +1,419 @@ -//// 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.Generic; -//using System.IO; -//using System.Linq; -//using System.Net; -//using System.Threading.Tasks; -//using Microsoft.AspNetCore.Http; -//using Microsoft.AspNetCore.Connections; -//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.Libuv.Internal; -//using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; -//using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; -//using Microsoft.AspNetCore.Testing; -//using Microsoft.Extensions.Logging; -//using Xunit; - -//namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests -//{ -// public class ListenerPrimaryTests -// { -// [Fact] -// public async Task ConnectionsGetRoundRobinedToSecondaryListeners() -// { -// var libuv = new LibuvFunctions(); - -// var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - -// var serviceContextPrimary = new TestServiceContext(); -// var transportContextPrimary = new TestLibuvTransportContext(); -// var builderPrimary = new ConnectionBuilder(); -// builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); -// transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - -// var serviceContextSecondary = new TestServiceContext(); -// var builderSecondary = new ConnectionBuilder(); -// builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); -// var transportContextSecondary = new TestLibuvTransportContext(); -// transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - -// var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - -// var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); -// var pipeMessage = Guid.NewGuid().ToByteArray(); - -// // Start primary listener -// var libuvThreadPrimary = new LibuvThread(libuvTransport); -// await libuvThreadPrimary.StartAsync(); -// var listenerPrimary = new ListenerPrimary(transportContextPrimary); -// await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); -// var address = GetUri(listenOptions); - -// // Until a secondary listener is added, TCP connections get dispatched directly -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - -// var listenerCount = listenerPrimary.UvPipeCount; -// // Add secondary listener -// var libuvThreadSecondary = new LibuvThread(libuvTransport); -// await libuvThreadSecondary.StartAsync(); -// var listenerSecondary = new ListenerSecondary(transportContextSecondary); -// await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); - -// var maxWait = Task.Delay(TestConstants.DefaultTimeout); -// // wait for ListenerPrimary.ReadCallback to add the secondary pipe -// while (listenerPrimary.UvPipeCount == listenerCount) -// { -// var completed = await Task.WhenAny(maxWait, Task.Delay(100)); -// if (ReferenceEquals(completed, maxWait)) -// { -// throw new TimeoutException("Timed out waiting for secondary listener to become available"); -// } -// } - -// // Once a secondary listener is added, TCP connections start getting dispatched to it -// await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); - -// // TCP connections will still get round-robined to the primary listener -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - -// await listenerSecondary.DisposeAsync(); -// await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - -// await listenerPrimary.DisposeAsync(); -// await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); -// } - -// // https://github.com/aspnet/KestrelHttpServer/issues/1182 -// [Fact] -// public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() -// { -// var libuv = new LibuvFunctions(); -// var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); -// var logger = new TestApplicationErrorLogger(); - -// var serviceContextPrimary = new TestServiceContext(); -// var builderPrimary = new ConnectionBuilder(); -// builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); -// var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; -// transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - -// var serviceContextSecondary = new TestServiceContext -// { -// DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, -// ServerOptions = serviceContextPrimary.ServerOptions, -// Scheduler = serviceContextPrimary.Scheduler, -// HttpParser = serviceContextPrimary.HttpParser, -// }; -// var builderSecondary = new ConnectionBuilder(); -// builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); -// var transportContextSecondary = new TestLibuvTransportContext(); -// transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - -// var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - -// var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); -// var pipeMessage = Guid.NewGuid().ToByteArray(); - -// // Start primary listener -// var libuvThreadPrimary = new LibuvThread(libuvTransport); -// await libuvThreadPrimary.StartAsync(); -// var listenerPrimary = new ListenerPrimary(transportContextPrimary); -// await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); -// var address = GetUri(listenOptions); - -// // Add secondary listener -// var libuvThreadSecondary = new LibuvThread(libuvTransport); -// await libuvThreadSecondary.StartAsync(); -// var listenerSecondary = new ListenerSecondary(transportContextSecondary); -// await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); - -// // TCP Connections get round-robined -// await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - -// // Create a pipe connection and keep it open without sending any data -// var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); -// var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger()); -// var pipe = new UvPipeHandle(connectionTrace); - -// libuvThreadPrimary.Post(_ => -// { -// var connectReq = new UvConnectRequest(connectionTrace); - -// pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle); -// connectReq.Init(libuvThreadPrimary); - -// connectReq.Connect( -// pipe, -// pipeName, -// (req, status, ex, __) => -// { -// req.Dispose(); - -// if (ex != null) -// { -// connectTcs.SetException(ex); -// } -// else -// { -// connectTcs.SetResult(null); -// } -// }, -// null); -// }, (object)null); - -// await connectTcs.Task; - -// // TCP connections will still get round-robined between only the two listeners -// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - -// await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null); - -// // Wait up to 10 seconds for error to be logged -// for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) -// { -// await Task.Delay(100); -// } - -// // Same for after the non-listener pipe connection is closed -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - -// await listenerSecondary.DisposeAsync(); -// await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - -// await listenerPrimary.DisposeAsync(); -// await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - -// Assert.Equal(0, logger.TotalErrorsLogged); - -// var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); -// Assert.Equal(LogLevel.Debug, logMessage.LogLevel); -// } - - -// [Fact] -// public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() -// { -// var libuv = new LibuvFunctions(); -// var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - -// var logger = new TestApplicationErrorLogger(); - -// var serviceContextPrimary = new TestServiceContext(); -// var builderPrimary = new ConnectionBuilder(); -// builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); -// var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; -// transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - -// var serviceContextSecondary = new TestServiceContext -// { -// DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, -// ServerOptions = serviceContextPrimary.ServerOptions, -// Scheduler = serviceContextPrimary.Scheduler, -// HttpParser = serviceContextPrimary.HttpParser, -// }; -// var builderSecondary = new ConnectionBuilder(); -// builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); -// var transportContextSecondary = new TestLibuvTransportContext(); -// transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - -// var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - -// var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); -// var pipeMessage = Guid.NewGuid().ToByteArray(); - -// // Start primary listener -// var libuvThreadPrimary = new LibuvThread(libuvTransport); -// await libuvThreadPrimary.StartAsync(); -// var listenerPrimary = new ListenerPrimary(transportContextPrimary); -// await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); -// var address = GetUri(listenOptions); - -// // Add secondary listener with wrong pipe message -// var libuvThreadSecondary = new LibuvThread(libuvTransport); -// await libuvThreadSecondary.StartAsync(); -// var listenerSecondary = new ListenerSecondary(transportContextSecondary); -// await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary); - -// // Wait up to 10 seconds for error to be logged -// for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) -// { -// await Task.Delay(100); -// } - -// // TCP Connections don't get round-robined -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); -// Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - -// await listenerSecondary.DisposeAsync(); -// await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - -// await listenerPrimary.DisposeAsync(); -// await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - -// Assert.Equal(1, logger.TotalErrorsLogged); -// var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error); -// Assert.IsType(errorMessage.Exception); -// Assert.Contains("Bad data", errorMessage.Exception.ToString()); -// } - -// private static async Task AssertResponseEventually( -// Uri address, -// string expected, -// string[] allowed = null, -// int maxRetries = 100, -// int retryDelay = 100) -// { -// for (var i = 0; i < maxRetries; i++) -// { -// var response = await HttpClientSlim.GetStringAsync(address); -// if (response == expected) -// { -// return; -// } - -// if (allowed != null) -// { -// Assert.Contains(response, allowed); -// } - -// await Task.Delay(retryDelay); -// } - -// Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries."); -// } - -// private static Uri GetUri(ListenOptions options) -// { -// if (options.Type != ListenType.IPEndPoint) -// { -// throw new InvalidOperationException($"Could not determine a proper URI for options with Type {options.Type}"); -// } - -// var scheme = options.ConnectionAdapters.Any(f => f.IsHttps) -// ? "https" -// : "http"; - -// return new Uri($"{scheme}://{options.IPEndPoint}"); -// } - -// private class ConnectionBuilder : IConnectionBuilder -// { -// private readonly List> _components = new List>(); - -// public IServiceProvider ApplicationServices { get; set; } - -// public IConnectionBuilder Use(Func middleware) -// { -// _components.Add(middleware); -// return this; -// } - -// public ConnectionDelegate Build() -// { -// ConnectionDelegate app = context => -// { -// return Task.CompletedTask; -// }; - -// for (int i = _components.Count - 1; i >= 0; i--) -// { -// var component = _components[i]; -// app = component(app); -// } - -// return app; -// } -// } -// } -//} +// 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.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Connections; +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.Libuv.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Xunit; +using System.Threading; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests +{ + public class ListenerPrimaryTests + { + [Fact] + public async Task ConnectionsGetRoundRobinedToSecondaryListeners() + { + var libuv = new LibuvFunctions(); + + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + + var transportContextPrimary = new TestLibuvTransportContext(); + var transportContextSecondary = new TestLibuvTransportContext(); + + var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); + + // Start primary listener + var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary); + await libuvThreadPrimary.StartAsync(); + var listenerPrimary = new ListenerPrimary(transportContextPrimary); + await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary); + var address = GetUri(listenerPrimary.EndPoint); + + var acceptTask = listenerPrimary.AcceptAsync().AsTask(); + using (var socket = await HttpClientSlim.GetSocket(address)) + { + await (await acceptTask.DefaultTimeout()).DisposeAsync(); + } + + acceptTask = listenerPrimary.AcceptAsync().AsTask(); + using (var socket = await HttpClientSlim.GetSocket(address)) + { + await (await acceptTask.DefaultTimeout()).DisposeAsync(); + } + + var listenerCount = listenerPrimary.UvPipeCount; + // Add secondary listener + var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary); + await libuvThreadSecondary.StartAsync(); + var listenerSecondary = new ListenerSecondary(transportContextSecondary); + await listenerSecondary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadSecondary); + + var maxWait = Task.Delay(TestConstants.DefaultTimeout); + // wait for ListenerPrimary.ReadCallback to add the secondary pipe + while (listenerPrimary.UvPipeCount == listenerCount) + { + var completed = await Task.WhenAny(maxWait, Task.Delay(100)); + if (ReferenceEquals(completed, maxWait)) + { + throw new TimeoutException("Timed out waiting for secondary listener to become available"); + } + } + + // Once a secondary listener is added, TCP connections start getting dispatched to it + // This returns the incomplete primary task after the secondary listener got the last + // connection + var primary = await WaitForSecondaryListener(); + + async Task> WaitForSecondaryListener() + { + int maxRetries = 100; + int retryDelay = 100; + + Task primary = null; + Task secondary = null; + + for (var i = 0; i < maxRetries; i++) + { + primary ??= listenerPrimary.AcceptAsync().AsTask(); + secondary ??= listenerSecondary.AcceptAsync().AsTask(); + + using var _ = await HttpClientSlim.GetSocket(address); + + var task = await Task.WhenAny(primary, secondary); + + if (task == secondary) + { + // Dispose this connection now that we know the seconary listener is working + await (await secondary).DisposeAsync(); + + // Return the primary task (it should be incomplete), we do this so that we can + return primary; + } + else + { + // Dispose the connection + await (await primary).DisposeAsync(); + + primary = null; + } + + await Task.Delay(retryDelay); + } + + Assert.True(false, $"'{address}' failed to get queued connection in secondary listener in {maxRetries} retries."); + return null; + } + + // TCP connections will still get round-robined to the primary listener + ListenerContext currentListener = listenerSecondary; + Task expected = primary; + + for (int i = 0; i < 4; i++) + { + if (currentListener == listenerPrimary) + { + expected ??= listenerSecondary.AcceptAsync().AsTask(); + currentListener = listenerSecondary; + } + else + { + expected ??= listenerPrimary.AcceptAsync().AsTask(); + currentListener = listenerPrimary; + } + + using var socket = await HttpClientSlim.GetSocket(address); + + await using var connection = await expected.DefaultTimeout(); + + expected = null; + } + + await listenerSecondary.DisposeAsync(); + + await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + + await listenerPrimary.DisposeAsync(); + await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + } + + //// https://github.com/aspnet/KestrelHttpServer/issues/1182 + //[Fact] + //public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() + //{ + // var libuv = new LibuvFunctions(); + // var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + // var logger = new TestApplicationErrorLogger(); + + // var serviceContextPrimary = new TestServiceContext(); + // var builderPrimary = new ConnectionBuilder(); + // builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); + // var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; + // transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); + + // var serviceContextSecondary = new TestServiceContext + // { + // DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, + // ServerOptions = serviceContextPrimary.ServerOptions, + // Scheduler = serviceContextPrimary.Scheduler, + // HttpParser = serviceContextPrimary.HttpParser, + // }; + // var builderSecondary = new ConnectionBuilder(); + // builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); + // var transportContextSecondary = new TestLibuvTransportContext(); + // transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); + + // var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); + + // var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + // var pipeMessage = Guid.NewGuid().ToByteArray(); + + // // Start primary listener + // var libuvThreadPrimary = new LibuvThread(libuvTransport); + // await libuvThreadPrimary.StartAsync(); + // var listenerPrimary = new ListenerPrimary(transportContextPrimary); + // await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); + // var address = GetUri(listenOptions); + + // // Add secondary listener + // var libuvThreadSecondary = new LibuvThread(libuvTransport); + // await libuvThreadSecondary.StartAsync(); + // var listenerSecondary = new ListenerSecondary(transportContextSecondary); + // await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); + + // // TCP Connections get round-robined + // await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + + // // Create a pipe connection and keep it open without sending any data + // var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + // var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger()); + // var pipe = new UvPipeHandle(connectionTrace); + + // libuvThreadPrimary.Post(_ => + // { + // var connectReq = new UvConnectRequest(connectionTrace); + + // pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle); + // connectReq.Init(libuvThreadPrimary); + + // connectReq.Connect( + // pipe, + // pipeName, + // (req, status, ex, __) => + // { + // req.Dispose(); + + // if (ex != null) + // { + // connectTcs.SetException(ex); + // } + // else + // { + // connectTcs.SetResult(null); + // } + // }, + // null); + // }, (object)null); + + // await connectTcs.Task; + + // // TCP connections will still get round-robined between only the two listeners + // Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + // Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); + + // await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null); + + // // Wait up to 10 seconds for error to be logged + // for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) + // { + // await Task.Delay(100); + // } + + // // Same for after the non-listener pipe connection is closed + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + // Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + + // await listenerSecondary.DisposeAsync(); + // await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + + // await listenerPrimary.DisposeAsync(); + // await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + + // Assert.Equal(0, logger.TotalErrorsLogged); + + // var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); + // Assert.Equal(LogLevel.Debug, logMessage.LogLevel); + //} + + + //[Fact] + //public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() + //{ + // var libuv = new LibuvFunctions(); + // var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + // var logger = new TestApplicationErrorLogger(); + + // var serviceContextPrimary = new TestServiceContext(); + // var builderPrimary = new ConnectionBuilder(); + // builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); + // var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; + // transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); + + // var serviceContextSecondary = new TestServiceContext + // { + // DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, + // ServerOptions = serviceContextPrimary.ServerOptions, + // Scheduler = serviceContextPrimary.Scheduler, + // HttpParser = serviceContextPrimary.HttpParser, + // }; + // var builderSecondary = new ConnectionBuilder(); + // builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); + // var transportContextSecondary = new TestLibuvTransportContext(); + // transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); + + // var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); + + // var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + // var pipeMessage = Guid.NewGuid().ToByteArray(); + + // // Start primary listener + // var libuvThreadPrimary = new LibuvThread(libuvTransport); + // await libuvThreadPrimary.StartAsync(); + // var listenerPrimary = new ListenerPrimary(transportContextPrimary); + // await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); + // var address = GetUri(listenOptions); + + // // Add secondary listener with wrong pipe message + // var libuvThreadSecondary = new LibuvThread(libuvTransport); + // await libuvThreadSecondary.StartAsync(); + // var listenerSecondary = new ListenerSecondary(transportContextSecondary); + // await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary); + + // // Wait up to 10 seconds for error to be logged + // for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) + // { + // await Task.Delay(100); + // } + + // // TCP Connections don't get round-robined + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); + + // await listenerSecondary.DisposeAsync(); + // await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + + // await listenerPrimary.DisposeAsync(); + // await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + + // Assert.Equal(1, logger.TotalErrorsLogged); + // var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error); + // Assert.IsType(errorMessage.Exception); + // Assert.Contains("Bad data", errorMessage.Exception.ToString()); + //} + + private static async Task AssertResponseEventually( + Uri address, + string expected, + string[] allowed = null, + int maxRetries = 100, + int retryDelay = 100) + { + for (var i = 0; i < maxRetries; i++) + { + var response = await HttpClientSlim.GetStringAsync(address); + if (response == expected) + { + return; + } + + if (allowed != null) + { + Assert.Contains(response, allowed); + } + + await Task.Delay(retryDelay); + } + + Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries."); + } + + private static Uri GetUri(EndPoint endpoint) + { + return new Uri($"http://{endpoint}"); + } + + private class LibuvConnectionListener : IConnectionListener + { + private readonly Listener _listener; + + public LibuvConnectionListener(Listener listener) + { + _listener = listener; + } + public EndPoint EndPoint => _listener.EndPoint; + + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + return await _listener.AcceptAsync(); + } + + public ValueTask DisposeAsync() + { + return StopAsync(default); + } + + public ValueTask StopAsync(CancellationToken cancellationToken = default) + { + return new ValueTask(_listener.DisposeAsync()); + } + } + + private class ConnectionBuilder : IConnectionBuilder + { + private readonly List> _components = new List>(); + + public IServiceProvider ApplicationServices { get; set; } + + public IConnectionBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public ConnectionDelegate Build() + { + ConnectionDelegate app = context => + { + return Task.CompletedTask; + }; + + for (int i = _components.Count - 1; i >= 0; i--) + { + var component = _components[i]; + app = component(app); + } + + return app; + } + } + } +} From 5ecfb4ffe72f0eeee0cc346aab61ee42d36b2a7d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 25 May 2019 12:13:13 -0700 Subject: [PATCH 42/78] Wait for connection close to fire before disposing the token --- .../src/Internal/LibuvConnection.cs | 17 +- .../test/ListenerPrimaryTests.cs | 148 +++++------------- .../src/Internal/SocketConnection.cs | 19 ++- 3 files changed, 73 insertions(+), 111 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 5ca906d41c0a..3bbb31bc4fad 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -125,7 +125,22 @@ private async Task StartCore() // We're done with the socket now _socket.Dispose(); - ThreadPool.UnsafeQueueUserWorkItem(state => ((LibuvConnection)state).CancelConnectionClosedToken(), this); + + // Fire the connection closed token and wait for it to complete + var waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + (var connection, var tcs) = state; + + connection.CancelConnectionClosedToken(); + + tcs.TrySetResult(null); + }, + (this, waitForConnectionClosedTcs), + preferLocal: false); + + await waitForConnectionClosedTcs.Task; } } catch (Exception e) diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index 1fb9d73d342c..5722038c38de 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -263,97 +263,57 @@ async Task> WaitForSecondaryListener() //} - //[Fact] - //public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() - //{ - // var libuv = new LibuvFunctions(); - // var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - - // var logger = new TestApplicationErrorLogger(); - - // var serviceContextPrimary = new TestServiceContext(); - // var builderPrimary = new ConnectionBuilder(); - // builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); - // var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; - // transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - - // var serviceContextSecondary = new TestServiceContext - // { - // DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, - // ServerOptions = serviceContextPrimary.ServerOptions, - // Scheduler = serviceContextPrimary.Scheduler, - // HttpParser = serviceContextPrimary.HttpParser, - // }; - // var builderSecondary = new ConnectionBuilder(); - // builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); - // var transportContextSecondary = new TestLibuvTransportContext(); - // transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - - // var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - - // var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); - // var pipeMessage = Guid.NewGuid().ToByteArray(); - - // // Start primary listener - // var libuvThreadPrimary = new LibuvThread(libuvTransport); - // await libuvThreadPrimary.StartAsync(); - // var listenerPrimary = new ListenerPrimary(transportContextPrimary); - // await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - // var address = GetUri(listenOptions); - - // // Add secondary listener with wrong pipe message - // var libuvThreadSecondary = new LibuvThread(libuvTransport); - // await libuvThreadSecondary.StartAsync(); - // var listenerSecondary = new ListenerSecondary(transportContextSecondary); - // await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary); + [Fact] + public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() + { + var libuv = new LibuvFunctions(); + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); - // // Wait up to 10 seconds for error to be logged - // for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) - // { - // await Task.Delay(100); - // } + var logger = new TestApplicationErrorLogger(); - // // TCP Connections don't get round-robined - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - // await listenerSecondary.DisposeAsync(); - // await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; + var transportContextSecondary = new TestLibuvTransportContext(); + + var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); - // await listenerPrimary.DisposeAsync(); - // await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + // Start primary listener + var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary); + await libuvThreadPrimary.StartAsync(); + var listenerPrimary = new ListenerPrimary(transportContextPrimary); + await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary); + var address = GetUri(listenerPrimary.EndPoint); - // Assert.Equal(1, logger.TotalErrorsLogged); - // var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error); - // Assert.IsType(errorMessage.Exception); - // Assert.Contains("Bad data", errorMessage.Exception.ToString()); - //} + // Add secondary listener with wrong pipe message + var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary); + await libuvThreadSecondary.StartAsync(); + var listenerSecondary = new ListenerSecondary(transportContextSecondary); + await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), endpoint, libuvThreadSecondary); - private static async Task AssertResponseEventually( - Uri address, - string expected, - string[] allowed = null, - int maxRetries = 100, - int retryDelay = 100) - { - for (var i = 0; i < maxRetries; i++) + // Wait up to 10 seconds for error to be logged + for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) { - var response = await HttpClientSlim.GetStringAsync(address); - if (response == expected) - { - return; - } + await Task.Delay(100); + } - if (allowed != null) - { - Assert.Contains(response, allowed); - } + // TCP Connections don't get round-robined. This should time out if the request goes to the secondary listener + for (int i = 0; i < 3; i++) + { + using var socket = await HttpClientSlim.GetSocket(address); - await Task.Delay(retryDelay); + await using var connection = await listenerPrimary.AcceptAsync().AsTask().DefaultTimeout(); } - Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries."); + await listenerSecondary.DisposeAsync(); + await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); + + await listenerPrimary.DisposeAsync(); + await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + + Assert.Equal(1, logger.TotalErrorsLogged); + var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error); + Assert.IsType(errorMessage.Exception); + Assert.Contains("Bad data", errorMessage.Exception.ToString()); } private static Uri GetUri(EndPoint endpoint) @@ -361,32 +321,6 @@ private static Uri GetUri(EndPoint endpoint) return new Uri($"http://{endpoint}"); } - private class LibuvConnectionListener : IConnectionListener - { - private readonly Listener _listener; - - public LibuvConnectionListener(Listener listener) - { - _listener = listener; - } - public EndPoint EndPoint => _listener.EndPoint; - - public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) - { - return await _listener.AcceptAsync(); - } - - public ValueTask DisposeAsync() - { - return StopAsync(default); - } - - public ValueTask StopAsync(CancellationToken cancellationToken = default) - { - return new ValueTask(_listener.DisposeAsync()); - } - } - private class ConnectionBuilder : IConnectionBuilder { private readonly List> _components = new List>(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index aa857874d09d..83459863ec84 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -89,13 +89,26 @@ private async Task StartAsync() _receiver.Dispose(); _sender.Dispose(); - ThreadPool.UnsafeQueueUserWorkItem(state => ((SocketConnection)state).CancelConnectionClosedToken(), this); + + // Fire the connection closed token and wait for it to complete + var waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + (var connection, var tcs) = state; + + connection.CancelConnectionClosedToken(); + + tcs.TrySetResult(null); + }, + (this, waitForConnectionClosedTcs), + preferLocal: false); + + await waitForConnectionClosedTcs.Task; } catch (Exception ex) { _trace.LogError(0, ex, $"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}."); - - // REVIEW: Should this dispose the socket? } } From 2e2e2b5dada8c4813a71ce5bf6c2e665ceeb993e Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 25 May 2019 12:32:48 -0700 Subject: [PATCH 43/78] Make last libuv test work --- .../test/ListenerPrimaryTests.cs | 315 +++++++++--------- 1 file changed, 150 insertions(+), 165 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index 5722038c38de..88858881d640 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -7,18 +7,13 @@ using System.Linq; using System.Net; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Connections; -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.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Xunit; -using System.Threading; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests { @@ -77,190 +72,116 @@ public async Task ConnectionsGetRoundRobinedToSecondaryListeners() // Once a secondary listener is added, TCP connections start getting dispatched to it // This returns the incomplete primary task after the secondary listener got the last // connection - var primary = await WaitForSecondaryListener(); + var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary); - async Task> WaitForSecondaryListener() - { - int maxRetries = 100; - int retryDelay = 100; + // TCP connections will still get round-robined to the primary listener + ListenerContext currentListener = listenerSecondary; + Task expected = primary; - Task primary = null; - Task secondary = null; + await AssertRoundRobin(address, listenerPrimary, listenerSecondary, currentListener, expected); - for (var i = 0; i < maxRetries; i++) - { - primary ??= listenerPrimary.AcceptAsync().AsTask(); - secondary ??= listenerSecondary.AcceptAsync().AsTask(); + await listenerSecondary.DisposeAsync(); - using var _ = await HttpClientSlim.GetSocket(address); + await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - var task = await Task.WhenAny(primary, secondary); + await listenerPrimary.DisposeAsync(); + await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); + } - if (task == secondary) - { - // Dispose this connection now that we know the seconary listener is working - await (await secondary).DisposeAsync(); + // https://github.com/aspnet/KestrelHttpServer/issues/1182 + [Fact] + public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() + { + var libuv = new LibuvFunctions(); + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + var logger = new TestApplicationErrorLogger(); - // Return the primary task (it should be incomplete), we do this so that we can - return primary; - } - else - { - // Dispose the connection - await (await primary).DisposeAsync(); + var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; + var transportContextSecondary = new TestLibuvTransportContext(); - primary = null; - } + var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); - await Task.Delay(retryDelay); - } + // Start primary listener + var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary); + await libuvThreadPrimary.StartAsync(); + var listenerPrimary = new ListenerPrimary(transportContextPrimary); + await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary); + var address = GetUri(listenerPrimary.EndPoint); + + // Add secondary listener + var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary); + await libuvThreadSecondary.StartAsync(); + var listenerSecondary = new ListenerSecondary(transportContextSecondary); + await listenerSecondary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadSecondary); - Assert.True(false, $"'{address}' failed to get queued connection in secondary listener in {maxRetries} retries."); - return null; + // TCP Connections get round-robined + var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary); + + // Make sure the pending accept get yields + using (var socket = HttpClientSlim.GetSocket(address)) + { + await (await primary.DefaultTimeout()).DisposeAsync(); } - // TCP connections will still get round-robined to the primary listener - ListenerContext currentListener = listenerSecondary; - Task expected = primary; + // Create a pipe connection and keep it open without sending any data + var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger()); + var pipe = new UvPipeHandle(connectionTrace); - for (int i = 0; i < 4; i++) + libuvThreadPrimary.Post(_ => { - if (currentListener == listenerPrimary) - { - expected ??= listenerSecondary.AcceptAsync().AsTask(); - currentListener = listenerSecondary; - } - else - { - expected ??= listenerPrimary.AcceptAsync().AsTask(); - currentListener = listenerPrimary; - } + var connectReq = new UvConnectRequest(connectionTrace); - using var socket = await HttpClientSlim.GetSocket(address); + pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle); + connectReq.Init(libuvThreadPrimary); - await using var connection = await expected.DefaultTimeout(); + connectReq.Connect( + pipe, + pipeName, + (req, status, ex, __) => + { + req.Dispose(); - expected = null; + if (ex != null) + { + connectTcs.SetException(ex); + } + else + { + connectTcs.SetResult(null); + } + }, + null); + }, (object)null); + + await connectTcs.Task; + + // TCP connections will still get round-robined between only the two listeners + await AssertRoundRobin(address, listenerPrimary, listenerSecondary, listenerPrimary); + + await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null); + + // Wait up to 10 seconds for error to be logged + for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) + { + await Task.Delay(100); } - await listenerSecondary.DisposeAsync(); + // Same for after the non-listener pipe connection is closed + await AssertRoundRobin(address, listenerPrimary, listenerSecondary, listenerPrimary); + await listenerSecondary.DisposeAsync(); await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); await listenerPrimary.DisposeAsync(); await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - } - //// https://github.com/aspnet/KestrelHttpServer/issues/1182 - //[Fact] - //public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() - //{ - // var libuv = new LibuvFunctions(); - // var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - // var logger = new TestApplicationErrorLogger(); - - // var serviceContextPrimary = new TestServiceContext(); - // var builderPrimary = new ConnectionBuilder(); - // builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1); - // var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; - // transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build()); - - // var serviceContextSecondary = new TestServiceContext - // { - // DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, - // ServerOptions = serviceContextPrimary.ServerOptions, - // Scheduler = serviceContextPrimary.Scheduler, - // HttpParser = serviceContextPrimary.HttpParser, - // }; - // var builderSecondary = new ConnectionBuilder(); - // builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1); - // var transportContextSecondary = new TestLibuvTransportContext(); - // transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build()); - - // var libuvTransport = new LibuvConnectionListener(libuv, transportContextPrimary, listenOptions); - - // var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); - // var pipeMessage = Guid.NewGuid().ToByteArray(); - - // // Start primary listener - // var libuvThreadPrimary = new LibuvThread(libuvTransport); - // await libuvThreadPrimary.StartAsync(); - // var listenerPrimary = new ListenerPrimary(transportContextPrimary); - // await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - // var address = GetUri(listenOptions); - - // // Add secondary listener - // var libuvThreadSecondary = new LibuvThread(libuvTransport); - // await libuvThreadSecondary.StartAsync(); - // var listenerSecondary = new ListenerSecondary(transportContextSecondary); - // await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary); - - // // TCP Connections get round-robined - // await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" }); - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - // // Create a pipe connection and keep it open without sending any data - // var connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - // var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger()); - // var pipe = new UvPipeHandle(connectionTrace); - - // libuvThreadPrimary.Post(_ => - // { - // var connectReq = new UvConnectRequest(connectionTrace); - - // pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle); - // connectReq.Init(libuvThreadPrimary); - - // connectReq.Connect( - // pipe, - // pipeName, - // (req, status, ex, __) => - // { - // req.Dispose(); - - // if (ex != null) - // { - // connectTcs.SetException(ex); - // } - // else - // { - // connectTcs.SetResult(null); - // } - // }, - // null); - // }, (object)null); - - // await connectTcs.Task; - - // // TCP connections will still get round-robined between only the two listeners - // Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - // Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - - // await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null); - - // // Wait up to 10 seconds for error to be logged - // for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++) - // { - // await Task.Delay(100); - // } - - // // Same for after the non-listener pipe connection is closed - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - // Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); - // Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); - - // await listenerSecondary.DisposeAsync(); - // await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); - - // await listenerPrimary.DisposeAsync(); - // await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); - - // Assert.Equal(0, logger.TotalErrorsLogged); - - // var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); - // Assert.Equal(LogLevel.Debug, logMessage.LogLevel); - //} + Assert.Equal(0, logger.TotalErrorsLogged); + + var logMessage = logger.Messages.Single(m => m.Message == "An internal pipe was opened unexpectedly."); + Assert.Equal(LogLevel.Debug, logMessage.LogLevel); + } [Fact] @@ -273,7 +194,7 @@ public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() var transportContextPrimary = new TestLibuvTransportContext { Log = new LibuvTrace(logger) }; var transportContextSecondary = new TestLibuvTransportContext(); - + var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeMessage = Guid.NewGuid().ToByteArray(); @@ -316,6 +237,70 @@ public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() Assert.Contains("Bad data", errorMessage.Exception.ToString()); } + + private static async Task AssertRoundRobin(Uri address, ListenerPrimary listenerPrimary, ListenerSecondary listenerSecondary, ListenerContext currentListener, Task expected = null, int connections = 4) + { + for (int i = 0; i < connections; i++) + { + if (currentListener == listenerPrimary) + { + expected ??= listenerSecondary.AcceptAsync().AsTask(); + currentListener = listenerSecondary; + } + else + { + expected ??= listenerPrimary.AcceptAsync().AsTask(); + currentListener = listenerPrimary; + } + + using var socket = await HttpClientSlim.GetSocket(address); + + await using var connection = await expected.DefaultTimeout(); + + expected = null; + } + } + + private static async Task> WaitForSecondaryListener(Uri address, ListenerContext listenerPrimary, ListenerContext listenerSecondary) + { + int maxRetries = 100; + int retryDelay = 100; + + Task primary = null; + Task secondary = null; + + for (var i = 0; i < maxRetries; i++) + { + primary ??= listenerPrimary.AcceptAsync().AsTask(); + secondary ??= listenerSecondary.AcceptAsync().AsTask(); + + using var _ = await HttpClientSlim.GetSocket(address); + + var task = await Task.WhenAny(primary, secondary); + + if (task == secondary) + { + // Dispose this connection now that we know the seconary listener is working + await (await secondary).DisposeAsync(); + + // Return the primary task (it should be incomplete), we do this so that we can + return primary; + } + else + { + // Dispose the connection + await (await primary).DisposeAsync(); + + primary = null; + } + + await Task.Delay(retryDelay); + } + + Assert.True(false, $"'{address}' failed to get queued connection in secondary listener in {maxRetries} retries."); + return null; + } + private static Uri GetUri(EndPoint endpoint) { return new Uri($"http://{endpoint}"); From 85f16f9833450ac1d83f9ce0b8da2a90d3e459d3 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 25 May 2019 12:38:07 -0700 Subject: [PATCH 44/78] Wait for the token to fire before disposing --- .../InMemoryTransportConnection.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index 3315eb8c64f0..e20924c0a0df 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -70,14 +70,31 @@ public void OnClosed() }, this); } - public override ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { - _connectionClosedTokenSource.Dispose(); - Transport.Input.Complete(); Transport.Output.Complete(); - return base.DisposeAsync(); + if (_isClosed) + { + await CancellationTokenAsTask(_connectionClosedTokenSource.Token); + } + + _connectionClosedTokenSource.Dispose(); + + await base.DisposeAsync(); + } + + private static Task CancellationTokenAsTask(CancellationToken token) + { + if (token.IsCancellationRequested) + { + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(); + token.Register(() => tcs.SetResult(null)); + return tcs.Task; } // This piece of code allows us to wait until the PipeReader has been awaited on. From 9e5cd63521e7b5f41ff0dcc416494489fe50e828 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 25 May 2019 13:10:54 -0700 Subject: [PATCH 45/78] Transport is responsible for waiting on the token to fire - We can now call DisposeAsync in the accept loop --- .../Core/src/Internal/ConnectionDispatcher.cs | 39 ++++--------------- .../Infrastructure/KestrelConnection.cs | 15 ++++--- .../InMemoryTransportConnection.cs | 29 ++++---------- 3 files changed, 24 insertions(+), 59 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 58911ced4ac4..d7280301259a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -51,10 +51,7 @@ async Task AcceptConnectionsAsync() break; } - // Set a connection id if the transport didn't set one - connection.ConnectionId ??= CorrelationIdGenerator.GetNextId(); - - _ = OnConnection(connection); + _ = Execute(new KestrelConnection(connection, _serviceContext.Log)); } } catch (Exception) @@ -68,15 +65,6 @@ async Task AcceptConnectionsAsync() } } - // Internal for testing - private async Task OnConnection(ConnectionContext connection) - { - await using (connection) - { - await Execute(new KestrelConnection(connection, _serviceContext.Log)); - } - } - internal async Task Execute(KestrelConnection connection) { var id = Interlocked.Increment(ref _lastConnectionId); @@ -99,19 +87,20 @@ internal async Task Execute(KestrelConnection connection) { Log.LogCritical(0, ex, $"{nameof(ConnectionDispatcher)}.{nameof(Execute)}() {connectionContext.ConnectionId}"); } - - // TODO: Move this into the transport and have it wait for all connections to be disposed - // Wait for the transport to close - await CancellationTokenAsTask(connectionContext.ConnectionClosed); } } finally { - await connection.CompleteAsync(); + await connection.FireOnCompletedAsync(); Log.ConnectionStop(connectionContext.ConnectionId); KestrelEventSource.Log.ConnectionStop(connectionContext); + // Dispose the transport connection, this needs to happen before removing it from the + // connection manager so that we only signal completion of this connection after the transport + // is properly torn down. + await connection.TransportConnection.DisposeAsync(); + _serviceContext.ConnectionManager.RemoveConnection(id); } } @@ -125,19 +114,5 @@ private IDisposable BeginConnectionScope(ConnectionContext connectionContext) return null; } - - private static Task CancellationTokenAsTask(CancellationToken token) - { - if (token.IsCancellationRequested) - { - return Task.CompletedTask; - } - - // Transports already dispatch prior to tripping ConnectionClosed - // since application code can register to this token. - var tcs = new TaskCompletionSource(); - token.Register(state => ((TaskCompletionSource)state).SetResult(null), tcs); - return tcs.Task; - } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index ebbb6ab38e96..434679ca641c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -26,6 +26,9 @@ public KestrelConnection(ConnectionContext connectionContext, ILogger logger) { Logger = logger; TransportConnection = connectionContext; + + // Set a connection id if the transport didn't set one + TransportConnection.ConnectionId ??= CorrelationIdGenerator.GetNextId(); connectionContext.Features.Set(this); connectionContext.Features.Set(this); connectionContext.Features.Set(this); @@ -38,11 +41,6 @@ public KestrelConnection(ConnectionContext connectionContext, ILogger logger) public CancellationToken ConnectionClosedRequested { get; set; } public Task ExecutionTask => _completionTcs.Task; - public void Complete() - { - _completionTcs.TrySetResult(null); - } - public void TickHeartbeat() { lock (_heartbeatLock) @@ -86,7 +84,7 @@ void IConnectionCompleteFeature.OnCompleted(Func callback, object _onCompleted.Push(new KeyValuePair, object>(callback, state)); } - public Task CompleteAsync() + public Task FireOnCompletedAsync() { if (_completed) { @@ -161,5 +159,10 @@ public void RequestClose() // swallow the exception and no-op } } + + public void Complete() + { + _completionTcs.TrySetResult(null); + } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index e20924c0a0df..d291af01c03d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -21,6 +21,7 @@ internal class InMemoryTransportConnection : TransportConnection private readonly ILogger _logger; private bool _isClosed; + private readonly TaskCompletionSource _waitForCloseTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger, PipeScheduler scheduler = null) { @@ -65,9 +66,12 @@ public void OnClosed() ThreadPool.UnsafeQueueUserWorkItem(state => { - var self = (InMemoryTransportConnection)state; - self._connectionClosedTokenSource.Cancel(); - }, this); + state._connectionClosedTokenSource.Cancel(); + + state._waitForCloseTcs.TrySetResult(null); + }, + this, + preferLocal: false); } public override async ValueTask DisposeAsync() @@ -75,26 +79,9 @@ public override async ValueTask DisposeAsync() Transport.Input.Complete(); Transport.Output.Complete(); - if (_isClosed) - { - await CancellationTokenAsTask(_connectionClosedTokenSource.Token); - } + await _waitForCloseTcs.Task; _connectionClosedTokenSource.Dispose(); - - await base.DisposeAsync(); - } - - private static Task CancellationTokenAsTask(CancellationToken token) - { - if (token.IsCancellationRequested) - { - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(); - token.Register(() => tcs.SetResult(null)); - return tcs.Task; } // This piece of code allows us to wait until the PipeReader has been awaited on. From c5b27b2cdc8eb01236f454b31f50f653c558d864 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 25 May 2019 14:31:11 -0700 Subject: [PATCH 46/78] Wrote a test for unaccepted connections --- .../src/Internal/LibuvConnectionListener.cs | 104 ++++++++++-------- .../Transport.Libuv/src/Internal/Listener.cs | 3 +- .../src/Internal/ListenerContext.cs | 4 +- .../src/Internal/ListenerSecondary.cs | 5 +- .../test/LibuvTransportTests.cs | 104 ++++++++++++------ 5 files changed, 141 insertions(+), 79 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 8ca9cf640587..cfee67eee689 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; @@ -19,6 +18,8 @@ internal class LibuvConnectionListener : IConnectionListener { private readonly List _listeners = new List(); private IAsyncEnumerator _acceptEnumerator; + private bool _stopped; + private bool _disposed; public LibuvConnectionListener(LibuvTransportContext context, EndPoint endPoint) : this(new LibuvFunctions(), context, endPoint) @@ -43,7 +44,54 @@ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, public EndPoint EndPoint { get; set; } - public async Task StopThreadsAsync() + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().FullName); + } + + if (await _acceptEnumerator.MoveNextAsync()) + { + return _acceptEnumerator.Current; + } + + // null means we're done... + return null; + } + + public async ValueTask StopAsync(CancellationToken cancellationToken = default) + { + await UnbindAsync().ConfigureAwait(false); + } + + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + _disposed = true; + + await UnbindAsync().ConfigureAwait(false); + + if (_acceptEnumerator != null) + { + while (await _acceptEnumerator.MoveNextAsync()) + { + _acceptEnumerator.Current.Abort(); + } + + await _acceptEnumerator.DisposeAsync(); + } + + _listeners.Clear(); + + await StopThreadsAsync().ConfigureAwait(false); + } + + internal async Task StopThreadsAsync() { try { @@ -71,7 +119,7 @@ await Task.WhenAll(Threads.Select(thread => thread.StopAsync(TimeSpan.FromSecond #endif } - public async Task BindAsync() + internal async Task BindAsync() { // TODO: Move thread management to LibuvTransportFactory // TODO: Split endpoint management from thread management @@ -143,7 +191,7 @@ private async IAsyncEnumerator AcceptConnections() while (remainingSlots > 0) { // Calling GetAwaiter().GetResult() is safe because we know the task is completed - (LibuvConnection connection, int slot) = (await Task.WhenAny(slots)).GetAwaiter().GetResult(); + (var connection, var slot) = (await Task.WhenAny(slots)).GetAwaiter().GetResult(); // If the connection is null then the listener was closed if (connection == null) @@ -166,58 +214,26 @@ private async IAsyncEnumerator AcceptConnections() } } - public async Task UnbindAsync() + internal async Task UnbindAsync() { + if (_stopped) + { + return; + } + + _stopped = true; + var disposeTasks = _listeners.Select(listener => ((IAsyncDisposable)listener).DisposeAsync()).ToArray(); if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false)) { Log.LogError(0, null, "Disposing listeners failed"); } - - _listeners.Clear(); } private static async Task WaitAsync(Task task, TimeSpan timeout) { return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task; } - - public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) - { - if (await _acceptEnumerator.MoveNextAsync()) - { - return _acceptEnumerator.Current; - } - - // null means we're done... - return null; - } - - public async ValueTask StopAsync(CancellationToken cancellationToken) - { - await UnbindAsync().ConfigureAwait(false); - } - - public async ValueTask DisposeAsync() - { - // TODO: ConfigureAwait - await UnbindAsync().ConfigureAwait(false); - - if (_acceptEnumerator != null) - { - // TODO: Log how many connections were unaccepted - if (await _acceptEnumerator.MoveNextAsync().ConfigureAwait(false)) - { - // Abort the connection - _acceptEnumerator.Current.Abort(); - } - - // Dispose the enumerator - await _acceptEnumerator.DisposeAsync().ConfigureAwait(false); - } - - await StopThreadsAsync().ConfigureAwait(false); - } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index f88b7b23b050..f341c44d8684 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -197,7 +197,6 @@ protected virtual void DispatchConnection(UvStreamHandle socket) public virtual async Task DisposeAsync() { - StopAcceptingConnections(); // Ensure the event loop is still running. // If the event loop isn't running and we try to wait on this Post // to complete, then LibuvTransport will never be disposed and @@ -210,6 +209,8 @@ await Thread.PostAsync(listener => listener._closed = true; + listener.StopAcceptingConnections(); + }, this).ConfigureAwait(false); } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index 96863f13ebcb..ff89a8aed7aa 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.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.IO.Pipelines; using System.Net; using System.Net.Sockets; @@ -89,7 +90,8 @@ protected internal void HandleConnection(UvStreamHandle socket) var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions); connection.Start(); - _acceptQueue.Writer.TryWrite(connection); + bool accepted = _acceptQueue.Writer.TryWrite(connection); + Debug.Assert(accepted, "The connection was not written to the channel!"); } catch (Exception ex) { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs index 4fa1537918d5..477d391f8897 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs @@ -177,7 +177,6 @@ private void FreeBuffer() public async Task DisposeAsync() { - StopAcceptingConnections(); // Ensure the event loop is still running. // If the event loop isn't running and we try to wait on this Post // to complete, then LibuvTransport will never be disposed and @@ -191,11 +190,15 @@ await Thread.PostAsync(listener => listener._closed = true; + listener.StopAcceptingConnections(); + }, this).ConfigureAwait(false); } else { FreeBuffer(); + + StopAcceptingConnections(); } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 8da288eb7094..5dc436b33b20 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -1,18 +1,17 @@ // 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.Buffers; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; using Microsoft.AspNetCore.Testing; @@ -23,15 +22,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests { public class LibuvTransportTests { - public static TheoryData ConnectionAdapterData => new TheoryData - { - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } - }; - public static IEnumerable OneToTen => Enumerable.Range(1, 10).Select(i => new object[] { i }); [Fact] @@ -42,7 +32,7 @@ public async Task TransportCanBindAndStop() // The transport can no longer start threads without binding to an endpoint. await transport.BindAsync(); - await transport.StopThreadsAsync(); + await transport.DisposeAsync(); } [Fact] @@ -52,42 +42,92 @@ public async Task TransportCanBindUnbindAndStop() var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); - await transport.UnbindAsync(); - await transport.StopThreadsAsync(); + await transport.StopAsync(); + await transport.DisposeAsync(); } - [Theory] - [MemberData(nameof(ConnectionAdapterData))] - public async Task ConnectionCanReadAndWrite(ListenOptions listenOptions) + [Fact] + public async Task ConnectionCanReadAndWrite() { - var serviceContext = new TestServiceContext(); - listenOptions.UseHttpServer(listenOptions.ConnectionAdapters, serviceContext, new DummyApplication(TestApp.EchoApp), HttpProtocols.Http1); + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); + var transport = new LibuvConnectionListener(transportContext, endpoint); await transport.BindAsync(); - listenOptions.EndPoint = transport.EndPoint; + endpoint = (IPEndPoint)transport.EndPoint; - var dispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()); - _ = dispatcher.StartAcceptingConnections(transport); + async Task EchoServerAsync() + { + await using var connection = await transport.AcceptAsync(); - using (var socket = TestConnection.CreateConnectedLoopbackSocket(listenOptions.IPEndPoint.Port)) + while (true) + { + var result = await connection.Transport.Input.ReadAsync(); + + if (result.IsCompleted) + { + break; + } + await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); + + connection.Transport.Input.AdvanceTo(result.Buffer.End); + } + } + + var serverTask = EchoServerAsync(); + + using (var socket = TestConnection.CreateConnectedLoopbackSocket(endpoint.Port)) { - var data = "Hello World"; - socket.Send(Encoding.ASCII.GetBytes($"POST / HTTP/1.0\r\nContent-Length: 11\r\n\r\n{data}")); + var data = Encoding.ASCII.GetBytes("Hello World"); + socket.Send(data); + var buffer = new byte[data.Length]; var read = 0; while (read < data.Length) { read += socket.Receive(buffer, read, buffer.Length - read, SocketFlags.None); } + + Assert.Equal(data, buffer); } - - Assert.True(await serviceContext.ConnectionManager.CloseAllConnectionsAsync(new CancellationTokenSource(TestConstants.DefaultTimeout).Token)); - await transport.UnbindAsync(); - await transport.StopThreadsAsync(); + await serverTask.DefaultTimeout(); + + await transport.StopAsync(); + await transport.DisposeAsync(); + } + + [Fact] + public async Task UnacceptedConnectionsAreAborted() + { + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + + var transportContext = new TestLibuvTransportContext(); + var transport = new LibuvConnectionListener(transportContext, endpoint); + + await transport.BindAsync(); + endpoint = (IPEndPoint)transport.EndPoint; + + async Task ConnectAsync() + { + using (var socket = TestConnection.CreateConnectedLoopbackSocket(endpoint.Port)) + { + var read = await socket.ReceiveAsync(new byte[10], SocketFlags.None); + Assert.Equal(0, read); + } + } + + var connectTask = ConnectAsync(); + + await transport.StopAsync(); + await transport.DisposeAsync(); + + // The connection was accepted because libuv eagerly accepts connections + // they sit in a queue in each listener, we want to make sure that resources + // are cleaned up if they are never accepted by the caller + + await connectTask.DefaultTimeout(); } [ConditionalTheory] @@ -132,7 +172,7 @@ public async Task OneToTenThreads(int threadCount) } } - await transport.UnbindAsync().ConfigureAwait(false); + await transport.StopAsync().ConfigureAwait(false); await acceptTask.ConfigureAwait(false); @@ -141,7 +181,7 @@ public async Task OneToTenThreads(int threadCount) await serviceContext.ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false); } - await transport.StopThreadsAsync().ConfigureAwait(false); + await transport.DisposeAsync().ConfigureAwait(false); } } } From c41a6ab5a90e028873cbbe52ff6c94dbf6d0ce23 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 00:07:24 -0700 Subject: [PATCH 47/78] Added support for setting the read and write buffers on each transport --- .../src/Internal/LibuvConnection.cs | 11 ++++++++--- .../src/Internal/ListenerContext.cs | 3 ++- .../Transport.Libuv/src/LibuvTransportOptions.cs | 5 +++++ .../src/Internal/SocketConnection.cs | 14 +++++++++++--- .../src/SocketConnectionListener.cs | 2 +- .../src/SocketTransportOptions.cs | 5 +++++ .../shared/test/TransportTestHelpers/TestServer.cs | 2 +- .../FunctionalTests/MaxRequestBufferSizeTests.cs | 2 +- .../Libuv.FunctionalTests/TransportSelector.cs | 11 ++++++++--- .../Sockets.FunctionalTests/TransportSelector.cs | 11 ++++++++--- 10 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 3bbb31bc4fad..f5a7b7b76d7a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -39,7 +39,9 @@ public LibuvConnection(UvStreamHandle socket, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint, PipeOptions inputOptions = null, - PipeOptions outputOptions = null) + PipeOptions outputOptions = null, + long? maxReadBufferSize = null, + long? maxWriteBufferSize = null) { _socket = socket; @@ -50,8 +52,11 @@ public LibuvConnection(UvStreamHandle socket, Log = log; Thread = thread; - inputOptions ??= new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, Thread, useSynchronizationContext: false); - outputOptions ??= new PipeOptions(MemoryPool, Thread, PipeScheduler.ThreadPool, useSynchronizationContext: false); + maxReadBufferSize ??= 0; + maxWriteBufferSize ??= 0; + + inputOptions ??= new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, Thread, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); + outputOptions ??= new PipeOptions(MemoryPool, Thread, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index ff89a8aed7aa..ecc48615788d 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -87,7 +87,8 @@ protected internal void HandleConnection(UvStreamHandle socket) } } - var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions); + var options = TransportContext.Options; + var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions, options.MaxReadBufferSize, options.MaxWriteBufferSize); connection.Start(); bool accepted = _acceptQueue.Writer.TryWrite(connection); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index 02946e023611..4349b25d3cd8 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv @@ -22,6 +23,10 @@ public class LibuvTransportOptions public bool NoDelay { get; set; } + public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + + public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); private static int ProcessorThreadCount diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 83459863ec84..841baeae407c 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -34,7 +34,12 @@ internal sealed class SocketConnection : TransportConnection private volatile Exception _shutdownReason; private Task _task; - internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeScheduler scheduler, ISocketsTrace trace) + internal SocketConnection(Socket socket, + MemoryPool memoryPool, + PipeScheduler scheduler, + ISocketsTrace trace, + long? maxReadBufferSize = null, + long? maxWriteBufferSize = null) { Debug.Assert(socket != null); Debug.Assert(memoryPool != null); @@ -58,8 +63,11 @@ internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeSchedu _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); - var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, awaiterScheduler, useSynchronizationContext: false); - var outputOptions = new PipeOptions(MemoryPool, awaiterScheduler, PipeScheduler.ThreadPool, useSynchronizationContext: false); + maxReadBufferSize ??= 0; + maxWriteBufferSize ??= 0; + + var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, awaiterScheduler, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, awaiterScheduler, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 897300ba6846..9bb6a7feacf2 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -100,7 +100,7 @@ public async ValueTask AcceptAsync(CancellationToken cancella var acceptSocket = await _listenSocket.AcceptAsync(); acceptSocket.NoDelay = _options.NoDelay; - var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace); + var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize); connection.Start(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index f5bc3e54db9e..e4ee7afddc8b 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets @@ -19,6 +20,10 @@ public class SocketTransportOptions public bool NoDelay { get; set; } + public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + + public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); } } diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs index cf36786f0476..1382a7017599 100644 --- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs +++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs @@ -61,7 +61,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action { configureKestrel(options); diff --git a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs index 540690317b6e..039b148db018 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs @@ -289,7 +289,7 @@ private async Task StartWebHost(long? maxRequestBufferSize, TaskCompletionSource clientFinishedSendingRequestBody, Func> memoryPoolFactory = null) { - var host = TransportSelector.GetWebHostBuilder(memoryPoolFactory) + var host = TransportSelector.GetWebHostBuilder(memoryPoolFactory, maxRequestBufferSize) .ConfigureServices(AddTestLogging) .UseKestrel(options => { diff --git a/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs b/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs index ca209ba6e767..4bcae2a6cff4 100644 --- a/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs +++ b/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -9,9 +9,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { public static class TransportSelector { - public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null) + public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null, + long? maxReadBufferSize = null) { - return new WebHostBuilder().UseLibuv(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; }); + return new WebHostBuilder().UseLibuv(options => + { + options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; + options.MaxReadBufferSize = maxReadBufferSize; + }); } } } diff --git a/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs b/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs index 3e3cfe3f6c74..6d2461866ef6 100644 --- a/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs +++ b/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -9,9 +9,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { public static class TransportSelector { - public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null) + public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null, + long? maxReadBufferSize = null) { - return new WebHostBuilder().UseSockets(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; }); + return new WebHostBuilder().UseSockets(options => + { + options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; + options.MaxReadBufferSize = maxReadBufferSize; + }); } } } From ba6ae6028f51cec84fd8f5706104bb85ab4c4694 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 00:16:43 -0700 Subject: [PATCH 48/78] Update the refs --- ...tCore.Server.Kestrel.Core.netcoreapp3.0.cs | 14 ++- .../Kestrel/Core/src/KestrelServerOptions.cs | 9 -- .../Kestrel/Core/src/SchedulingMode.cs | 12 --- ...el.Transport.Abstractions.netcoreapp3.0.cs | 86 +++---------------- ...Core.Server.Kestrel.Transport.Libuv.csproj | 1 + ...r.Kestrel.Transport.Libuv.netcoreapp3.0.cs | 3 + ...Kestrel.Transport.Sockets.netcoreapp3.0.cs | 9 +- .../src/SocketTransportFactory.cs | 5 -- 8 files changed, 30 insertions(+), 109 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/SchedulingMode.cs diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs index 1c23c2dc9874..26ddcc4b4c75 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs @@ -87,7 +87,7 @@ public enum HttpProtocols } public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable { - public KestrelServer(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory transportFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public KestrelServer(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Connections.IConnectionListenerFactory transportFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { get { throw null; } } public void Dispose() { } @@ -118,7 +118,6 @@ public partial class KestrelServerOptions public KestrelServerOptions() { } public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.SchedulingMode ApplicationSchedulingMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool DisableStringReuse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } @@ -140,19 +139,18 @@ public void ListenLocalhost(int port, System.Action configure) { } } - public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation + public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder { internal ListenOptions() { } public System.IServiceProvider ApplicationServices { get { throw null; } } public System.Collections.Generic.List ConnectionAdapters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.FileHandleType HandleType { get { throw null; } set { } } - public System.Net.IPEndPoint IPEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Net.EndPoint EndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public ulong FileHandle { get { throw null; } } + public System.Net.IPEndPoint IPEndPoint { get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions KestrelServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public string SocketPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ListenType Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string SocketPath { get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } public override string ToString() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index bbe5d30b9a34..01a94fb0e702 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -38,14 +37,6 @@ public class KestrelServerOptions /// public bool AddServerHeader { get; set; } = true; - /// - /// Gets or sets a value that determines how Kestrel should schedule user callbacks. - /// - /// The default mode is -#pragma warning disable PUB0001 // Pubternal type in public API - public SchedulingMode ApplicationSchedulingMode { get; set; } = SchedulingMode.Default; -#pragma warning restore PUB0001 // Pubternal type in public API - /// /// Gets or sets a value that controls whether synchronous IO is allowed for the and /// diff --git a/src/Servers/Kestrel/Core/src/SchedulingMode.cs b/src/Servers/Kestrel/Core/src/SchedulingMode.cs deleted file mode 100644 index 881006087c71..000000000000 --- a/src/Servers/Kestrel/Core/src/SchedulingMode.cs +++ /dev/null @@ -1,12 +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. - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public enum SchedulingMode - { - Default, - ThreadPool, - Inline - } -} diff --git a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs index 5420d07eacf4..391b0701aae9 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs @@ -1,113 +1,55 @@ // 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. -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions { + public partial class FileHandleEndPoint : System.Net.EndPoint + { + public FileHandleEndPoint(ulong fileHandle, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.FileHandleType fileHandleType) { } + public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } public enum FileHandleType { Auto = 0, Tcp = 1, Pipe = 2, } - public partial interface IApplicationTransportFeature - { - System.IO.Pipelines.IDuplexPipe Application { get; set; } - } - public partial interface IConnectionDispatcher - { - System.Threading.Tasks.Task OnConnection(Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.TransportConnection connection); - } - public partial interface IEndPointInformation - { - ulong FileHandle { get; } - Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.FileHandleType HandleType { get; set; } - System.Net.IPEndPoint IPEndPoint { get; set; } - bool NoDelay { get; } - string SocketPath { get; } - Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ListenType Type { get; } - } - public partial interface ITransport - { - System.Threading.Tasks.Task BindAsync(); - System.Threading.Tasks.Task StopAsync(); - System.Threading.Tasks.Task UnbindAsync(); - } - public partial interface ITransportFactory - { - Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport Create(Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation endPointInformation, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IConnectionDispatcher dispatcher); - } - public partial interface ITransportSchedulerFeature - { - System.IO.Pipelines.PipeScheduler InputWriterScheduler { get; } - System.IO.Pipelines.PipeScheduler OutputReaderScheduler { get; } - } +} +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +{ public static partial class KestrelMemoryPool { public static readonly int MinimumSegmentSize; public static System.Buffers.MemoryPool Create() { throw null; } public static System.Buffers.MemoryPool CreateSlabMemoryPool() { throw null; } } - public enum ListenType - { - IPEndPoint = 0, - SocketPath = 1, - FileHandle = 2, - } - public enum SchedulingMode - { - Default = 0, - ThreadPool = 1, - Inline = 2, - } - public abstract partial class TransportConnection : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionCompleteFeature, Microsoft.AspNetCore.Connections.Features.IConnectionHeartbeatFeature, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeNotificationFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature, Microsoft.AspNetCore.Http.Features.IFeatureCollection, Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IApplicationTransportFeature, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportSchedulerFeature, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + public abstract partial class TransportConnection : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature, Microsoft.AspNetCore.Http.Features.IFeatureCollection, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { - protected readonly System.Threading.CancellationTokenSource _connectionClosingCts; public TransportConnection() { } public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Threading.CancellationToken ConnectionClosedRequested { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } public System.IO.Pipelines.PipeWriter Input { get { throw null; } } - public virtual System.IO.Pipelines.PipeScheduler InputWriterScheduler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public override System.Collections.Generic.IDictionary Items { get { throw null; } set { } } - public System.Net.IPAddress LocalAddress { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int LocalPort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected internal virtual Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Buffers.MemoryPool MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } System.Collections.Generic.IDictionary Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature.Items { get { throw null; } set { } } System.Threading.CancellationToken Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.ConnectionClosed { get { throw null; } set { } } - System.Threading.CancellationToken Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeNotificationFeature.ConnectionClosedRequested { get { throw null; } set { } } System.IO.Pipelines.IDuplexPipe Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature.Transport { get { throw null; } set { } } System.Buffers.MemoryPool Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature.MemoryPool { get { throw null; } } bool Microsoft.AspNetCore.Http.Features.IFeatureCollection.IsReadOnly { get { throw null; } } object Microsoft.AspNetCore.Http.Features.IFeatureCollection.this[System.Type key] { get { throw null; } set { } } int Microsoft.AspNetCore.Http.Features.IFeatureCollection.Revision { get { throw null; } } - string Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.ConnectionId { get { throw null; } set { } } - System.Net.IPAddress Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.LocalIpAddress { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.LocalPort { get { throw null; } set { } } - System.Net.IPAddress Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.RemoteIpAddress { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature.RemotePort { get { throw null; } set { } } - System.IO.Pipelines.IDuplexPipe Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IApplicationTransportFeature.Application { get { throw null; } set { } } - System.IO.Pipelines.PipeScheduler Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportSchedulerFeature.InputWriterScheduler { get { throw null; } } - System.IO.Pipelines.PipeScheduler Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportSchedulerFeature.OutputReaderScheduler { get { throw null; } } public System.IO.Pipelines.PipeReader Output { get { throw null; } } - public virtual System.IO.Pipelines.PipeScheduler OutputReaderScheduler { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public System.Net.IPAddress RemoteAddress { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int RemotePort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public System.Threading.Tasks.Task CompleteAsync() { throw null; } - void Microsoft.AspNetCore.Connections.Features.IConnectionCompleteFeature.OnCompleted(System.Func callback, object state) { } - void Microsoft.AspNetCore.Connections.Features.IConnectionHeartbeatFeature.OnHeartbeat(System.Action action, object state) { } void Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.Abort() { } - void Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeNotificationFeature.RequestClose() { } TFeature Microsoft.AspNetCore.Http.Features.IFeatureCollection.Get() { throw null; } void Microsoft.AspNetCore.Http.Features.IFeatureCollection.Set(TFeature feature) { } - public void OnHeartbeat(System.Action action, object state) { } - public void RequestClose() { } System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public void TickHeartbeat() { } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj b/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj index 381c885bdd0f..1e4f9cd02ab5 100644 --- a/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj +++ b/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj @@ -10,5 +10,6 @@ + diff --git a/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs index 8e37f8714db2..f24337e79d12 100644 --- a/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs @@ -14,6 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv public partial class LibuvTransportOptions { public LibuvTransportOptions() { } + public long? MaxReadBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public long? MaxWriteBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public int ThreadCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } diff --git a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs index 2b9b9b1b87f1..aa9129b46d08 100644 --- a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs @@ -11,15 +11,18 @@ public static partial class WebHostBuilderSocketExtensions } namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { - public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory + public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory { - public SocketTransportFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransport Create(Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IEndPointInformation endPointInformation, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.IConnectionDispatcher dispatcher) { throw null; } + public SocketTransportFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint) { throw null; } } public partial class SocketTransportOptions { public SocketTransportOptions() { } public int IOQueueCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public long? MaxReadBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public long? MaxWriteBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs index c26cfcc66eaf..8504c0e6d0e4 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs @@ -17,11 +17,6 @@ public sealed class SocketTransportFactory : IConnectionListenerFactory private readonly SocketTransportOptions _options; private readonly SocketsTrace _trace; - public SocketTransportFactory(): this(Options.Create(new SocketTransportOptions()), NullLoggerFactory.Instance) - { - - } - public SocketTransportFactory( IOptions options, ILoggerFactory loggerFactory) From 77d3c27c468d69e2bda0195b2a64d68a921bf783 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 00:19:26 -0700 Subject: [PATCH 49/78] Remove scheduling mode --- .../Kestrel/samples/Http2SampleApp/Program.cs | 3 -- .../Kestrel/samples/PlaintextApp/Startup.cs | 44 +------------------ .../Kestrel/samples/SampleApp/Startup.cs | 3 -- .../Kestrel/samples/SystemdTestApp/Startup.cs | 5 +-- 4 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index f1fc2223cfee..04c4cc90ea4a 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -29,9 +29,6 @@ public static void Main(string[] args) { var basePort = context.Configuration.GetValue("BASE_PORT") ?? 5000; - // Run callbacks on the transport thread - options.ApplicationSchedulingMode = SchedulingMode.Inline; - // Http/1.1 endpoint for comparison options.Listen(IPAddress.Any, basePort, listenOptions => { diff --git a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs index 25b1f9e4e2db..044e8b5dfed2 100644 --- a/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs +++ b/src/Servers/Kestrel/samples/PlaintextApp/Startup.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; namespace PlaintextApp { @@ -40,52 +39,11 @@ public static async Task Main(string[] args) { options.Listen(IPAddress.Loopback, 5001); }) - // .UseLibuv() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .Build(); - var hostTask = host.RunAsync(); - var serverTask = ServerAsync(new SocketTransportFactory(), 5002); - - await hostTask;; - } - - private static async Task ServerAsync(IConnectionListenerFactory factory, int port) - { - await using var listener = await factory.BindAsync(new IPEndPoint(IPAddress.Loopback, port)); - - while (true) - { - var connection = await listener.AcceptAsync(); - - // Fire and forget so we can handle more than a single connection at a time - _ = HandleConnectionAsync(connection); - - static async Task HandleConnectionAsync(ConnectionContext connection) - { - await using (connection) - { - while (true) - { - var result = await connection.Transport.Input.ReadAsync(); - var buffer = result.Buffer; - - foreach (var segment in buffer) - { - await connection.Transport.Output.WriteAsync(segment); - } - - if (result.IsCompleted) - { - break; - } - - connection.Transport.Input.AdvanceTo(buffer.End); - } - } - } - } + await host.RunAsync(); } } diff --git a/src/Servers/Kestrel/samples/SampleApp/Startup.cs b/src/Servers/Kestrel/samples/SampleApp/Startup.cs index 71f218d81603..d279a82034db 100644 --- a/src/Servers/Kestrel/samples/SampleApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SampleApp/Startup.cs @@ -95,9 +95,6 @@ public static Task Main(string[] args) httpsOptions.SslProtocols = SslProtocols.Tls12; }); - // Run callbacks on the transport thread - options.ApplicationSchedulingMode = SchedulingMode.Inline; - options.Listen(IPAddress.Loopback, basePort, listenOptions => { // Uncomment the following to enable Nagle's algorithm for this endpoint. diff --git a/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs b/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs index e91322a82795..8888fd143413 100644 --- a/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs @@ -51,9 +51,6 @@ public static Task Main(string[] args) { var basePort = context.Configuration.GetValue("BASE_PORT") ?? 5000; - // Run callbacks on the transport thread - options.ApplicationSchedulingMode = SchedulingMode.Inline; - options.Listen(IPAddress.Loopback, basePort, listenOptions => { // Uncomment the following to enable Nagle's algorithm for this endpoint. @@ -89,4 +86,4 @@ public static Task Main(string[] args) return hostBuilder.Build().RunAsync(); } } -} \ No newline at end of file +} From 4977f1b5f237a84a85ca0e0d4cd4eb7f85cd3e51 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 00:50:00 -0700 Subject: [PATCH 50/78] Fixed fallout from adding DisposeAsync to ConnectionContext --- .../Client/test/FunctionalTests/HubConnectionTests.cs | 2 +- .../test/UnitTests/HubConnectionTests.Helpers.cs | 2 +- .../csharp/Client/test/UnitTests/HubConnectionTests.cs | 2 +- .../csharp/Client/test/UnitTests/TestConnection.cs | 4 ++-- ...AspNetCore.Http.Connections.Client.netcoreapp3.0.cs | 2 +- ...spNetCore.Http.Connections.Client.netstandard2.0.cs | 2 +- .../common/testassets/Tests.Utils/TaskExtensions.cs | 10 ++++++++++ src/SignalR/samples/ClientSample/Tcp/TcpConnection.cs | 4 ++-- .../Tcp/TcpHubConnectionBuilderExtensions.cs | 2 +- 9 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 5a8d26f060f1..d7239736292c 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -59,7 +59,7 @@ private HubConnection CreateHubConnection( var delegateConnectionFactory = new DelegateConnectionFactory( GetHttpConnectionFactory(url, loggerFactory, path, transportType ?? HttpTransportType.LongPolling | HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents), - connection => ((HttpConnection)connection).DisposeAsync()); + connection => ((HttpConnection)connection).DisposeAsync().AsTask()); hubConnectionBuilder.Services.AddSingleton(delegateConnectionFactory); return hubConnectionBuilder.Build(); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Helpers.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Helpers.cs index 0ec53e6479bf..96c18db8d0e8 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Helpers.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.Helpers.cs @@ -16,7 +16,7 @@ private static HubConnection CreateHubConnection(TestConnection connection, IHub var delegateConnectionFactory = new DelegateConnectionFactory( connection.StartAsync, - c => ((TestConnection)c).DisposeAsync()); + c => c.DisposeAsync().AsTask()); builder.Services.AddSingleton(delegateConnectionFactory); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs index 40cca161c0c8..74a2da0c7f32 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs @@ -63,7 +63,7 @@ public async Task ClosedEventRaisedWhenTheClientIsStopped() var delegateConnectionFactory = new DelegateConnectionFactory( format => new TestConnection().StartAsync(format), - connection => ((TestConnection)connection).DisposeAsync()); + connection => ((TestConnection)connection).DisposeAsync().AsTask()); builder.Services.AddSingleton(delegateConnectionFactory); var hubConnection = builder.Build(); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs index dc9e1b9d563b..cdb61db868be 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/TestConnection.cs @@ -60,7 +60,7 @@ public TestConnection(Func onStart = null, Func onDispose = null, bo null); } - public Task DisposeAsync() => DisposeCoreAsync(); + public override ValueTask DisposeAsync() => DisposeCoreAsync(); public async Task StartAsync(TransferFormat transferFormat = TransferFormat.Binary) { @@ -195,7 +195,7 @@ public void CompleteFromTransport(Exception ex = null) Application.Output.Complete(ex); } - private async Task DisposeCoreAsync(Exception ex = null) + private async ValueTask DisposeCoreAsync(Exception ex = null) { Interlocked.Increment(ref _disposeCount); _disposed.TrySetResult(null); diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs index dd78ecdf90eb..3c74a6e8c1fa 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs @@ -15,7 +15,7 @@ public HttpConnection(System.Uri url, Microsoft.AspNetCore.Http.Connections.Http bool Microsoft.AspNetCore.Connections.Features.IConnectionInherentKeepAliveFeature.HasInherentKeepAlive { get { throw null; } } public override System.IO.Pipelines.IDuplexPipe Transport { get { throw null; } set { } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task DisposeAsync() { throw null; } + public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StartAsync(Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs index dd78ecdf90eb..3c74a6e8c1fa 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs @@ -15,7 +15,7 @@ public HttpConnection(System.Uri url, Microsoft.AspNetCore.Http.Connections.Http bool Microsoft.AspNetCore.Connections.Features.IConnectionInherentKeepAliveFeature.HasInherentKeepAlive { get { throw null; } } public override System.IO.Pipelines.IDuplexPipe Transport { get { throw null; } set { } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task DisposeAsync() { throw null; } + public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StartAsync(Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/src/SignalR/common/testassets/Tests.Utils/TaskExtensions.cs b/src/SignalR/common/testassets/Tests.Utils/TaskExtensions.cs index 5aa16750125e..2e0a5215c3bb 100644 --- a/src/SignalR/common/testassets/Tests.Utils/TaskExtensions.cs +++ b/src/SignalR/common/testassets/Tests.Utils/TaskExtensions.cs @@ -17,6 +17,16 @@ static class TaskExtensions { private const int DefaultTimeout = 30 * 1000; + public static Task OrTimeout(this ValueTask task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) + { + return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber); + } + + public static Task OrTimeout(this ValueTask task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) + { + return task.AsTask().TimeoutAfter(timeout, filePath, lineNumber ?? 0); + } + public static Task OrTimeout(this Task task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) { return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber); diff --git a/src/SignalR/samples/ClientSample/Tcp/TcpConnection.cs b/src/SignalR/samples/ClientSample/Tcp/TcpConnection.cs index 1fbb213b9b70..d2802b2e0c3a 100644 --- a/src/SignalR/samples/ClientSample/Tcp/TcpConnection.cs +++ b/src/SignalR/samples/ClientSample/Tcp/TcpConnection.cs @@ -43,14 +43,14 @@ public TcpConnection(EndPoint endPoint) // We claim to have inherent keep-alive so the client doesn't kill the connection when it hasn't seen ping frames. public bool HasInherentKeepAlive { get; } = true; - public Task DisposeAsync() + public override ValueTask DisposeAsync() { Transport?.Output.Complete(); Transport?.Input.Complete(); _socket?.Dispose(); - return Task.CompletedTask; + return default; } public async Task StartAsync() diff --git a/src/SignalR/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs b/src/SignalR/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs index 763725cb69bf..a52b18600e68 100644 --- a/src/SignalR/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs +++ b/src/SignalR/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs @@ -54,7 +54,7 @@ public Task ConnectAsync(TransferFormat transferFormat, Cance public Task DisposeAsync(ConnectionContext connection) { - return ((TcpConnection)connection).DisposeAsync(); + return connection.DisposeAsync().AsTask(); } } } From 83754d2bf52d27eb4f0c63a48af97b7417cd5e6a Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 00:51:44 -0700 Subject: [PATCH 51/78] Generated ref for connection abstractions --- ...Connections.Abstractions.netstandard2.0.cs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index 1189ad64cf5c..47cf25c5705e 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -30,12 +30,16 @@ public static partial class ConnectionBuilderExtensions public abstract partial class ConnectionContext { protected ConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public abstract string ConnectionId { get; set; } public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } public virtual void Abort() { } public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -70,20 +74,23 @@ public partial class ConnectionResetException : System.IO.IOException public ConnectionResetException(string message) { } public ConnectionResetException(string message, System.Exception inner) { } } - public partial class DefaultConnectionContext : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IConnectionUserFeature, System.IDisposable + public partial class DefaultConnectionContext : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionEndPointFeature, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IConnectionUserFeature, System.IDisposable { public DefaultConnectionContext() { } public DefaultConnectionContext(string id) { } public DefaultConnectionContext(string id, System.IO.Pipelines.IDuplexPipe transport, System.IO.Pipelines.IDuplexPipe application) { } public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public void Dispose() { } + public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public partial interface IConnectionBuilder { @@ -91,6 +98,17 @@ public partial interface IConnectionBuilder Microsoft.AspNetCore.Connections.ConnectionDelegate Build(); Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware); } + public partial interface IConnectionListener + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask DisposeAsync(); + System.Threading.Tasks.ValueTask StopAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint); + } [System.FlagsAttribute] public enum TransferFormat { @@ -104,6 +122,11 @@ public partial interface IConnectionCompleteFeature { void OnCompleted(System.Func callback, object state); } + public partial interface IConnectionEndPointFeature + { + System.Net.EndPoint LocalEndPoint { get; set; } + System.Net.EndPoint RemoteEndPoint { get; set; } + } public partial interface IConnectionHeartbeatFeature { void OnHeartbeat(System.Action action, object state); From eb73fc8a15c32487e8844d02ddbcfaa668ebd80d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 01:18:38 -0700 Subject: [PATCH 52/78] Rename StopAsync to UnbindAsync and added ct to args --- ...Connections.Abstractions.netstandard2.0.cs | 4 +-- .../src/IConnectionListener.cs | 6 +--- .../src/IConnectionListenerFactory.cs | 3 +- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 +- .../Kestrel/Core/test/KestrelServerTests.cs | 28 +++++++-------- .../src/Internal/LibuvConnectionListener.cs | 34 ++++++++----------- .../src/Internal/LibuvTransportFactory.cs | 3 +- .../test/LibuvTransportTests.cs | 8 ++--- ...Kestrel.Transport.Sockets.netcoreapp3.0.cs | 2 +- .../src/SocketConnectionListener.cs | 2 +- .../src/SocketTransportFactory.cs | 3 +- .../InMemoryTransportBenchmark.cs | 4 +-- .../TestTransport/InMemoryTransportFactory.cs | 8 ++--- 13 files changed, 49 insertions(+), 58 deletions(-) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index 47cf25c5705e..f1863b85c4fa 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -103,11 +103,11 @@ public partial interface IConnectionListener System.Net.EndPoint EndPoint { get; } System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.ValueTask DisposeAsync(); - System.Threading.Tasks.ValueTask StopAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public partial interface IConnectionListenerFactory { - System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint); + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } [System.FlagsAttribute] public enum TransferFormat diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 6b8cab82a1af..59029e676386 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,8 +10,7 @@ public interface IConnectionListener ValueTask AcceptAsync(CancellationToken cancellationToken = default); - // Rename to UnbindAsync maybe... or StopListeningAsync? - ValueTask StopAsync(CancellationToken cancellationToken = default); + ValueTask UnbindAsync(CancellationToken cancellationToken = default); ValueTask DisposeAsync(); } diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs index ef8cf9aab296..f1f7ddd860b4 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -9,7 +9,6 @@ namespace Microsoft.AspNetCore.Connections { public interface IConnectionListenerFactory { - // Add CancellationToken cancellationToken = default - ValueTask BindAsync(EndPoint endpoint); + ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index ee6648331798..0481df8fad71 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -168,7 +168,7 @@ public async Task StopAsync(CancellationToken cancellationToken) for (int i = 0; i < _transports.Count; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; - tasks[i] = Task.WhenAll(listener.StopAsync(cancellationToken).AsTask(), acceptLoop); + tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); } await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 92fe4d63cebb..5523780c93e9 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -237,14 +237,14 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var mockTransport = new Mock(); var mockTransportFactory = new Mock(); mockTransportFactory - .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) - .Returns(e => + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) + .Returns((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) .Returns(new ValueTask((ConnectionContext)null)); mockTransport - .Setup(transport => transport.StopAsync(It.IsAny())) + .Setup(transport => transport.UnbindAsync(It.IsAny())) .Returns(() => new ValueTask(unbind.WaitAsync())); mockTransport .Setup(transport => transport.DisposeAsync()) @@ -274,7 +274,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() await Task.WhenAll(new[] { stopTask1, stopTask2, stopTask3 }).DefaultTimeout(); - mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); + mockTransport.Verify(transport => transport.UnbindAsync(It.IsAny()), Times.Once); } [Fact] @@ -294,14 +294,14 @@ public async Task StopAsyncCallsCompleteWithThrownException() var mockTransport = new Mock(); var mockTransportFactory = new Mock(); mockTransportFactory - .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) - .Returns(e => + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) + .Returns((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) .Returns(new ValueTask((ConnectionContext)null)); mockTransport - .Setup(transport => transport.StopAsync(It.IsAny())) + .Setup(transport => transport.UnbindAsync(It.IsAny())) .Returns(async () => { await unbind.WaitAsync(); @@ -334,7 +334,7 @@ public async Task StopAsyncCallsCompleteWithThrownException() Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask2.TimeoutAfter(timeout))); Assert.Same(unbindException, await Assert.ThrowsAsync(() => stopTask3.TimeoutAfter(timeout))); - mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); + mockTransport.Verify(transport => transport.UnbindAsync(It.IsAny()), Times.Once); } [Fact] @@ -353,15 +353,15 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() var mockTransport = new Mock(); var mockTransportFactory = new Mock(); mockTransportFactory - .Setup(transportFactory => transportFactory.BindAsync(It.IsAny())) - .Returns(e => + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) + .Returns((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) .Returns(new ValueTask((ConnectionContext)null)); mockTransport - .Setup(transport => transport.StopAsync(It.IsAny())) - .Returns( new ValueTask(unbindTcs.Task)); + .Setup(transport => transport.UnbindAsync(It.IsAny())) + .Returns(new ValueTask(unbindTcs.Task)); mockTransport .Setup(transport => transport.EndPoint).Returns(e); @@ -393,7 +393,7 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() await stopTask2.DefaultTimeout(); await continuationTask.DefaultTimeout(); - mockTransport.Verify(transport => transport.StopAsync(It.IsAny()), Times.Once); + mockTransport.Verify(transport => transport.UnbindAsync(It.IsAny()), Times.Once); } [Fact] @@ -449,7 +449,7 @@ private static void StartDummyApplication(IServer server) private class MockTransportFactory : IConnectionListenerFactory { - public ValueTask BindAsync(EndPoint endpoint) + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var mock = new Mock(); mock.Setup(m => m.EndPoint).Returns(endpoint); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index cfee67eee689..1e8ea35b2631 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -60,11 +60,24 @@ public async ValueTask AcceptAsync(CancellationToken cancella return null; } - public async ValueTask StopAsync(CancellationToken cancellationToken = default) + public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) { - await UnbindAsync().ConfigureAwait(false); + if (_stopped) + { + return; + } + + _stopped = true; + + var disposeTasks = _listeners.Select(listener => ((IAsyncDisposable)listener).DisposeAsync()).ToArray(); + + if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false)) + { + Log.LogError(0, null, "Disposing listeners failed"); + } } + public async ValueTask DisposeAsync() { if (_disposed) @@ -214,23 +227,6 @@ private async IAsyncEnumerator AcceptConnections() } } - internal async Task UnbindAsync() - { - if (_stopped) - { - return; - } - - _stopped = true; - - var disposeTasks = _listeners.Select(listener => ((IAsyncDisposable)listener).DisposeAsync()).ToArray(); - - if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false)) - { - Log.LogError(0, null, "Disposing listeners failed"); - } - } - private static async Task WaitAsync(Task task, TimeSpan timeout) { return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs index 771d3aad1093..689c473195a8 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs @@ -3,6 +3,7 @@ using System; using System.Net; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Hosting; @@ -63,7 +64,7 @@ public LibuvTransportFactory( }; } - public async ValueTask BindAsync(EndPoint endpoint) + public async ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var transportContext = new LibuvTransportContext { diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 5dc436b33b20..29b9863f615a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -42,7 +42,7 @@ public async Task TransportCanBindUnbindAndStop() var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); - await transport.StopAsync(); + await transport.UnbindAsync(); await transport.DisposeAsync(); } @@ -94,7 +94,7 @@ async Task EchoServerAsync() await serverTask.DefaultTimeout(); - await transport.StopAsync(); + await transport.UnbindAsync(); await transport.DisposeAsync(); } @@ -120,7 +120,7 @@ async Task ConnectAsync() var connectTask = ConnectAsync(); - await transport.StopAsync(); + await transport.UnbindAsync(); await transport.DisposeAsync(); // The connection was accepted because libuv eagerly accepts connections @@ -172,7 +172,7 @@ public async Task OneToTenThreads(int threadCount) } } - await transport.StopAsync().ConfigureAwait(false); + await transport.UnbindAsync().ConfigureAwait(false); await acceptTask.ConfigureAwait(false); diff --git a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs index aa9129b46d08..34c7f982bb60 100644 --- a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory { public SocketTransportFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint) { throw null; } + public System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class SocketTransportOptions { diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 9bb6a7feacf2..8d2134291c73 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -114,7 +114,7 @@ public async ValueTask AcceptAsync(CancellationToken cancella } } - public ValueTask StopAsync(CancellationToken cancellationToken) + public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _listenSocket?.Dispose(); _listenSocket = null; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs index 8504c0e6d0e4..1988897855f1 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs @@ -3,6 +3,7 @@ using System; using System.Net; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; @@ -36,7 +37,7 @@ public SocketTransportFactory( _trace = new SocketsTrace(logger); } - public ValueTask BindAsync(EndPoint endpoint) + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var transport = new SocketConnectionListener(endpoint, _options, _trace); transport.Bind(); diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs index 347134c6bbbd..18df36401001 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs @@ -111,7 +111,7 @@ public InMemoryTransportFactory(int connectionsPerEndPoint) _connectionsPerEndPoint = connectionsPerEndPoint; } - public ValueTask BindAsync(EndPoint endpoint) + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var connections = new InMemoryConnection[_connectionsPerEndPoint]; for (var i = 0; i < _connectionsPerEndPoint; i++) @@ -154,7 +154,7 @@ public ValueTask DisposeAsync() return default; } - public ValueTask StopAsync(CancellationToken cancellationToken = default) + public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _tcs.TrySetResult(null); return default; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs index 776b94d20ec1..6c5ff148821c 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportFactory.cs @@ -35,24 +35,22 @@ public async ValueTask AcceptAsync(CancellationToken cancella } - public ValueTask BindAsync(EndPoint endpoint) + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { EndPoint = endpoint; - // The endpoint isn't important return new ValueTask(this); } public ValueTask DisposeAsync() { - return StopAsync(default); + return UnbindAsync(default); } - public ValueTask StopAsync(CancellationToken cancellationToken = default) + public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _acceptQueue.Writer.TryComplete(); - // TODO: Graceful shutdown return default; } } From cbf606362feee374e42acfa239318279ec6c7aaa Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 08:04:50 -0700 Subject: [PATCH 53/78] Added some more LibuvTransportTests --- .../test/LibuvTransportTests.cs | 60 ++++++++++++++++--- .../shared/test/TaskTimeoutExtensions.cs | 10 ++++ 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 29b9863f615a..091fe1e8bb59 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Buffers; using System.Collections.Generic; using System.Linq; @@ -49,13 +50,11 @@ public async Task TransportCanBindUnbindAndStop() [Fact] public async Task ConnectionCanReadAndWrite() { - var endpoint = new IPEndPoint(IPAddress.Loopback, 0); - var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, endpoint); + var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); - endpoint = (IPEndPoint)transport.EndPoint; + var endpoint = (IPEndPoint)transport.EndPoint; async Task EchoServerAsync() { @@ -101,13 +100,11 @@ async Task EchoServerAsync() [Fact] public async Task UnacceptedConnectionsAreAborted() { - var endpoint = new IPEndPoint(IPAddress.Loopback, 0); - var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, endpoint); + var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); - endpoint = (IPEndPoint)transport.EndPoint; + var endpoint = (IPEndPoint)transport.EndPoint; async Task ConnectAsync() { @@ -130,6 +127,53 @@ async Task ConnectAsync() await connectTask.DefaultTimeout(); } + [Fact] + public async Task CallingAcceptAfterDisposeAsyncThrows() + { + var transportContext = new TestLibuvTransportContext(); + var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); + + await transport.BindAsync(); + var endpoint = (IPEndPoint)transport.EndPoint; + + async Task ConnectAsync() + { + using (var socket = TestConnection.CreateConnectedLoopbackSocket(endpoint.Port)) + { + var read = await socket.ReceiveAsync(new byte[10], SocketFlags.None); + Assert.Equal(0, read); + } + } + + var connectTask = ConnectAsync(); + + await transport.UnbindAsync(); + await transport.DisposeAsync(); + + await Assert.ThrowsAsync(() => transport.AcceptAsync().AsTask()); + + await connectTask.DefaultTimeout(); + } + + [Fact] + public async Task CallingDisposeAsyncWillYieldPendingAccepts() + { + var transportContext = new TestLibuvTransportContext(); + var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); + + await transport.BindAsync(); + + var acceptTask = transport.AcceptAsync(); + + await transport.UnbindAsync(); + + var connection = await acceptTask.DefaultTimeout(); + + Assert.Null(connection); + + await transport.DisposeAsync(); + } + [ConditionalTheory] [MemberData(nameof(OneToTen))] [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Tests fail on OS X due to low file descriptor limit.")] diff --git a/src/Servers/Kestrel/shared/test/TaskTimeoutExtensions.cs b/src/Servers/Kestrel/shared/test/TaskTimeoutExtensions.cs index 8e83a7a70e94..68010b090a16 100644 --- a/src/Servers/Kestrel/shared/test/TaskTimeoutExtensions.cs +++ b/src/Servers/Kestrel/shared/test/TaskTimeoutExtensions.cs @@ -7,6 +7,16 @@ namespace System.Threading.Tasks { public static class TaskTimeoutExtensions { + public static Task DefaultTimeout(this ValueTask task) + { + return task.AsTask().TimeoutAfter(TestConstants.DefaultTimeout); + } + + public static Task DefaultTimeout(this ValueTask task) + { + return task.AsTask().TimeoutAfter(TestConstants.DefaultTimeout); + } + public static Task DefaultTimeout(this Task task) { return task.TimeoutAfter(TestConstants.DefaultTimeout); From 1c29e3b1948b485dde4e93512b3837ecdcd96d25 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 08:53:44 -0700 Subject: [PATCH 54/78] Moved TransportConnection to Connections.Abstractions --- .../src}/TransportConnection.FeatureCollection.cs | 3 +-- .../src}/TransportConnection.Generated.cs | 2 +- .../src}/TransportConnection.cs | 9 +++------ .../Kestrel/Core/test/ConnectionDispatcherTests.cs | 3 +-- .../Kestrel/Core/test/HttpConnectionManagerTests.cs | 2 +- .../Microsoft.AspNetCore.Server.Kestrel.Tests.csproj | 4 ++-- .../Transport.Libuv/src/Internal/LibuvConnection.cs | 4 ++++ .../Transport.Sockets/src/Internal/SocketConnection.cs | 4 ++++ .../Kestrel.Performance/InMemoryTransportBenchmark.cs | 4 ++++ .../TestTransport/InMemoryTransportConnection.cs | 6 ++++-- .../Kestrel/tools/CodeGenerator/CodeGenerator.csproj | 2 +- .../TransportConnectionFeatureCollection.cs | 2 +- 12 files changed, 27 insertions(+), 18 deletions(-) rename src/Servers/{Kestrel/Transport.Abstractions/src/Internal => Connections.Abstractions/src}/TransportConnection.FeatureCollection.cs (93%) rename src/Servers/{Kestrel/Transport.Abstractions/src/Internal => Connections.Abstractions/src}/TransportConnection.Generated.cs (99%) rename src/Servers/{Kestrel/Transport.Abstractions/src/Internal => Connections.Abstractions/src}/TransportConnection.cs (88%) diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs b/src/Servers/Connections.Abstractions/src/TransportConnection.FeatureCollection.cs similarity index 93% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs rename to src/Servers/Connections.Abstractions/src/TransportConnection.FeatureCollection.cs index 871d46945acd..fc5443ecfc64 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs +++ b/src/Servers/Connections.Abstractions/src/TransportConnection.FeatureCollection.cs @@ -5,10 +5,9 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Threading; -using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Connections { public partial class TransportConnection : IConnectionIdFeature, IConnectionTransportFeature, diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs b/src/Servers/Connections.Abstractions/src/TransportConnection.Generated.cs similarity index 99% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs rename to src/Servers/Connections.Abstractions/src/TransportConnection.Generated.cs index 433563ca9912..eb6f2ba253e7 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs +++ b/src/Servers/Connections.Abstractions/src/TransportConnection.Generated.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Connections { public partial class TransportConnection : IFeatureCollection { diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs b/src/Servers/Connections.Abstractions/src/TransportConnection.cs similarity index 88% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs rename to src/Servers/Connections.Abstractions/src/TransportConnection.cs index cbebc1ee6161..4cef0363c988 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs +++ b/src/Servers/Connections.Abstractions/src/TransportConnection.cs @@ -7,10 +7,9 @@ using System.IO.Pipelines; using System.Net; using System.Threading; -using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Connections { public abstract partial class TransportConnection : ConnectionContext { @@ -31,6 +30,7 @@ public TransportConnection() public virtual MemoryPool MemoryPool { get; } public override IDuplexPipe Transport { get; set; } + public IDuplexPipe Application { get; set; } public override IDictionary Items @@ -46,9 +46,6 @@ public override IDictionary Items } } - public PipeWriter Input => Application.Output; - public PipeReader Output => Application.Input; - public override CancellationToken ConnectionClosed { get; set; } // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause @@ -59,7 +56,7 @@ public override IDictionary Items // sufficient to abort the connection if there is backpressure. public override void Abort(ConnectionAbortedException abortReason) { - Output.CancelPendingRead(); + Application.Input.CancelPendingRead(); } } } diff --git a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs index 302f19abfcef..985ab6b6f4b9 100644 --- a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs +++ b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs @@ -3,14 +3,13 @@ using System; using System.Collections.Generic; -using System.IO.Pipelines; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs index 34f6d210d70b..b7c7df6a1d9d 100644 --- a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs @@ -3,8 +3,8 @@ using System; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; using Moq; using Xunit; diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj index 814c905495ec..85d69ca59ae7 100644 --- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj +++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -12,7 +12,7 @@ - + diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index f5a7b7b76d7a..292dbaba8244 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -65,6 +65,10 @@ public LibuvConnection(UvStreamHandle socket, Application = pair.Application; } + public PipeWriter Input => Application.Output; + + public PipeReader Output => Application.Input; + public LibuvOutputConsumer OutputConsumer { get; set; } private ILibuvTrace Log { get; } private LibuvThread Thread { get; } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 841baeae407c..025211e09e21 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -76,6 +76,10 @@ internal SocketConnection(Socket socket, Application = pair.Application; } + public PipeWriter Input => Application.Output; + + public PipeReader Output => Application.Input; + public override MemoryPool MemoryPool { get; } public void Start() diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs index 18df36401001..ad34845e3bcf 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs @@ -175,6 +175,10 @@ public InMemoryConnection() Application = pair.Application; } + public PipeWriter Input => Application.Output; + + public PipeReader Output => Application.Input; + public ValueTask SendRequestAsync(byte[] request) { return Input.WriteAsync(request); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index d291af01c03d..fe184fa889c1 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -3,14 +3,12 @@ using System; using System.Buffers; -using System.Diagnostics; using System.IO.Pipelines; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Sources; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport @@ -40,6 +38,10 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger, ConnectionClosed = _connectionClosedTokenSource.Token; } + public PipeWriter Input => Application.Output; + + public PipeReader Output => Application.Input; + public Task WaitForReadTask { get; } public override MemoryPool MemoryPool { get; } diff --git a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj index afb106a5cff8..90f9bb3627a5 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj +++ b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj @@ -18,7 +18,7 @@ $(MSBuildThisFileDirectory)..\..\ - Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs Transport.Abstractions/src/Internal/TransportConnection.Generated.cs + Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs ../Connections.Abstractions/src/TransportConnection.Generated.cs diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index 68738138a696..a9eabbe47435 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -23,7 +23,7 @@ public static string GenerateFile() using Microsoft.AspNetCore.Http.Features;"; return FeatureCollectionGenerator.GenerateFile( - namespaceName: "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal", + namespaceName: "Microsoft.AspNetCore.Connections", className: "TransportConnection", allFeatures: features, implementedFeatures: features, From 288d4e0270fe369c429857ab0b223e33444c5f77 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 08:56:50 -0700 Subject: [PATCH 55/78] Updated refs --- ...Connections.Abstractions.netstandard2.0.cs | 26 +++++++++++++++++ ...el.Transport.Abstractions.netcoreapp3.0.cs | 28 ------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index f1863b85c4fa..4f2f5e8166e4 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -115,6 +115,32 @@ public enum TransferFormat Binary = 1, Text = 2, } + public abstract partial class TransportConnection : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature, Microsoft.AspNetCore.Http.Features.IFeatureCollection, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + { + public TransportConnection() { } + public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } + public override System.Collections.Generic.IDictionary Items { get { throw null; } set { } } + public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Buffers.MemoryPool MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + System.Collections.Generic.IDictionary Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature.Items { get { throw null; } set { } } + System.Threading.CancellationToken Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.ConnectionClosed { get { throw null; } set { } } + System.IO.Pipelines.IDuplexPipe Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature.Transport { get { throw null; } set { } } + System.Buffers.MemoryPool Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature.MemoryPool { get { throw null; } } + bool Microsoft.AspNetCore.Http.Features.IFeatureCollection.IsReadOnly { get { throw null; } } + object Microsoft.AspNetCore.Http.Features.IFeatureCollection.this[System.Type key] { get { throw null; } set { } } + int Microsoft.AspNetCore.Http.Features.IFeatureCollection.Revision { get { throw null; } } + public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } + void Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.Abort() { } + TFeature Microsoft.AspNetCore.Http.Features.IFeatureCollection.Get() { throw null; } + void Microsoft.AspNetCore.Http.Features.IFeatureCollection.Set(TFeature feature) { } + System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } } namespace Microsoft.AspNetCore.Connections.Features { diff --git a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs index 391b0701aae9..3ff219361227 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs @@ -24,32 +24,4 @@ public static partial class KestrelMemoryPool public static System.Buffers.MemoryPool Create() { throw null; } public static System.Buffers.MemoryPool CreateSlabMemoryPool() { throw null; } } - public abstract partial class TransportConnection : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionIdFeature, Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature, Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature, Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature, Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature, Microsoft.AspNetCore.Http.Features.IFeatureCollection, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable - { - public TransportConnection() { } - public System.IO.Pipelines.IDuplexPipe Application { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } - public System.IO.Pipelines.PipeWriter Input { get { throw null; } } - public override System.Collections.Generic.IDictionary Items { get { throw null; } set { } } - public override System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public virtual System.Buffers.MemoryPool MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - System.Collections.Generic.IDictionary Microsoft.AspNetCore.Connections.Features.IConnectionItemsFeature.Items { get { throw null; } set { } } - System.Threading.CancellationToken Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.ConnectionClosed { get { throw null; } set { } } - System.IO.Pipelines.IDuplexPipe Microsoft.AspNetCore.Connections.Features.IConnectionTransportFeature.Transport { get { throw null; } set { } } - System.Buffers.MemoryPool Microsoft.AspNetCore.Connections.Features.IMemoryPoolFeature.MemoryPool { get { throw null; } } - bool Microsoft.AspNetCore.Http.Features.IFeatureCollection.IsReadOnly { get { throw null; } } - object Microsoft.AspNetCore.Http.Features.IFeatureCollection.this[System.Type key] { get { throw null; } set { } } - int Microsoft.AspNetCore.Http.Features.IFeatureCollection.Revision { get { throw null; } } - public System.IO.Pipelines.PipeReader Output { get { throw null; } } - public override System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - void Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.Abort() { } - TFeature Microsoft.AspNetCore.Http.Features.IFeatureCollection.Get() { throw null; } - void Microsoft.AspNetCore.Http.Features.IFeatureCollection.Set(TFeature feature) { } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } } From dd162c9c187634a91d6e6f47eb2f78382d59f252 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 09:08:07 -0700 Subject: [PATCH 56/78] Moved FileHandleEndPoint to Connections.Abstractions --- ...ore.Connections.Abstractions.netstandard2.0.cs | 12 ++++++++++++ .../src/FileHandleEndPoint.cs | 2 +- .../src/FileHandleType.cs | 4 ++-- ...estrel.Transport.Abstractions.netcoreapp3.0.cs | 15 --------------- .../Transport.Libuv/src/Internal/Listener.cs | 2 +- .../src/Internal/ListenerContext.cs | 2 +- 6 files changed, 17 insertions(+), 20 deletions(-) rename src/Servers/{Kestrel/Transport.Abstractions => Connections.Abstractions}/src/FileHandleEndPoint.cs (90%) rename src/Servers/{Kestrel/Transport.Abstractions => Connections.Abstractions}/src/FileHandleType.cs (66%) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index 4f2f5e8166e4..bb446f937d4b 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -92,6 +92,18 @@ public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedExc public void Dispose() { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } + public partial class FileHandleEndPoint : System.Net.EndPoint + { + public FileHandleEndPoint(ulong fileHandle, Microsoft.AspNetCore.Connections.FileHandleType fileHandleType) { } + public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Connections.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public enum FileHandleType + { + Auto = 0, + Tcp = 1, + Pipe = 2, + } public partial interface IConnectionBuilder { System.IServiceProvider ApplicationServices { get; } diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs b/src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs similarity index 90% rename from src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs rename to src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs index 65e90c0b91fc..1d7d6c49ca6a 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleEndPoint.cs +++ b/src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs @@ -1,7 +1,7 @@ using System; using System.Net; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions +namespace Microsoft.AspNetCore.Connections { public class FileHandleEndPoint : EndPoint { diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleType.cs b/src/Servers/Connections.Abstractions/src/FileHandleType.cs similarity index 66% rename from src/Servers/Kestrel/Transport.Abstractions/src/FileHandleType.cs rename to src/Servers/Connections.Abstractions/src/FileHandleType.cs index d9b8a50acc57..f16935e044f9 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/FileHandleType.cs +++ b/src/Servers/Connections.Abstractions/src/FileHandleType.cs @@ -1,10 +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. -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions +namespace Microsoft.AspNetCore.Connections { /// - /// Enumerates the types. + /// Enumerates the types. /// public enum FileHandleType { diff --git a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs index 3ff219361227..9dbed1413912 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs @@ -1,21 +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. -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions -{ - public partial class FileHandleEndPoint : System.Net.EndPoint - { - public FileHandleEndPoint(ulong fileHandle, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.FileHandleType fileHandleType) { } - public ulong FileHandle { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.FileHandleType FileHandleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - public enum FileHandleType - { - Auto = 0, - Tcp = 1, - Pipe = 2, - } -} namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { public static partial class KestrelMemoryPool diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index f341c44d8684..9dc11a31a440 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -5,7 +5,7 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index ecc48615788d..46c0f835aabf 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; From e0f134848b68573a0f90c93ee484395bc01afedf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 16:13:30 -0700 Subject: [PATCH 57/78] Remove dependency on Transport.Abstractions --- .../src/Adapter/Internal/AdaptedPipeline.cs | 9 ++++--- .../Http/Http1ChunkedEncodingMessageBody.cs | 3 +-- .../Http/HttpProtocol.FeatureCollection.cs | 7 +++-- .../src/Internal/Http2/Http2OutputProducer.cs | 3 +-- .../Core/src/Internal/Http2/Http2Stream.cs | 3 +-- .../Core/src/Internal/HttpConnection.cs | 8 +++--- .../Core/src/Internal/MemoryPoolExtensions.cs | 26 +++++++++++++++++++ src/Servers/Kestrel/Core/src/ListenOptions.cs | 1 - ...soft.AspNetCore.Server.Kestrel.Core.csproj | 2 +- .../Kestrel/Core/test/Http1ConnectionTests.cs | 3 +-- .../Core/test/HttpResponseHeadersTests.cs | 4 +-- .../Kestrel/Core/test/KestrelServerTests.cs | 1 - .../Kestrel/Core/test/OutputProducerTests.cs | 3 +-- .../Kestrel/Core/test/PipeOptionsTests.cs | 1 - .../Core/test/PipelineExtensionTests.cs | 3 +-- .../Kestrel/Core/test/StartLineTests.cs | 3 +-- .../Core/test/TestHelpers/TestInput.cs | 3 +-- .../src/WebHostBuilderKestrelExtensions.cs | 1 - ...oft.AspNetCore.Server.Kestrel.Tests.csproj | 1 - ...rver.Kestrel.Transport.Abstractions.csproj | 2 +- .../src/Internal/LibuvConnection.cs | 3 +-- .../src/Internal/LibuvTransportContext.cs | 1 - .../src/LibuvTransportOptions.cs | 3 +-- ...Core.Server.Kestrel.Transport.Libuv.csproj | 5 ++-- .../src/WebHostBuilderLibuvExtensions.cs | 1 - .../test/LibuvOutputConsumerTests.cs | 3 +-- .../src/Internal/SocketConnection.cs | 3 +-- ...re.Server.Kestrel.Transport.Sockets.csproj | 5 ++-- .../src/SocketTransportOptions.cs | 3 +-- .../ChunkWriterBenchmark.cs | 3 +-- .../Http1ConnectionBenchmark.cs | 3 +-- ...Http1ConnectionParsingOverheadBenchmark.cs | 3 +-- .../Http1ReadingBenchmark.cs | 3 +-- .../Http1WritingBenchmark.cs | 3 +-- .../HttpProtocolFeatureCollection.cs | 6 ++--- .../InMemoryTransportBenchmark.cs | 1 - ...pNetCore.Server.Kestrel.Performance.csproj | 1 + .../PipeThroughputBenchmark.cs | 5 ++-- .../RequestParsingBenchmark.cs | 3 +-- .../ResponseHeaderCollectionBenchmark.cs | 4 +-- .../Kestrel/samples/Http2SampleApp/Program.cs | 1 - .../Kestrel/samples/SampleApp/Startup.cs | 1 - .../Kestrel/samples/SystemdTestApp/Startup.cs | 1 - .../Kestrel/shared/test/TestServiceContext.cs | 3 +-- .../DiagnosticMemoryPoolFactory.cs | 7 +++-- .../Http2/Http2TestBase.cs | 7 +++-- .../InMemory.FunctionalTests.csproj | 4 ++- .../Buffers.MemoryPool/MemoryPoolFactory.cs} | 9 ++----- .../Buffers.MemoryPool/SlabMemoryPool.cs | 5 ++++ 49 files changed, 92 insertions(+), 95 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs rename src/{Servers/Kestrel/Transport.Abstractions/src/Internal/KestrelMemoryPool.cs => Shared/Buffers.MemoryPool/MemoryPoolFactory.cs} (69%) diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs index 8d9a35b45639..8043ca28fe56 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs +++ b/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs @@ -6,26 +6,27 @@ using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal { internal class AdaptedPipeline : IDuplexPipe { - private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; + private readonly int _minAllocBufferSize; private readonly IDuplexPipe _transport; public AdaptedPipeline(IDuplexPipe transport, Pipe inputPipe, Pipe outputPipe, - IKestrelTrace log) + IKestrelTrace log, + int minAllocBufferSize) { _transport = transport; Input = inputPipe; Output = outputPipe; Log = log; + _minAllocBufferSize = minAllocBufferSize; } public Pipe Input { get; } @@ -115,7 +116,7 @@ private async Task ReadInputAsync(Stream stream) while (true) { - var outputBuffer = Input.Writer.GetMemory(MinAllocBufferSize); + var outputBuffer = Input.Writer.GetMemory(_minAllocBufferSize); var bytesRead = await stream.ReadAsync(outputBuffer); Input.Writer.Advance(bytesRead); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs index 7c1327cd73ae..db9e78aa2f8d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { @@ -546,7 +545,7 @@ private Pipe CreateRequestBodyPipe(Http1Connection context) pauseWriterThreshold: 1, resumeWriterThreshold: 1, useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: context.MemoryPool.GetMinimumSegmentSize() )); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs index 11fcadc07413..27b385888cb1 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http @@ -107,8 +106,8 @@ Stream IHttpRequestFeature.Body { RequestBody = value; var requestPipeReader = new StreamPipeReader(RequestBody, new StreamPipeReaderAdapterOptions( - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize, - minimumReadThreshold: KestrelMemoryPool.MinimumSegmentSize / 4, + minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize(), + minimumReadThreshold: _context.MemoryPool.GetMinimumAllocSize(), _context.MemoryPool)); RequestBodyPipeReader = requestPipeReader; @@ -264,7 +263,7 @@ Stream IHttpResponseFeature.Body set { ResponseBody = value; - var responsePipeWriter = new StreamPipeWriter(ResponseBody, minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize, _context.MemoryPool); + var responsePipeWriter = new StreamPipeWriter(ResponseBody, minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize(), _context.MemoryPool); ResponsePipeWriter = responsePipeWriter; // The StreamPipeWrapper needs to be disposed as it hold onto blocks of memory diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index fec48783efce..baf28b162dd8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { @@ -363,7 +362,7 @@ private static Pipe CreateDataPipe(MemoryPool pool) pauseWriterThreshold: 1, resumeWriterThreshold: 1, useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: pool.GetMinimumAllocSize() )); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index 5a27a5641f75..2405b93d3c2a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -494,7 +493,7 @@ private Pipe CreateRequestBodyPipe(uint windowSize) pauseWriterThreshold: windowSize + 1, resumeWriterThreshold: windowSize + 1, useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: _context.MemoryPool.GetMinimumAllocSize() )); private (StreamCompletionFlags OldState, StreamCompletionFlags NewState) ApplyCompletionFlag(StreamCompletionFlags completionState) diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 2bb90b1ae0ad..2d7b3c76337b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal @@ -62,7 +61,7 @@ public HttpConnection(HttpConnectionContext context) pauseWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, resumeWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: MemoryPool.GetMinimumSegmentSize() ); internal PipeOptions AdaptedOutputPipeOptions => new PipeOptions @@ -73,7 +72,7 @@ public HttpConnection(HttpConnectionContext context) pauseWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxResponseBufferSize ?? 0, resumeWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxResponseBufferSize ?? 0, useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: MemoryPool.GetMinimumSegmentSize() ); private IKestrelTrace Log => _context.ServiceContext.Log; @@ -94,7 +93,8 @@ public async Task ProcessRequestsAsync(IHttpApplication http adaptedPipeline = new AdaptedPipeline(_adaptedTransport, new Pipe(AdaptedInputPipeOptions), new Pipe(AdaptedOutputPipeOptions), - Log); + Log, + MemoryPool.GetMinimumAllocSize()); _adaptedTransport = adaptedPipeline; } diff --git a/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs new file mode 100644 index 000000000000..69c038807d52 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal static class MemoryPoolExtensions + { + /// + /// Computes a minimum segment size + /// + /// + /// + public static int GetMinimumSegmentSize(this MemoryPool pool) + { + return Math.Min(4096, pool.MaxBufferSize); + } + + public static int GetMinimumAllocSize(this MemoryPool pool) + { + // 1/2 of a segment + return pool.GetMinimumSegmentSize() / 2; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 1ee395856d04..a541f2e43cf1 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; namespace Microsoft.AspNetCore.Server.Kestrel.Core { diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index a528049cf616..816133461348 100644 --- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs index 494e4531e6c6..042a136b3e5f 100644 --- a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs @@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -43,7 +42,7 @@ public class Http1ConnectionTests : IDisposable public Http1ConnectionTests() { - _pipelineFactory = KestrelMemoryPool.Create(); + _pipelineFactory = MemoryPoolFactory.Create(); var options = new PipeOptions(_pipelineFactory, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs index 4aad2e3561fd..6a4e1c235221 100644 --- a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.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.Buffers; using System.Collections.Generic; using System.Globalization; using System.IO.Pipelines; @@ -9,7 +10,6 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Primitives; using Xunit; @@ -21,7 +21,7 @@ public class HttpResponseHeadersTests [Fact] public void InitialDictionaryIsEmpty() { - using (var memoryPool = KestrelMemoryPool.Create()) + using (var memoryPool = MemoryPoolFactory.Create()) { var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 5523780c93e9..8af4397809be 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/Core/test/OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/OutputProducerTests.cs index 749e2473a561..53aa300f1cd6 100644 --- a/src/Servers/Kestrel/Core/test/OutputProducerTests.cs +++ b/src/Servers/Kestrel/Core/test/OutputProducerTests.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; @@ -22,7 +21,7 @@ public class OutputProducerTests : IDisposable public OutputProducerTests() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); } public void Dispose() diff --git a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs b/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs index cbaf74f5a66a..0b06bec0e9eb 100644 --- a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs +++ b/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs @@ -3,7 +3,6 @@ using System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; diff --git a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs index 1e5f82677d72..e2dfc2efd472 100644 --- a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs +++ b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs @@ -6,7 +6,6 @@ using System.IO.Pipelines; using System.Text; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests @@ -17,7 +16,7 @@ public class PipelineExtensionTests : IDisposable private const int _ulongMaxValueLength = 20; private readonly Pipe _pipe; - private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); + private readonly MemoryPool _memoryPool = MemoryPoolFactory.Create(); public PipelineExtensionTests() { diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs index 9b9656fbba10..bd4d5f4d5b5b 100644 --- a/src/Servers/Kestrel/Core/test/StartLineTests.cs +++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Moq; using Xunit; @@ -500,7 +499,7 @@ public void AuthorityForms(string rawTarget, string path, string query) public StartLineTests() { - MemoryPool = KestrelMemoryPool.Create(); + MemoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(MemoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); Transport = pair.Transport; diff --git a/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs b/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs index 81f046b10dd7..3a8ce70e67ee 100644 --- a/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs +++ b/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; @@ -24,7 +23,7 @@ class TestInput : IDisposable public TestInput() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(pool: _memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); Transport = pair.Transport; diff --git a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs index 1b93e76d5fd4..361667936996 100644 --- a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs +++ b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs @@ -6,7 +6,6 @@ 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; diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj index 85d69ca59ae7..cb32fe57c11a 100644 --- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj +++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj @@ -20,7 +20,6 @@ - diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj b/src/Servers/Kestrel/Transport.Abstractions/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj index 9f9270d232ba..a20f6d2640b3 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj +++ b/src/Servers/Kestrel/Transport.Abstractions/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj @@ -1,4 +1,4 @@ - + Transport abstractions for the ASP.NET Core Kestrel cross-platform web server. diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 292dbaba8244..9e06b2dc3dde 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -17,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal partial class LibuvConnection : TransportConnection { - private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; + private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2; private static readonly Action _readCallback = (handle, status, state) => ReadCallback(handle, status, state); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs index da5efe029758..973a0fec061b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs @@ -1,7 +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 Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Hosting; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index 4349b25d3cd8..7521eb00db8c 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.IO.Pipelines; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv { @@ -27,7 +26,7 @@ public class LibuvTransportOptions public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; - internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); + internal Func> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create; private static int ProcessorThreadCount { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj index 3d9faac3ca5f..d6bd6d4e450e 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj +++ b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj @@ -1,4 +1,4 @@ - + Libuv transport for the ASP.NET Core Kestrel cross-platform web server. @@ -11,13 +11,14 @@ + - + diff --git a/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs b/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs index c8514caed2c0..8f0986d3341a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs @@ -3,7 +3,6 @@ using System; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs index 8ce785997ec6..f0a9769357ed 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs @@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; @@ -42,7 +41,7 @@ public class LibuvOutputConsumerTests : IDisposable public LibuvOutputConsumerTests() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); _mockLibuv = new MockLibuv(); var context = new TestLibuvTransportContext(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 025211e09e21..d48b4cbf8210 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -11,14 +11,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal { internal sealed class SocketConnection : TransportConnection { - private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; + private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2; private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj index 6924af105e8f..337685e9b776 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj +++ b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj @@ -1,4 +1,4 @@ - + Managed socket transport for the ASP.NET Core Kestrel cross-platform web server. @@ -11,12 +11,13 @@ + - + diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index e4ee7afddc8b..9e6954d8bd75 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.IO.Pipelines; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { @@ -24,6 +23,6 @@ public class SocketTransportOptions public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; - internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); + internal Func> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create; } } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/ChunkWriterBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/ChunkWriterBenchmark.cs index 3b24e49cabec..c327d8d51f90 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/ChunkWriterBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/ChunkWriterBenchmark.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -21,7 +20,7 @@ public class ChunkWriterBenchmark [GlobalSetup] public void Setup() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); var pipe = new Pipe(new PipeOptions(_memoryPool)); _reader = pipe.Reader; _writer = pipe.Writer; diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs index c404a16c2795..d7bdd0a7095d 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -27,7 +26,7 @@ public class Http1ConnectionBenchmark [GlobalSetup] public void Setup() { - var memoryPool = KestrelMemoryPool.Create(); + var memoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs index 84ac57e630a3..8ffe0a0059e1 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -23,7 +22,7 @@ public class Http1ConnectionParsingOverheadBenchmark [IterationSetup] public void Setup() { - var memoryPool = KestrelMemoryPool.Create(); + var memoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ReadingBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ReadingBenchmark.cs index 122c5b694c19..2faca73d6ab2 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ReadingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ReadingBenchmark.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.Performance @@ -35,7 +34,7 @@ public class Http1ReadingBenchmark [GlobalSetup] public void GlobalSetup() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); _http1Connection = MakeHttp1Connection(); } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1WritingBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1WritingBenchmark.cs index d0c1cf3370f7..94dc4e66c52d 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1WritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1WritingBenchmark.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.Performance @@ -35,7 +34,7 @@ public class Http1WritingBenchmark [GlobalSetup] public void GlobalSetup() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); _http1Connection = MakeHttp1Connection(); } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/HttpProtocolFeatureCollection.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/HttpProtocolFeatureCollection.cs index 3f8bae879f79..7f948c5a4bb9 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/HttpProtocolFeatureCollection.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/HttpProtocolFeatureCollection.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Buffers; using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Threading; @@ -11,7 +12,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -78,7 +78,7 @@ public object GetViaGeneric_NotFound() public HttpProtocolFeatureCollection() { - var memoryPool = KestrelMemoryPool.Create(); + var memoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs index ad34845e3bcf..b3e4e13eff1a 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/InMemoryTransportBenchmark.cs @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Server.Kestrel.Performance diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj b/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj index e801048b6769..93d77cab5f02 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/PipeThroughputBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/PipeThroughputBenchmark.cs index b37656faec82..03b3eb2f1da5 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/PipeThroughputBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/PipeThroughputBenchmark.cs @@ -1,11 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Buffers; using System.IO.Pipelines; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -20,7 +19,7 @@ public class PipeThroughputBenchmark [IterationSetup] public void Setup() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); _pipe = new Pipe(new PipeOptions(_memoryPool)); } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs index da9f0f7faf30..a6cba511786a 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -24,7 +23,7 @@ public class RequestParsingBenchmark [IterationSetup] public void Setup() { - _memoryPool = KestrelMemoryPool.Create(); + _memoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs index 530a0ec968ce..417c5a8bbe05 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/ResponseHeaderCollectionBenchmark.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.Buffers; using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Text; @@ -12,7 +13,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Performance @@ -172,7 +172,7 @@ private void Unknown(int count) [IterationSetup] public void Setup() { - var memoryPool = KestrelMemoryPool.Create(); + var memoryPool = MemoryPoolFactory.Create(); var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index 04c4cc90ea4a..794f011ecc7b 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/samples/SampleApp/Startup.cs b/src/Servers/Kestrel/samples/SampleApp/Startup.cs index d279a82034db..4ab1d5090bc7 100644 --- a/src/Servers/Kestrel/samples/SampleApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SampleApp/Startup.cs @@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs b/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs index 8888fd143413..56e6f3f980da 100644 --- a/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/shared/test/TestServiceContext.cs b/src/Servers/Kestrel/shared/test/TestServiceContext.cs index 7b6abf939df5..e5332963aa8d 100644 --- a/src/Servers/Kestrel/shared/test/TestServiceContext.cs +++ b/src/Servers/Kestrel/shared/test/TestServiceContext.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Testing @@ -75,7 +74,7 @@ private void Initialize(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace public MockSystemClock MockSystemClock { get; set; } - public Func> MemoryPoolFactory { get; set; } = KestrelMemoryPool.Create; + public Func> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create; public int ExpectedConnectionMiddlewareCount { get; set; } diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs index d3a33bf2cf1e..26c8746e3985 100644 --- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs +++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { @@ -29,7 +28,7 @@ public MemoryPool Create() { lock (_pools) { - var pool = new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), _allowLateReturn, _rentTracking); + var pool = new DiagnosticMemoryPool(new SlabMemoryPool(), _allowLateReturn, _rentTracking); _pools.Add(pool); return pool; } @@ -43,4 +42,4 @@ public Task WhenAllBlocksReturned(TimeSpan span) } } } -} \ No newline at end of file +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index a11b9e3d3869..424307ac26d3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -23,7 +23,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -115,7 +114,7 @@ protected static IEnumerable> ReadRateRequestHeader protected static readonly byte[] _noData = new byte[0]; protected static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2PeerSettings.MinAllowedMaxFrameSize)); - private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); + private readonly MemoryPool _memoryPool = MemoryPoolFactory.Create(); internal readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); internal readonly HPackEncoder _hpackEncoder = new HPackEncoder(); @@ -1256,7 +1255,7 @@ protected void AdvanceClock(TimeSpan timeSpan) pauseWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, resumeWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: memoryPool.GetMinimumSegmentSize() ); private static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler readerScheduler) => new PipeOptions @@ -1267,7 +1266,7 @@ protected void AdvanceClock(TimeSpan timeSpan) pauseWriterThreshold: GetOutputResponseBufferSize(serviceContext), resumeWriterThreshold: GetOutputResponseBufferSize(serviceContext), useSynchronizationContext: false, - minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + minimumSegmentSize: memoryPool.GetMinimumSegmentSize() ); private static long GetOutputResponseBufferSize(ServiceContext serviceContext) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index 8bbf63c40087..c1c91075984c 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -1,9 +1,10 @@ - + netcoreapp3.0 true InMemory.FunctionalTests + true @@ -11,6 +12,7 @@ + diff --git a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/KestrelMemoryPool.cs b/src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs similarity index 69% rename from src/Servers/Kestrel/Transport.Abstractions/src/Internal/KestrelMemoryPool.cs rename to src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs index b3577f2c0acf..c7ee26ca2508 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/src/Internal/KestrelMemoryPool.cs +++ b/src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs @@ -1,12 +1,9 @@ // 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.Buffers; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace System.Buffers { - public static class KestrelMemoryPool + internal static class MemoryPoolFactory { public static MemoryPool Create() { @@ -21,7 +18,5 @@ public static MemoryPool CreateSlabMemoryPool() { return new SlabMemoryPool(); } - - public static readonly int MinimumSegmentSize = 4096; } } diff --git a/src/Shared/Buffers.MemoryPool/SlabMemoryPool.cs b/src/Shared/Buffers.MemoryPool/SlabMemoryPool.cs index 0359c7219351..e80862401073 100644 --- a/src/Shared/Buffers.MemoryPool/SlabMemoryPool.cs +++ b/src/Shared/Buffers.MemoryPool/SlabMemoryPool.cs @@ -30,6 +30,11 @@ internal sealed class SlabMemoryPool : MemoryPool /// public override int MaxBufferSize { get; } = _blockSize; + /// + /// The size of a block. 4096 is chosen because most operating systems use 4k pages. + /// + public static int BlockSize => _blockSize; + /// /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab /// From 13a8b4949b427fd4e2c23769e222b49089d8791d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 16:23:35 -0700 Subject: [PATCH 58/78] Updated the refs --- .../Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj | 2 +- .../Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj | 4 ++-- ...Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj | 2 +- ...crosoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index a7e5f1b75a17..367e32ffec62 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index b334a87ed9db..fa82cf0beb3c 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -12,12 +12,12 @@ + - + - diff --git a/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj b/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj index 1e4f9cd02ab5..366ac6ec5c68 100644 --- a/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj +++ b/src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj index d41703c771eb..d5f1350fec57 100644 --- a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj +++ b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj @@ -6,7 +6,7 @@ - + From 89c6410b484635f225b7c1b3c5d79434bbcbcbaf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 19:18:07 -0700 Subject: [PATCH 59/78] Fixed breaks after rebase --- src/SignalR/SignalR.sln | 7 +++++++ .../UnitTests/HubConnectionTests.ConnectionLifecycle.cs | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/SignalR/SignalR.sln b/src/SignalR/SignalR.sln index bfe46375f32a..8a76052bfdb7 100644 --- a/src/SignalR/SignalR.sln +++ b/src/SignalR/SignalR.sln @@ -147,6 +147,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOv EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Protocols.Json", "common\Protocols.Json\src\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj", "{BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Metadata", "..\Http\Metadata\src\Microsoft.AspNetCore.Metadata.csproj", "{2E107FBB-2387-4A9F-A2CA-EFDF2E4DD64D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -405,6 +407,10 @@ Global {BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0}.Release|Any CPU.Build.0 = Release|Any CPU + {2E107FBB-2387-4A9F-A2CA-EFDF2E4DD64D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E107FBB-2387-4A9F-A2CA-EFDF2E4DD64D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E107FBB-2387-4A9F-A2CA-EFDF2E4DD64D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E107FBB-2387-4A9F-A2CA-EFDF2E4DD64D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -475,6 +481,7 @@ Global {762A7DD1-E45E-4EA3-8109-521E844AE613} = {1C8016A8-F362-45C7-9EA9-A1CCE7918F2F} {FD3A8F8D-2967-4635-86FC-CC49BAF651C1} = {EDE8E45E-A5D0-4F0E-B72C-7CC14146C60A} {BB52C0FB-19FD-485A-9EBD-3FC173ECAEA0} = {9FCD621E-E710-4991-B45C-1BABC977BEEC} + {2E107FBB-2387-4A9F-A2CA-EFDF2E4DD64D} = {EDE8E45E-A5D0-4F0E-B72C-7CC14146C60A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59} diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs index f48742474bc4..e4d001b2050d 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs @@ -85,7 +85,7 @@ Task ConnectionFactory(TransferFormat format) Task DisposeAsync(ConnectionContext connection) { - return ((TestConnection)connection).DisposeAsync(); + return connection.DisposeAsync().AsTask(); } var builder = new HubConnectionBuilder(); @@ -121,7 +121,7 @@ Task ConnectionFactory(TransferFormat format) return new TestConnection(onDispose: createCount == 1 ? onDisposeForFirstConnection : null).StartAsync(format); } - Task DisposeAsync(ConnectionContext connection) => ((TestConnection)connection).DisposeAsync(); + Task DisposeAsync(ConnectionContext connection) => connection.DisposeAsync().AsTask(); var builder = new HubConnectionBuilder(); var delegateConnectionFactory = new DelegateConnectionFactory(ConnectionFactory, DisposeAsync); @@ -600,7 +600,7 @@ public async Task HubConnectionClosesWithErrorIfTerminatedWithPartialMessage() var delegateConnectionFactory = new DelegateConnectionFactory( format => innerConnection.StartAsync(format), - connection => ((TestConnection)connection).DisposeAsync()); + connection => connection.DisposeAsync().AsTask()); builder.Services.AddSingleton(delegateConnectionFactory); var hubConnection = builder.Build(); From 3301c1e6829addbf0ccbb70e04f89dd2d899412d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 19:38:11 -0700 Subject: [PATCH 60/78] Handle a null pool --- .../Kestrel/Core/src/Internal/MemoryPoolExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs index 69c038807d52..d3b1f30a0263 100644 --- a/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs @@ -14,6 +14,11 @@ internal static class MemoryPoolExtensions /// public static int GetMinimumSegmentSize(this MemoryPool pool) { + if (pool == null) + { + return 4096; + } + return Math.Min(4096, pool.MaxBufferSize); } From 148bcfaed50574822e7062551499c3156f1444bc Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 21:11:25 -0700 Subject: [PATCH 61/78] Fixed segment size --- .../Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs | 2 +- src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index baf28b162dd8..4f481850d703 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -362,7 +362,7 @@ private static Pipe CreateDataPipe(MemoryPool pool) pauseWriterThreshold: 1, resumeWriterThreshold: 1, useSynchronizationContext: false, - minimumSegmentSize: pool.GetMinimumAllocSize() + minimumSegmentSize: pool.GetMinimumSegmentSize() )); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index 2405b93d3c2a..d9407a831895 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -493,7 +493,7 @@ private Pipe CreateRequestBodyPipe(uint windowSize) pauseWriterThreshold: windowSize + 1, resumeWriterThreshold: windowSize + 1, useSynchronizationContext: false, - minimumSegmentSize: _context.MemoryPool.GetMinimumAllocSize() + minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize() )); private (StreamCompletionFlags OldState, StreamCompletionFlags NewState) ApplyCompletionFlag(StreamCompletionFlags completionState) From 0930aa6918e672923558de07212c2600ccabda97 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 21:18:29 -0700 Subject: [PATCH 62/78] Updated the ref --- ...erver.Kestrel.Transport.Abstractions.netcoreapp3.0.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs index 9dbed1413912..618082bc4a8a 100644 --- a/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs @@ -1,12 +1,3 @@ // 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. -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public static partial class KestrelMemoryPool - { - public static readonly int MinimumSegmentSize; - public static System.Buffers.MemoryPool Create() { throw null; } - public static System.Buffers.MemoryPool CreateSlabMemoryPool() { throw null; } - } -} From 8a91fb14961e19e2bbe127bc7ddeee482e2814aa Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 26 May 2019 22:42:25 -0700 Subject: [PATCH 63/78] Use the right scheduler --- .../Transport.Sockets/src/Internal/SocketConnection.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index d48b4cbf8210..2ab12d340462 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -22,7 +22,6 @@ internal sealed class SocketConnection : TransportConnection private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); private readonly Socket _socket; - private readonly PipeScheduler _scheduler; private readonly ISocketsTrace _trace; private readonly SocketReceiver _receiver; private readonly SocketSender _sender; @@ -46,7 +45,6 @@ internal SocketConnection(Socket socket, _socket = socket; MemoryPool = memoryPool; - _scheduler = scheduler; _trace = trace; LocalEndPoint = _socket.LocalEndPoint; @@ -57,7 +55,7 @@ internal SocketConnection(Socket socket, // On *nix platforms, Sockets already dispatches to the ThreadPool. // Yes, the IOQueues are still used for the PipeSchedulers. This is intentional. // https://github.com/aspnet/KestrelHttpServer/issues/2573 - var awaiterScheduler = IsWindows ? _scheduler : PipeScheduler.Inline; + var awaiterScheduler = IsWindows ? scheduler : PipeScheduler.Inline; _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); @@ -65,8 +63,8 @@ internal SocketConnection(Socket socket, maxReadBufferSize ??= 0; maxWriteBufferSize ??= 0; - var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, awaiterScheduler, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); - var outputOptions = new PipeOptions(MemoryPool, awaiterScheduler, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); + var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, scheduler, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, scheduler, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); From dbe8832716d085cd1b237e0bf86b1eb194ea05b8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 27 May 2019 01:10:12 -0700 Subject: [PATCH 64/78] Skip ParseAddressUnixPipe on windows 7 --- src/Servers/Kestrel/Core/test/AddressBinderTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs index 00d0102f6b08..a0fd8ff0d4b2 100644 --- a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs +++ b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging.Abstractions; using Xunit; @@ -72,7 +73,8 @@ public void ParseAddressLocalhost() Assert.False(https); } - [Fact] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "UnixDomainSocketEndPoint is not supported on Windows 7")] + [ConditionalFact] public void ParseAddressUnixPipe() { var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https); @@ -89,7 +91,6 @@ public void ParseAddressUnixPipe() [InlineData("https://127.0.0.1", "127.0.0.1", 443, true)] public void ParseAddressIP(string address, string ip, int port, bool isHttps) { - var options = new KestrelServerOptions(); var listenOptions = AddressBinder.ParseAddress(address, out var https); Assert.IsType(listenOptions.EndPoint); Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address); From bf48a2a3bf7f540bac3f4681ccd7ecaa19c872c8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 27 May 2019 01:30:02 -0700 Subject: [PATCH 65/78] Only run unix pipe test on windows10 --- src/Servers/Kestrel/Core/test/AddressBinderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs index a0fd8ff0d4b2..76d517220710 100644 --- a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs +++ b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs @@ -73,7 +73,7 @@ public void ParseAddressLocalhost() Assert.False(https); } - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "UnixDomainSocketEndPoint is not supported on Windows 7")] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win8, WindowsVersions.Win81, WindowsVersions.Win2008R2, SkipReason = "UnixDomainSocketEndPoint is not supported on older versions of Windows")] [ConditionalFact] public void ParseAddressUnixPipe() { From ff56f85a1ca4e3449c9e90d3683dcad06b6d3919 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 27 May 2019 10:10:06 -0700 Subject: [PATCH 66/78] Don't capture the sync context --- .../TestTransport/InMemoryTransportConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs index fe184fa889c1..cc91efd9b27f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryTransportConnection.cs @@ -29,7 +29,7 @@ public InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger, LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 0); - var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(memoryPool, readerScheduler: scheduler), new PipeOptions(memoryPool, writerScheduler: scheduler)); + var pair = DuplexPipe.CreateConnectionPair(new PipeOptions(memoryPool, readerScheduler: scheduler, useSynchronizationContext: false), new PipeOptions(memoryPool, writerScheduler: scheduler, useSynchronizationContext: false)); Application = pair.Application; var wrapper = new ObservableDuplexPipe(pair.Transport); Transport = wrapper; From f0e7f4267cfd0c2687e5c6a8e5a4bd35bc9c327b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 27 May 2019 21:25:31 -0700 Subject: [PATCH 67/78] Some PR feedback - Fixed test bug found in another PR - Added copyright --- .../Connections.Abstractions/src/ConnectionDelegate.cs | 5 ++++- .../src/Features/IConnectionEndpointFeature.cs | 3 +++ .../src/Features/IConnectionUserFeature.cs | 5 ++++- .../Connections.Abstractions/src/FileHandleEndPoint.cs | 3 +++ .../Connections.Abstractions/src/IConnectionBuilder.cs | 5 ++++- .../Connections.Abstractions/src/IConnectionListener.cs | 3 +++ .../src/IConnectionListenerFactory.cs | 3 +++ .../Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs | 2 +- 8 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Servers/Connections.Abstractions/src/ConnectionDelegate.cs b/src/Servers/Connections.Abstractions/src/ConnectionDelegate.cs index f0d64d158724..dff0384f6018 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionDelegate.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionDelegate.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// 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; namespace Microsoft.AspNetCore.Connections { diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs index 4431fc963d0a..7c44146ede79 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs @@ -1,3 +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.Net; namespace Microsoft.AspNetCore.Connections.Features diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionUserFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionUserFeature.cs index 3efb362fc79d..7698693a54e0 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IConnectionUserFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionUserFeature.cs @@ -1,3 +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.Security.Claims; namespace Microsoft.AspNetCore.Connections.Features @@ -6,4 +9,4 @@ public interface IConnectionUserFeature { ClaimsPrincipal User { get; set; } } -} \ No newline at end of file +} diff --git a/src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs b/src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs index 1d7d6c49ca6a..41f4d50812f1 100644 --- a/src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs +++ b/src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs @@ -1,3 +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.Net; diff --git a/src/Servers/Connections.Abstractions/src/IConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/IConnectionBuilder.cs index 482574829287..5fe3ec25a0fe 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionBuilder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; namespace Microsoft.AspNetCore.Connections { diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 59029e676386..c9d0564447ce 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -1,3 +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.Net; using System.Threading; using System.Threading.Tasks; diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs index f1f7ddd860b4..b28724e1dd33 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -1,3 +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.Generic; using System.Net; diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index 88858881d640..f245f1a2d70b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -119,7 +119,7 @@ public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary); // Make sure the pending accept get yields - using (var socket = HttpClientSlim.GetSocket(address)) + using (var socket = await HttpClientSlim.GetSocket(address)) { await (await primary.DefaultTimeout()).DisposeAsync(); } From 1a143b73061f8d80215b34f8a6b7ca747c6da7cb Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 27 May 2019 23:14:57 -0700 Subject: [PATCH 68/78] PR feedback and fixes - Log a critical error if the call to AcceptAsync throws. - Fix null ref when AcceptAsync was pending during call to UnbindAsync or DisposeAsync - Fixed libuv test that could throw if the dispose happened before the connection was estabilished --- .../Core/src/Internal/ConnectionDispatcher.cs | 7 ++-- .../Core/test/ConnectionDispatcherTests.cs | 39 +++++++++++++++++++ .../src/Internal/LibuvConnection.cs | 1 + .../test/LibuvTransportTests.cs | 13 ------- .../src/SocketConnectionListener.cs | 8 +++- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index d7280301259a..4e354d811358 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -16,7 +16,7 @@ internal class ConnectionDispatcher private readonly ServiceContext _serviceContext; private readonly ConnectionDelegate _connectionDelegate; - private readonly TaskCompletionSource _acceptLoopTcs = new TaskCompletionSource(); + private readonly TaskCompletionSource _acceptLoopTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public ConnectionDispatcher(ServiceContext serviceContext, ConnectionDelegate connectionDelegate) { @@ -54,9 +54,10 @@ async Task AcceptConnectionsAsync() _ = Execute(new KestrelConnection(connection, _serviceContext.Log)); } } - catch (Exception) + catch (Exception ex) { - // REVIEW: Today the only way to end the accept loop is an exception + // REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang + Log.LogCritical(0, ex, "The connection listener failed to accept any new connections."); } finally { diff --git a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs index 985ab6b6f4b9..5302cccba714 100644 --- a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs +++ b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -50,6 +51,24 @@ public void OnConnectionCreatesLogScopeWithConnectionId() Assert.True(((TestKestrelTrace)serviceContext.Log).Logger.Scopes.IsEmpty); } + [Fact] + public async Task StartAcceptingConnectionsAsyncLogsIfAcceptAsyncThrows() + { + var serviceContext = new TestServiceContext(); + var logger = ((TestKestrelTrace)serviceContext.Log).Logger; + logger.ThrowOnCriticalErrors = false; + + var dispatcher = new ConnectionDispatcher(serviceContext, _ => Task.CompletedTask); + + await dispatcher.StartAcceptingConnections(new ThrowingListener()); + + Assert.Equal(1, logger.CriticalErrorsLogged); + var critical = logger.Messages.SingleOrDefault(m => m.LogLevel == LogLevel.Critical); + Assert.NotNull(critical); + Assert.IsType(critical.Exception); + Assert.Equal("Unexpected error listening", critical.Exception.Message); + } + [Fact] public async Task OnConnectionFiresOnCompleted() { @@ -95,5 +114,25 @@ public async Task OnConnectionOnCompletedExceptionCaught() Assert.Equal("An error occured running an IConnectionCompleteFeature.OnCompleted callback.", log.Arguments[2].ToString()); Assert.IsType(log.Arguments[3]); } + + private class ThrowingListener : IConnectionListener + { + public EndPoint EndPoint { get; set; } + + public ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + throw new InvalidOperationException("Unexpected error listening"); + } + + public ValueTask DisposeAsync() + { + return default; + } + + public ValueTask UnbindAsync(CancellationToken cancellationToken = default) + { + return default; + } + } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 9e06b2dc3dde..5491c7299fc8 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -72,6 +72,7 @@ public LibuvConnection(UvStreamHandle socket, private ILibuvTrace Log { get; } private LibuvThread Thread { get; } public override MemoryPool MemoryPool => Thread.MemoryPool; + public void Start() { _task = StartCore(); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 091fe1e8bb59..fbc1b894fb63 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -136,23 +136,10 @@ public async Task CallingAcceptAfterDisposeAsyncThrows() await transport.BindAsync(); var endpoint = (IPEndPoint)transport.EndPoint; - async Task ConnectAsync() - { - using (var socket = TestConnection.CreateConnectedLoopbackSocket(endpoint.Port)) - { - var read = await socket.ReceiveAsync(new byte[10], SocketFlags.None); - Assert.Equal(0, read); - } - } - - var connectTask = ConnectAsync(); - await transport.UnbindAsync(); await transport.DisposeAsync(); await Assert.ThrowsAsync(() => transport.AcceptAsync().AsTask()); - - await connectTask.DefaultTimeout(); } [Fact] diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 8d2134291c73..94a0c68b3085 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -108,8 +108,14 @@ public async ValueTask AcceptAsync(CancellationToken cancella return connection; } + catch (ObjectDisposedException) + { + // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done + return null; + } catch (SocketException) { + // REVIEW: Should we catch all exceptions here? or log them? return null; } } @@ -117,8 +123,6 @@ public async ValueTask AcceptAsync(CancellationToken cancella public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _listenSocket?.Dispose(); - _listenSocket = null; - return default; } From f91816222a569c40afe268aace1646186d6a88e8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 28 May 2019 14:58:07 -0700 Subject: [PATCH 69/78] Handle connection resets in accept --- .../src/SocketConnectionListener.cs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index 94a0c68b3085..b51e22e61ea1 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -94,29 +94,31 @@ internal void Bind() public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - // TODO: throw for overlapping accepts - try + while (true) { - var acceptSocket = await _listenSocket.AcceptAsync(); - acceptSocket.NoDelay = _options.NoDelay; + try + { + var acceptSocket = await _listenSocket.AcceptAsync(); + acceptSocket.NoDelay = _options.NoDelay; - var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize); + var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize); - connection.Start(); + connection.Start(); - _schedulerIndex = (_schedulerIndex + 1) % _numSchedulers; + _schedulerIndex = (_schedulerIndex + 1) % _numSchedulers; - return connection; - } - catch (ObjectDisposedException) - { - // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done - return null; - } - catch (SocketException) - { - // REVIEW: Should we catch all exceptions here? or log them? - return null; + return connection; + } + catch (ObjectDisposedException) + { + // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done + return null; + } + catch (SocketException) + { + // The connection got reset while it was in the backlog, so we try again. + _trace.ConnectionReset(connectionId: "(null)"); + } } } From f6fe0127a0437d7c039964d41021cb3aac8e74fa Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 28 May 2019 22:21:18 -0700 Subject: [PATCH 70/78] Set NoDelay to true by default --- src/Servers/Kestrel/Core/src/ListenOptions.cs | 8 ----- .../Core/src/LocalhostListenOptions.cs | 1 - .../Core/test/KestrelServerOptionsTests.cs | 33 +++++-------------- .../test/KestrelConfigurationBuilderTests.cs | 6 ---- .../src/LibuvTransportOptions.cs | 8 ++++- .../src/SocketTransportOptions.cs | 8 ++++- .../Kestrel/samples/SampleApp/Startup.cs | 7 +--- 7 files changed, 24 insertions(+), 47 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index a541f2e43cf1..3a850c25f9f4 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -68,14 +68,6 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) /// public KestrelServerOptions KestrelServerOptions { get; internal set; } - /// - /// Set to false to enable Nagle's algorithm for all connections. - /// - /// - /// Defaults to true. - /// - public bool NoDelay { get; set; } = true; - /// /// The protocols enabled on this endpoint. /// diff --git a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs index d3edacaeeb3f..c7e5f47cadb5 100644 --- a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs @@ -77,7 +77,6 @@ internal ListenOptions Clone(IPAddress address) var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port)) { KestrelServerOptions = KestrelServerOptions, - NoDelay = NoDelay, Protocols = Protocols, }; diff --git a/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs index 02e52db7f0a5..c8243b6025ad 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs @@ -8,20 +8,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class KestrelServerOptionsTests { - [Fact] - public void NoDelayDefaultsToTrue() - { - var o1 = new KestrelServerOptions(); - o1.Listen(IPAddress.Loopback, 0); - o1.Listen(IPAddress.Loopback, 0, d => - { - d.NoDelay = false; - }); - - Assert.True(o1.ListenOptions[0].NoDelay); - Assert.False(o1.ListenOptions[1].NoDelay); - } - [Fact] public void AllowSynchronousIODefaultsToFalse() { @@ -36,33 +22,32 @@ public void ConfigureEndpointDefaultsAppliesToNewEndpoints() var options = new KestrelServerOptions(); options.ListenLocalhost(5000); - Assert.True(options.ListenOptions[0].NoDelay); + Assert.Equal(HttpProtocols.Http1AndHttp2, options.ListenOptions[0].Protocols); options.ConfigureEndpointDefaults(opt => { - opt.NoDelay = false; + opt.Protocols = HttpProtocols.Http1; }); options.Listen(new IPEndPoint(IPAddress.Loopback, 5000), opt => { // ConfigureEndpointDefaults runs before this callback - Assert.False(opt.NoDelay); + Assert.Equal(HttpProtocols.Http1, opt.Protocols); }); - Assert.False(options.ListenOptions[1].NoDelay); + Assert.Equal(HttpProtocols.Http1, options.ListenOptions[1].Protocols); options.ListenLocalhost(5000, opt => { - Assert.False(opt.NoDelay); - opt.NoDelay = true; // Can be overriden + Assert.Equal(HttpProtocols.Http1, opt.Protocols); + opt.Protocols = HttpProtocols.Http2; // Can be overriden }); - Assert.True(options.ListenOptions[2].NoDelay); - + Assert.Equal(HttpProtocols.Http2, options.ListenOptions[2].Protocols); options.ListenAnyIP(5000, opt => { - Assert.False(opt.NoDelay); + opt.Protocols = HttpProtocols.Http2; }); - Assert.False(options.ListenOptions[3].NoDelay); + Assert.Equal(HttpProtocols.Http2, options.ListenOptions[3].Protocols); } } } diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs index c2bb4938459c..12d32431ae13 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs @@ -133,7 +133,6 @@ public void ConfigureDefaultsAppliesToNewConfigureEndpoints() serverOptions.ConfigureEndpointDefaults(opt => { - opt.NoDelay = false; opt.Protocols = HttpProtocols.Http2; }); @@ -156,13 +155,11 @@ public void ConfigureDefaultsAppliesToNewConfigureEndpoints() Assert.True(opt.IsHttps); Assert.NotNull(opt.HttpsOptions.ServerCertificate); Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode); - Assert.False(opt.ListenOptions.NoDelay); Assert.Equal(HttpProtocols.Http2, opt.ListenOptions.Protocols); }) .LocalhostEndpoint(5002, opt => { ran2 = true; - Assert.False(opt.NoDelay); Assert.Equal(HttpProtocols.Http2, opt.Protocols); }) .Load(); @@ -181,7 +178,6 @@ public void ConfigureEndpointDefaultCanEnableHttps() serverOptions.ConfigureEndpointDefaults(opt => { - opt.NoDelay = false; opt.UseHttps(TestResources.GetTestCertificate()); }); @@ -202,12 +198,10 @@ public void ConfigureEndpointDefaultCanEnableHttps() ran1 = true; Assert.True(opt.IsHttps); Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode); - Assert.False(opt.ListenOptions.NoDelay); }) .LocalhostEndpoint(5002, opt => { ran2 = true; - Assert.False(opt.NoDelay); }) .Load(); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index 7521eb00db8c..9110b39587bd 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -20,7 +20,13 @@ public class LibuvTransportOptions /// public int ThreadCount { get; set; } = ProcessorThreadCount; - public bool NoDelay { get; set; } + /// + /// Set to false to enable Nagle's algorithm for all connections. + /// + /// + /// Defaults to true. + /// + public bool NoDelay { get; set; } = true; public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index 9e6954d8bd75..2ec6c52fd076 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -17,7 +17,13 @@ public class SocketTransportOptions /// public int IOQueueCount { get; set; } = Math.Min(Environment.ProcessorCount, 16); - public bool NoDelay { get; set; } + /// + /// Set to false to enable Nagle's algorithm for all connections. + /// + /// + /// Defaults to true. + /// + public bool NoDelay { get; set; } = true; public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; diff --git a/src/Servers/Kestrel/samples/SampleApp/Startup.cs b/src/Servers/Kestrel/samples/SampleApp/Startup.cs index 4ab1d5090bc7..82f505c51007 100644 --- a/src/Servers/Kestrel/samples/SampleApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SampleApp/Startup.cs @@ -84,11 +84,6 @@ public static Task Main(string[] args) var basePort = context.Configuration.GetValue("BASE_PORT") ?? 5000; - options.ConfigureEndpointDefaults(opt => - { - opt.NoDelay = true; - }); - options.ConfigureHttpsDefaults(httpsOptions => { httpsOptions.SslProtocols = SslProtocols.Tls12; @@ -144,7 +139,7 @@ public static Task Main(string[] args) .Configure(context.Configuration.GetSection("Kestrel")) .Endpoint("NamedEndpoint", opt => { - opt.ListenOptions.NoDelay = true; + }) .Endpoint("NamedHttpsEndpoint", opt => { From 98d6afbd2c85ec57de84d3d59a238ccc953f37bf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 28 May 2019 23:22:23 -0700 Subject: [PATCH 71/78] Update the ref assembly --- .../Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs index 26ddcc4b4c75..8891d7feb782 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs @@ -148,7 +148,6 @@ internal ListenOptions() { } public ulong FileHandle { get { throw null; } } public System.Net.IPEndPoint IPEndPoint { get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions KestrelServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string SocketPath { get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } From e488e078293951177106886cd750ca234d54559d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 29 May 2019 01:25:51 -0700 Subject: [PATCH 72/78] PR feedback --- .../Core/src/Internal/Infrastructure/KestrelConnection.cs | 2 ++ .../Transport.Libuv/src/Internal/LibuvConnection.cs | 8 ++++---- .../Transport.Libuv/src/Internal/ListenerContext.cs | 2 +- .../Transport.Sockets/src/Internal/SocketConnection.cs | 8 ++++---- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index 434679ca641c..ccb51230c54c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -163,6 +163,8 @@ public void RequestClose() public void Complete() { _completionTcs.TrySetResult(null); + + _connectionClosingCts.Dispose(); } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs index 5491c7299fc8..962359bd8f43 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs @@ -30,7 +30,7 @@ internal partial class LibuvConnection : TransportConnection private volatile ConnectionAbortedException _abortReason; private MemoryHandle _bufferHandle; - private Task _task; + private Task _processingTask; public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, @@ -75,7 +75,7 @@ public LibuvConnection(UvStreamHandle socket, public void Start() { - _task = StartCore(); + _processingTask = StartCore(); } private async Task StartCore() @@ -174,9 +174,9 @@ public override async ValueTask DisposeAsync() Transport.Input.Complete(); Transport.Output.Complete(); - if (_task != null) + if (_processingTask != null) { - await _task; + await _processingTask; } _connectionClosedTokenSource.Dispose(); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index 46c0f835aabf..07827e416bcd 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal class ListenerContext { - private readonly Channel _acceptQueue = Channel.CreateUnbounded(); + private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true }); public ListenerContext(LibuvTransportContext transportContext) { diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index 2ab12d340462..1c867f3199d2 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -30,7 +30,7 @@ internal sealed class SocketConnection : TransportConnection private readonly object _shutdownLock = new object(); private volatile bool _socketDisposed; private volatile Exception _shutdownReason; - private Task _task; + private Task _processingTask; internal SocketConnection(Socket socket, MemoryPool memoryPool, @@ -81,7 +81,7 @@ internal SocketConnection(Socket socket, public void Start() { - _task = StartAsync(); + _processingTask = StartAsync(); } private async Task StartAsync() @@ -136,9 +136,9 @@ public override async ValueTask DisposeAsync() Transport.Input.Complete(); Transport.Output.Complete(); - if (_task != null) + if (_processingTask != null) { - await _task; + await _processingTask; } _connectionClosedTokenSource.Dispose(); From 9e341c3c9cb626361019a8f46dec36b6f821a73b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 29 May 2019 01:42:36 -0700 Subject: [PATCH 73/78] Handle connection reset --- .../Transport.Libuv/test/LibuvTransportTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index fbc1b894fb63..8ee40b4027e9 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -110,8 +110,15 @@ async Task ConnectAsync() { using (var socket = TestConnection.CreateConnectedLoopbackSocket(endpoint.Port)) { - var read = await socket.ReceiveAsync(new byte[10], SocketFlags.None); - Assert.Equal(0, read); + try + { + var read = await socket.ReceiveAsync(new byte[10], SocketFlags.None); + Assert.Equal(0, read); + } + catch (SocketException) + { + // The connection can be reset sometimes + } } } From 21ac350c1f0ab373a47f40da0743abf3953e2207 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 29 May 2019 23:25:44 -0700 Subject: [PATCH 74/78] Some cleanup - Added ConfigureAwait(false) to DisposeAsync path - Added await using in tests to make sure threads are disposed - Remove more dead code --- .../src/Internal/LibuvConnectionListener.cs | 4 +-- .../test/LibuvTransportTests.cs | 21 +++++-------- .../test/ListenerPrimaryTests.cs | 31 ------------------- 3 files changed, 10 insertions(+), 46 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index 1e8ea35b2631..eab59a0d30e9 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -91,12 +91,12 @@ public async ValueTask DisposeAsync() if (_acceptEnumerator != null) { - while (await _acceptEnumerator.MoveNextAsync()) + while (await _acceptEnumerator.MoveNextAsync().ConfigureAwait(false)) { _acceptEnumerator.Current.Abort(); } - await _acceptEnumerator.DisposeAsync(); + await _acceptEnumerator.DisposeAsync().ConfigureAwait(false); } _listeners.Clear(); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 8ee40b4027e9..8c2c9376d405 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -51,7 +51,7 @@ public async Task TransportCanBindUnbindAndStop() public async Task ConnectionCanReadAndWrite() { var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); + await using var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); var endpoint = (IPEndPoint)transport.EndPoint; @@ -85,7 +85,7 @@ async Task EchoServerAsync() var read = 0; while (read < data.Length) { - read += socket.Receive(buffer, read, buffer.Length - read, SocketFlags.None); + read += await socket.ReceiveAsync(buffer.AsMemory(read, buffer.Length - read), SocketFlags.None); } Assert.Equal(data, buffer); @@ -94,7 +94,6 @@ async Task EchoServerAsync() await serverTask.DefaultTimeout(); await transport.UnbindAsync(); - await transport.DisposeAsync(); } [Fact] @@ -153,7 +152,7 @@ public async Task CallingAcceptAfterDisposeAsyncThrows() public async Task CallingDisposeAsyncWillYieldPendingAccepts() { var transportContext = new TestLibuvTransportContext(); - var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); + await using var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0)); await transport.BindAsync(); @@ -164,8 +163,6 @@ public async Task CallingDisposeAsyncWillYieldPendingAccepts() var connection = await acceptTask.DefaultTimeout(); Assert.Null(connection); - - await transport.DisposeAsync(); } [ConditionalTheory] @@ -187,7 +184,7 @@ public async Task OneToTenThreads(int threadCount) Options = new LibuvTransportOptions { ThreadCount = threadCount } }; - var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); + await using var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); await transport.BindAsync(); listenOptions.EndPoint = transport.EndPoint; @@ -210,16 +207,14 @@ public async Task OneToTenThreads(int threadCount) } } - await transport.UnbindAsync().ConfigureAwait(false); + await transport.UnbindAsync(); - await acceptTask.ConfigureAwait(false); + await acceptTask; - if (!await serviceContext.ConnectionManager.CloseAllConnectionsAsync(default).ConfigureAwait(false)) + if (!await serviceContext.ConnectionManager.CloseAllConnectionsAsync(default)) { - await serviceContext.ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false); + await serviceContext.ConnectionManager.AbortAllConnectionsAsync(); } - - await transport.DisposeAsync().ConfigureAwait(false); } } } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs index f245f1a2d70b..b3ed1b94fe07 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; @@ -305,34 +303,5 @@ private static Uri GetUri(EndPoint endpoint) { return new Uri($"http://{endpoint}"); } - - private class ConnectionBuilder : IConnectionBuilder - { - private readonly List> _components = new List>(); - - public IServiceProvider ApplicationServices { get; set; } - - public IConnectionBuilder Use(Func middleware) - { - _components.Add(middleware); - return this; - } - - public ConnectionDelegate Build() - { - ConnectionDelegate app = context => - { - return Task.CompletedTask; - }; - - for (int i = _components.Count - 1; i >= 0; i--) - { - var component = _components[i]; - app = component(app); - } - - return app; - } - } } } From 940e6fa15d28c6da2838555ee9018ab53da7686e Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 30 May 2019 00:54:30 -0700 Subject: [PATCH 75/78] Make everything async and clean up the accept loop --- .../test/LibuvTransportTests.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 8c2c9376d405..d18af3aa39bf 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -58,19 +58,27 @@ public async Task ConnectionCanReadAndWrite() async Task EchoServerAsync() { - await using var connection = await transport.AcceptAsync(); - while (true) { - var result = await connection.Transport.Input.ReadAsync(); + await using var connection = await transport.AcceptAsync(); - if (result.IsCompleted) + if (connection == null) { break; } - await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); - connection.Transport.Input.AdvanceTo(result.Buffer.End); + while (true) + { + var result = await connection.Transport.Input.ReadAsync(); + + if (result.IsCompleted) + { + break; + } + await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); + + connection.Transport.Input.AdvanceTo(result.Buffer.End); + } } } @@ -79,7 +87,7 @@ async Task EchoServerAsync() using (var socket = TestConnection.CreateConnectedLoopbackSocket(endpoint.Port)) { var data = Encoding.ASCII.GetBytes("Hello World"); - socket.Send(data); + await socket.SendAsync(data, SocketFlags.None); var buffer = new byte[data.Length]; var read = 0; @@ -91,9 +99,9 @@ async Task EchoServerAsync() Assert.Equal(data, buffer); } - await serverTask.DefaultTimeout(); - await transport.UnbindAsync(); + + await serverTask.DefaultTimeout(); } [Fact] From 1cec23c6c923b87f1c47d4fab048a900a4ddae57 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 30 May 2019 01:26:21 -0700 Subject: [PATCH 76/78] Improve aborting queued connection logic - Add a method to quickly run through each of the queued aborting all connections instead of using the IAsyncEnumerator. --- .../src/Internal/LibuvConnectionListener.cs | 9 ++------ .../src/Internal/ListenerContext.cs | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index eab59a0d30e9..d1b2e9a8e765 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -89,14 +89,9 @@ public async ValueTask DisposeAsync() await UnbindAsync().ConfigureAwait(false); - if (_acceptEnumerator != null) + foreach (var listener in _listeners) { - while (await _acceptEnumerator.MoveNextAsync().ConfigureAwait(false)) - { - _acceptEnumerator.Current.Abort(); - } - - await _acceptEnumerator.DisposeAsync().ConfigureAwait(false); + await listener.AbortQueuedConnectionAsync().ConfigureAwait(false); } _listeners.Clear(); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index 07827e416bcd..528ec5ca3b68 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -17,7 +17,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal class ListenerContext { - private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true }); + // Single reader, single writer queue since all writes happen from the uv thread and reads happen sequentially + private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions + { + SingleReader = true, + SingleWriter = true + }); public ListenerContext(LibuvTransportContext transportContext) { @@ -47,6 +52,22 @@ public async ValueTask AcceptAsync(CancellationToken cancellati return null; } + /// + /// Aborts all unaccepted connections in the queue + /// + /// + public async Task AbortQueuedConnectionAsync() + { + while (await _acceptQueue.Reader.WaitToReadAsync()) + { + while (_acceptQueue.Reader.TryRead(out var connection)) + { + // REVIEW: Pass an abort reason? + connection.Abort(); + } + } + } + /// /// Creates a socket which can be used to accept an incoming connection. /// From c4cdab01ee39ea56ce3d1de9889d959de258c6a0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 30 May 2019 16:05:17 -0700 Subject: [PATCH 77/78] Added logs --- .../Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs index b5512a27dfc9..88fa78b3c091 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs @@ -112,9 +112,13 @@ public async Task StopAsync(TimeSpan timeout) Post(t => t.AllowStop()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { + _log.LogWarning($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(AllowStop)}"); + Post(t => t.OnStopRude()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { + _log.LogWarning($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(OnStopRude)}."); + Post(t => t.OnStopImmediate()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { From 74868c1cb6d2714d00780d262c56f7aa4aa1f2d4 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 30 May 2019 16:12:20 -0700 Subject: [PATCH 78/78] Log critical for ungraceful loop shutdown --- .../Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs index 88fa78b3c091..de212ca89df7 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs @@ -112,17 +112,17 @@ public async Task StopAsync(TimeSpan timeout) Post(t => t.AllowStop()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { - _log.LogWarning($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(AllowStop)}"); + _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(AllowStop)}"); Post(t => t.OnStopRude()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { - _log.LogWarning($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(OnStopRude)}."); + _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(OnStopRude)}."); Post(t => t.OnStopImmediate()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { - _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread."); + _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(OnStopImmediate)}."); } } }