Sfoglia il codice sorgente

Implement new bedrock listener abstraction and re-plat Kestrel on top (#10321)

This is a massive set of changes to Kestrel to remove the existing pubternal transport layer and implement a public facing API for listeners and clients, see the details here #10308.

This change only has the server side pieces of the story as I don't want to add the client APIs without having ported SignalR to use them. Here are the highlights:

- Transport.Abstractions is empty (will be removed in a separate PR as it requires removing it from a ton of places)
- TransportConnection has been moved to Connection.Abstractions (we can decide if we need to consolidate with DefaultConnectionContext in a later PR)
- Added FileHandleEndPoint which allows binding to a file handle (could be a pipe or tcp handle)
ListenOptions has been gutted for most pubternal API and returns various types of binding information . The source of truth is the EndPoint instance.
- Cleaned up a bunch of libuv tests decoupling them from Kestrel.Core

## Breaking changes

- Removing pubternal API is itself a breaking change but one that we already planned to do.
- We've removed the ability to set the scheduling mode on Kestrel
- DisposeAsync was added to ConnectionContext 
- NoDelay was removed from ListenOptions. This has been moved to each of the transports. One major difference though is that it's no longer localized per endpoint but is global. We'd need a derived EndPoint type (or maybe extend IPEndPoint) to store both the socket options and the binding information.
David Fowler 6 anni fa
parent
commit
04bf1bf32e
100 ha cambiato i file con 1721 aggiunte e 1919 eliminazioni
  1. 63 2
      src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs
  2. 14 0
      src/Servers/Connections.Abstractions/src/ConnectionContext.cs
  3. 4 1
      src/Servers/Connections.Abstractions/src/ConnectionDelegate.cs
  4. 14 2
      src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs
  5. 13 0
      src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs
  6. 4 1
      src/Servers/Connections.Abstractions/src/Features/IConnectionUserFeature.cs
  7. 30 0
      src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs
  8. 3 3
      src/Servers/Connections.Abstractions/src/FileHandleType.cs
  9. 4 1
      src/Servers/Connections.Abstractions/src/IConnectionBuilder.cs
  10. 20 0
      src/Servers/Connections.Abstractions/src/IConnectionListener.cs
  11. 17 0
      src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs
  12. 44 0
      src/Servers/Connections.Abstractions/src/TransportConnection.FeatureCollection.cs
  13. 5 143
      src/Servers/Connections.Abstractions/src/TransportConnection.Generated.cs
  14. 62 0
      src/Servers/Connections.Abstractions/src/TransportConnection.cs
  15. 1 1
      src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
  16. 6 9
      src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs
  17. 5 4
      src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs
  18. 2 2
      src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs
  19. 41 78
      src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs
  20. 1 2
      src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs
  21. 3 4
      src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs
  22. 1 2
      src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs
  23. 1 2
      src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs
  24. 4 4
      src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs
  25. 2 14
      src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs
  26. 51 2
      src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs
  27. 0 53
      src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs
  28. 2 2
      src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs
  29. 154 9
      src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs
  30. 5 6
      src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs
  31. 31 0
      src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs
  32. 21 32
      src/Servers/Kestrel/Core/src/KestrelServer.cs
  33. 0 9
      src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
  34. 18 73
      src/Servers/Kestrel/Core/src/ListenOptions.cs
  35. 1 3
      src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs
  36. 1 1
      src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
  37. 8 10
      src/Servers/Kestrel/Core/test/AddressBinderTests.cs
  38. 40 22
      src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs
  39. 1 2
      src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs
  40. 4 3
      src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs
  41. 2 2
      src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs
  42. 9 24
      src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs
  43. 70 60
      src/Servers/Kestrel/Core/test/KestrelServerTests.cs
  44. 2 2
      src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj
  45. 1 2
      src/Servers/Kestrel/Core/test/OutputProducerTests.cs
  46. 1 39
      src/Servers/Kestrel/Core/test/PipeOptionsTests.cs
  47. 1 2
      src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs
  48. 1 2
      src/Servers/Kestrel/Core/test/StartLineTests.cs
  49. 1 2
      src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs
  50. 2 2
      src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
  51. 0 6
      src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs
  52. 2 3
      src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj
  53. 6 6
      src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs
  54. 0 110
      src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs
  55. 0 12
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/IApplicationTransportFeature.cs
  56. 0 12
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/IConnectionDispatcher.cs
  57. 0 46
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/IEndPointInformation.cs
  58. 0 15
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransport.cs
  59. 0 10
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportFactory.cs
  60. 0 14
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportSchedulerFeature.cs
  61. 0 15
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/ListenType.cs
  62. 0 12
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/SchedulingMode.cs
  63. 0 188
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs
  64. 0 121
      src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs
  65. 1 1
      src/Servers/Kestrel/Transport.Abstractions/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj
  66. 2 1
      src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj
  67. 3 0
      src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs
  68. 64 20
      src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs
  69. 112 21
      src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs
  70. 20 17
      src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs
  71. 0 3
      src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs
  72. 11 7
      src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs
  73. 25 19
      src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs
  74. 67 17
      src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs
  75. 3 3
      src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs
  76. 8 6
      src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs
  77. 15 3
      src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs
  78. 8 2
      src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj
  79. 3 3
      src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs
  80. 67 93
      src/Servers/Kestrel/Transport.Libuv/test/LibuvConnectionTests.cs
  81. 41 4
      src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs
  82. 2 4
      src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs
  83. 124 39
      src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs
  84. 97 132
      src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs
  85. 0 31
      src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/MockConnectionDispatcher.cs
  86. 0 1
      src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs
  87. 1 1
      src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj
  88. 6 3
      src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs
  89. 61 24
      src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs
  90. 6 1
      src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj
  91. 139 0
      src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs
  92. 0 205
      src/Servers/Kestrel/Transport.Sockets/src/SocketTransport.cs
  93. 13 33
      src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs
  94. 15 3
      src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs
  95. 3 3
      src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs
  96. 1 2
      src/Servers/Kestrel/perf/Kestrel.Performance/ChunkWriterBenchmark.cs
  97. 1 2
      src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs
  98. 1 2
      src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs
  99. 1 2
      src/Servers/Kestrel/perf/Kestrel.Performance/Http1ReadingBenchmark.cs
  100. 1 2
      src/Servers/Kestrel/perf/Kestrel.Performance/Http1WritingBenchmark.cs

+ 63 - 2
src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs

@@ -30,12 +30,16 @@ namespace Microsoft.AspNetCore.Connections
     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<object, object> 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,35 @@ namespace Microsoft.AspNetCore.Connections
         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<object, object> 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 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
     {
@@ -91,12 +110,49 @@ namespace Microsoft.AspNetCore.Connections
         Microsoft.AspNetCore.Connections.ConnectionDelegate Build();
         Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Connections.ConnectionDelegate> middleware);
     }
+    public partial interface IConnectionListener
+    {
+        System.Net.EndPoint EndPoint { get; }
+        System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+        System.Threading.Tasks.ValueTask DisposeAsync();
+        System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+    }
+    public partial interface IConnectionListenerFactory
+    {
+        System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+    }
     [System.FlagsAttribute]
     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.Generic.KeyValuePair<System.Type, object>>, 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<object, object> 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<byte> MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
+        System.Collections.Generic.IDictionary<object, object> 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<byte> 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<TFeature>() { throw null; }
+        void Microsoft.AspNetCore.Http.Features.IFeatureCollection.Set<TFeature>(TFeature feature) { }
+        System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.Type, object>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type,System.Object>>.GetEnumerator() { throw null; }
+        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+    }
 }
 namespace Microsoft.AspNetCore.Connections.Features
 {
@@ -104,6 +160,11 @@ namespace Microsoft.AspNetCore.Connections.Features
     {
         void OnCompleted(System.Func<object, System.Threading.Tasks.Task> 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<object> action, object state);

+ 14 - 0
src/Servers/Connections.Abstractions/src/ConnectionContext.cs

@@ -3,6 +3,9 @@
 
 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;
 
@@ -18,6 +21,12 @@ namespace Microsoft.AspNetCore.Connections
 
         public abstract IDuplexPipe Transport { get; set; }
 
+        public virtual CancellationToken ConnectionClosed { get; set; }
+
+        public virtual EndPoint LocalEndPoint { get; set; }
+
+        public virtual EndPoint RemoteEndPoint { get; set; }
+
         public virtual void Abort(ConnectionAbortedException abortReason)
         {
             // We expect this to be overridden, but this helps maintain back compat
@@ -27,5 +36,10 @@ namespace Microsoft.AspNetCore.Connections
         }
 
         public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort()."));
+
+        public virtual ValueTask DisposeAsync()
+        {
+            return default;
+        }
     }
 }

+ 4 - 1
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
 {

+ 14 - 2
src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs

@@ -4,8 +4,10 @@
 using System;
 using System.Collections.Generic;
 using System.IO.Pipelines;
+using System.Net;
 using System.Security.Claims;
 using System.Threading;
+using System.Threading.Tasks;
 using Microsoft.AspNetCore.Connections.Features;
 using Microsoft.AspNetCore.Http.Features;
 
@@ -17,7 +19,8 @@ namespace Microsoft.AspNetCore.Connections
                                             IConnectionItemsFeature,
                                             IConnectionTransportFeature,
                                             IConnectionUserFeature,
-                                            IConnectionLifetimeFeature
+                                            IConnectionLifetimeFeature,
+                                            IConnectionEndPointFeature
     {
         private CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource();
 
@@ -42,6 +45,7 @@ namespace Microsoft.AspNetCore.Connections
             Features.Set<IConnectionIdFeature>(this);
             Features.Set<IConnectionTransportFeature>(this);
             Features.Set<IConnectionLifetimeFeature>(this);
+            Features.Set<IConnectionEndPointFeature>(this);
         }
 
         public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application)
@@ -63,7 +67,9 @@ namespace Microsoft.AspNetCore.Connections
 
         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; }
 
         public override void Abort(ConnectionAbortedException abortReason)
         {
@@ -74,5 +80,11 @@ namespace Microsoft.AspNetCore.Connections
         {
             _connectionClosedTokenSource.Dispose();
         }
+
+        public override ValueTask DisposeAsync()
+        {
+            _connectionClosedTokenSource.Dispose();
+            return base.DisposeAsync();
+        }
     }
 }

+ 13 - 0
src/Servers/Connections.Abstractions/src/Features/IConnectionEndpointFeature.cs

@@ -0,0 +1,13 @@
+// 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
+{
+    public interface IConnectionEndPointFeature
+    {
+        EndPoint LocalEndPoint { get; set; }
+        EndPoint RemoteEndPoint { get; set; }
+    }
+}

+ 4 - 1
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 @@ namespace Microsoft.AspNetCore.Connections.Features
     {
         ClaimsPrincipal User { get; set; }
     }
-}
+}

+ 30 - 0
src/Servers/Connections.Abstractions/src/FileHandleEndPoint.cs

@@ -0,0 +1,30 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Connections
+{
+    public class FileHandleEndPoint : EndPoint
+    {
+        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; }
+        public FileHandleType FileHandleType { get; }
+    }
+}

+ 3 - 3
src/Servers/Kestrel/Transport.Abstractions/src/Internal/FileHandleType.cs → src/Servers/Connections.Abstractions/src/FileHandleType.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
+namespace Microsoft.AspNetCore.Connections
 {
     /// <summary>
-    /// Enumerates the <see cref="IEndPointInformation.FileHandle"/> types.
+    /// Enumerates the <see cref="FileHandleEndPoint"/> types.
     /// </summary>
     public enum FileHandleType
     {

+ 4 - 1
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
 {

+ 20 - 0
src/Servers/Connections.Abstractions/src/IConnectionListener.cs

@@ -0,0 +1,20 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Connections
+{
+    public interface IConnectionListener
+    {
+        EndPoint EndPoint { get; }
+
+        ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default);
+
+        ValueTask UnbindAsync(CancellationToken cancellationToken = default);
+
+        ValueTask DisposeAsync();
+    }
+}

+ 17 - 0
src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs

@@ -0,0 +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;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Connections
+{
+    public interface IConnectionListenerFactory
+    {
+        ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default);
+    }
+}

+ 44 - 0
src/Servers/Connections.Abstractions/src/TransportConnection.FeatureCollection.cs

@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Threading;
+using Microsoft.AspNetCore.Connections.Features;
+
+namespace Microsoft.AspNetCore.Connections
+{
+    public partial class TransportConnection : IConnectionIdFeature,
+                                               IConnectionTransportFeature,
+                                               IConnectionItemsFeature,
+                                               IMemoryPoolFeature,
+                                               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.
+        // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs
+
+        MemoryPool<byte> IMemoryPoolFeature.MemoryPool => MemoryPool;
+
+        IDuplexPipe IConnectionTransportFeature.Transport
+        {
+            get => Transport;
+            set => Transport = value;
+        }
+
+        IDictionary<object, object> IConnectionItemsFeature.Items
+        {
+            get => Items;
+            set => Items = value;
+        }
+
+        CancellationToken IConnectionLifetimeFeature.ConnectionClosed
+        {
+            get => ConnectionClosed;
+            set => ConnectionClosed = value;
+        }
+
+        void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort()."));
+    }
+}

+ 5 - 143
src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.Generated.cs → src/Servers/Connections.Abstractions/src/TransportConnection.Generated.cs

@@ -8,33 +8,21 @@ using System.Collections.Generic;
 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
     {
-        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);
         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);
-        private static readonly Type IConnectionCompleteFeatureType = typeof(IConnectionCompleteFeature);
 
-        private object _currentIHttpConnectionFeature;
         private object _currentIConnectionIdFeature;
         private object _currentIConnectionTransportFeature;
         private object _currentIConnectionItemsFeature;
         private object _currentIMemoryPoolFeature;
-        private object _currentIApplicationTransportFeature;
-        private object _currentITransportSchedulerFeature;
         private object _currentIConnectionLifetimeFeature;
-        private object _currentIConnectionHeartbeatFeature;
-        private object _currentIConnectionLifetimeNotificationFeature;
-        private object _currentIConnectionCompleteFeature;
 
         private int _featureRevision;
 
@@ -42,17 +30,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
 
         private void FastReset()
         {
-            _currentIHttpConnectionFeature = this;
             _currentIConnectionIdFeature = this;
             _currentIConnectionTransportFeature = this;
             _currentIConnectionItemsFeature = this;
             _currentIMemoryPoolFeature = this;
-            _currentIApplicationTransportFeature = this;
-            _currentITransportSchedulerFeature = this;
             _currentIConnectionLifetimeFeature = this;
-            _currentIConnectionHeartbeatFeature = this;
-            _currentIConnectionLifetimeNotificationFeature = this;
-            _currentIConnectionCompleteFeature = this;
 
         }
 
@@ -108,11 +90,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
             get
             {
                 object feature = null;
-                if (key == IHttpConnectionFeatureType)
-                {
-                    feature = _currentIHttpConnectionFeature;
-                }
-                else if (key == IConnectionIdFeatureType)
+                if (key == IConnectionIdFeatureType)
                 {
                     feature = _currentIConnectionIdFeature;
                 }
@@ -128,30 +106,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
                 {
                     feature = _currentIMemoryPoolFeature;
                 }
-                else if (key == IApplicationTransportFeatureType)
-                {
-                    feature = _currentIApplicationTransportFeature;
-                }
-                else if (key == ITransportSchedulerFeatureType)
-                {
-                    feature = _currentITransportSchedulerFeature;
-                }
                 else if (key == IConnectionLifetimeFeatureType)
                 {
                     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);
@@ -164,11 +122,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
             {
                 _featureRevision++;
 
-                if (key == IHttpConnectionFeatureType)
-                {
-                    _currentIHttpConnectionFeature = value;
-                }
-                else if (key == IConnectionIdFeatureType)
+                if (key == IConnectionIdFeatureType)
                 {
                     _currentIConnectionIdFeature = value;
                 }
@@ -184,30 +138,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
                 {
                     _currentIMemoryPoolFeature = value;
                 }
-                else if (key == IApplicationTransportFeatureType)
-                {
-                    _currentIApplicationTransportFeature = value;
-                }
-                else if (key == ITransportSchedulerFeatureType)
-                {
-                    _currentITransportSchedulerFeature = value;
-                }
                 else if (key == IConnectionLifetimeFeatureType)
                 {
                     _currentIConnectionLifetimeFeature = value;
                 }
-                else if (key == IConnectionHeartbeatFeatureType)
-                {
-                    _currentIConnectionHeartbeatFeature = value;
-                }
-                else if (key == IConnectionLifetimeNotificationFeatureType)
-                {
-                    _currentIConnectionLifetimeNotificationFeature = value;
-                }
-                else if (key == IConnectionCompleteFeatureType)
-                {
-                    _currentIConnectionCompleteFeature = value;
-                }
                 else
                 {
                     ExtraFeatureSet(key, value);
@@ -218,11 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
         TFeature IFeatureCollection.Get<TFeature>()
         {
             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;
             }
@@ -238,30 +168,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
             {
                 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;
             }
-            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)));
@@ -273,11 +183,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
         void IFeatureCollection.Set<TFeature>(TFeature feature)
         {
             _featureRevision++;
-            if (typeof(TFeature) == typeof(IHttpConnectionFeature))
-            {
-                _currentIHttpConnectionFeature = feature;
-            }
-            else if (typeof(TFeature) == typeof(IConnectionIdFeature))
+            if (typeof(TFeature) == typeof(IConnectionIdFeature))
             {
                 _currentIConnectionIdFeature = feature;
             }
@@ -293,30 +199,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
             {
                 _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;
             }
-            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);
@@ -325,10 +211,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
 
         private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
         {
-            if (_currentIHttpConnectionFeature != null)
-            {
-                yield return new KeyValuePair<Type, object>(IHttpConnectionFeatureType, _currentIHttpConnectionFeature);
-            }
             if (_currentIConnectionIdFeature != null)
             {
                 yield return new KeyValuePair<Type, object>(IConnectionIdFeatureType, _currentIConnectionIdFeature);
@@ -345,30 +227,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
             {
                 yield return new KeyValuePair<Type, object>(IMemoryPoolFeatureType, _currentIMemoryPoolFeature);
             }
-            if (_currentIApplicationTransportFeature != null)
-            {
-                yield return new KeyValuePair<Type, object>(IApplicationTransportFeatureType, _currentIApplicationTransportFeature);
-            }
-            if (_currentITransportSchedulerFeature != null)
-            {
-                yield return new KeyValuePair<Type, object>(ITransportSchedulerFeatureType, _currentITransportSchedulerFeature);
-            }
             if (_currentIConnectionLifetimeFeature != null)
             {
                 yield return new KeyValuePair<Type, object>(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature);
             }
-            if (_currentIConnectionHeartbeatFeature != null)
-            {
-                yield return new KeyValuePair<Type, object>(IConnectionHeartbeatFeatureType, _currentIConnectionHeartbeatFeature);
-            }
-            if (_currentIConnectionLifetimeNotificationFeature != null)
-            {
-                yield return new KeyValuePair<Type, object>(IConnectionLifetimeNotificationFeatureType, _currentIConnectionLifetimeNotificationFeature);
-            }
-            if (_currentIConnectionCompleteFeature != null)
-            {
-                yield return new KeyValuePair<Type, object>(IConnectionCompleteFeatureType, _currentIConnectionCompleteFeature);
-            }
 
             if (MaybeExtra != null)
             {

+ 62 - 0
src/Servers/Connections.Abstractions/src/TransportConnection.cs

@@ -0,0 +1,62 @@
+// 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 Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Connections
+{
+    public abstract partial class TransportConnection : ConnectionContext
+    {
+        private IDictionary<object, object> _items;
+
+        public TransportConnection()
+        {
+            FastReset();
+        }
+
+        public override EndPoint LocalEndPoint { get; set; }
+        public override EndPoint RemoteEndPoint { get; set; }
+
+        public override string ConnectionId { get; set; }
+
+        public override IFeatureCollection Features => this;
+
+        public virtual MemoryPool<byte> MemoryPool { get; }
+        
+        public override IDuplexPipe Transport { get; set; }
+
+        public IDuplexPipe Application { get; set; }
+
+        public override IDictionary<object, object> Items
+        {
+            get
+            {
+                // Lazily allocate connection metadata
+                return _items ?? (_items = new ConnectionItems());
+            }
+            set
+            {
+                _items = value;
+            }
+        }
+
+        public override CancellationToken ConnectionClosed { 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.
+        // That said, all derived types should override this method should override
+        // this implementation of Abort because canceling pending output reads is not
+        // sufficient to abort the connection if there is backpressure.
+        public override void Abort(ConnectionAbortedException abortReason)
+        {
+            Application.Input.CancelPendingRead();
+        }
+    }
+}

+ 1 - 1
src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.csproj

@@ -7,7 +7,7 @@
     <Compile Include="Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs" />
     <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions"  />
     <Reference Include="Microsoft.AspNetCore.Http"  />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions"  />
+    <Reference Include="Microsoft.AspNetCore.Connections.Abstractions"  />
     <Reference Include="Microsoft.AspNetCore.WebUtilities"  />
     <Reference Include="Microsoft.Extensions.Configuration.Binder"  />
     <Reference Include="Microsoft.Extensions.Logging.Abstractions"  />

+ 6 - 9
src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs

@@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
     }
     public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable
     {
-        public KestrelServer(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions> options, Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.ITransportFactory transportFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
+        public KestrelServer(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions> 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
         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,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
         public void ListenUnixSocket(string socketPath) { }
         public void ListenUnixSocket(string socketPath, System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions> 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<Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal.IConnectionAdapter> 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<Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Connections.ConnectionDelegate> middleware) { throw null; }

+ 5 - 4
src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs

@@ -6,26 +6,27 @@ using System.IO;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
                 while (true)
                 {
 
-                    var outputBuffer = Input.Writer.GetMemory(MinAllocBufferSize);
+                    var outputBuffer = Input.Writer.GetMemory(_minAllocBufferSize);
                     var bytesRead = await stream.ReadAsync(outputBuffer);
                     Input.Writer.Advance(bytesRead);
 

+ 2 - 2
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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
                 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);
             }
         }

+ 41 - 78
src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs

@@ -2,23 +2,21 @@
 // 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
 {
-    internal class ConnectionDispatcher : IConnectionDispatcher
+    internal class ConnectionDispatcher
     {
         private static long _lastConnectionId = long.MinValue;
 
         private readonly ServiceContext _serviceContext;
         private readonly ConnectionDelegate _connectionDelegate;
+        private readonly TaskCompletionSource<object> _acceptLoopTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
 
         public ConnectionDispatcher(ServiceContext serviceContext, ConnectionDelegate connectionDelegate)
         {
@@ -28,26 +26,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
 
         private IKestrelTrace Log => _serviceContext.Log;
 
-        public Task OnConnection(TransportConnection connection)
+        public Task 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);
+            return _acceptLoopTcs.Task;
+        }
 
-            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;
+                        if (connection == null)
+                        {
+                            // We're done listening
+                            break;
+                        }
 
-            return Execute(new KestrelConnection(connection));
+                        _ = Execute(new KestrelConnection(connection, _serviceContext.Log));
+                    }
+                }
+                catch (Exception ex)
+                {
+                    // 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
+                {
+                    _acceptLoopTcs.TrySetResult(null);
+                }
+            }
         }
 
-        private async Task Execute(KestrelConnection connection)
+        internal async Task Execute(KestrelConnection connection)
         {
             var id = Interlocked.Increment(ref _lastConnectionId);
             var connectionContext = connection.TransportConnection;
@@ -69,25 +88,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
                     {
                         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);
                 }
             }
             finally
             {
-                await connectionContext.CompleteAsync();
+                await connection.FireOnCompletedAsync();
 
                 Log.ConnectionStop(connectionContext.ConnectionId);
                 KestrelEventSource.Log.ConnectionStop(connectionContext);
 
-                connection.Complete();
+                // 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);
             }
@@ -102,55 +115,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
 
             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<object>();
-            token.Register(state => ((TaskCompletionSource<object>)state).SetResult(null), tcs);
-            return tcs.Task;
-        }
-
-        // Internal for testing
-        internal static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool<byte> 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<byte> 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;
-        }
     }
 }

+ 1 - 2
src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs

@@ -9,7 +9,6 @@ using System.IO.Pipelines;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
                 pauseWriterThreshold: 1,
                 resumeWriterThreshold: 1,
                 useSynchronizationContext: false,
-                minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize
+                minimumSegmentSize: context.MemoryPool.GetMinimumSegmentSize()
             ));
     }
 }

+ 3 - 4
src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs

@@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Http.Features;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
             {
                 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
             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

+ 1 - 2
src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs

@@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Internal;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
                 pauseWriterThreshold: 1,
                 resumeWriterThreshold: 1,
                 useSynchronizationContext: false,
-                minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize
+                minimumSegmentSize: pool.GetMinimumSegmentSize()
             ));
     }
 }

+ 1 - 2
src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs

@@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Connections;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
                 pauseWriterThreshold: windowSize + 1,
                 resumeWriterThreshold: windowSize + 1,
                 useSynchronizationContext: false,
-                minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize
+                minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize()
             ));
 
         private (StreamCompletionFlags OldState, StreamCompletionFlags NewState) ApplyCompletionFlag(StreamCompletionFlags completionState)

+ 4 - 4
src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs

@@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
             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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
             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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
                     adaptedPipeline = new AdaptedPipeline(_adaptedTransport,
                                                           new Pipe(AdaptedInputPipeOptions),
                                                           new Pipe(AdaptedOutputPipeOptions),
-                                                          Log);
+                                                          Log,
+                                                          MemoryPool.GetMinimumAllocSize());
 
                     _adaptedTransport = adaptedPipeline;
                 }

+ 2 - 14
src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs

@@ -47,20 +47,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
                 Transport = connectionContext.Transport
             };
 
-            var connectionFeature = connectionContext.Features.Get<IHttpConnectionFeature>();
-
-            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);
 

+ 51 - 2
src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs

@@ -1,8 +1,12 @@
-// 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.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Connections;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
 {
@@ -37,10 +41,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
 
         public void RemoveConnection(long id)
         {
-            if (!_connectionReferences.TryRemove(id, out _))
+            if (!_connectionReferences.TryRemove(id, out var reference))
             {
                 throw new ArgumentException(nameof(id));
             }
+
+            if (reference.TryGetConnection(out var connection))
+            {
+                connection.Complete();
+            }
         }
 
         public void Walk(Action<KestrelConnection> callback)
@@ -64,6 +73,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
             }
         }
 
+        public async Task<bool> CloseAllConnectionsAsync(CancellationToken token)
+        {
+            var closeTasks = new List<Task>();
+
+            Walk(connection =>
+            {
+                connection.RequestClose();
+                closeTasks.Add(connection.ExecutionTask);
+            });
+
+            var allClosedTask = Task.WhenAll(closeTasks.ToArray());
+            return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask;
+        }
+
+        public async Task<bool> AbortAllConnectionsAsync()
+        {
+            var abortTasks = new List<Task>();
+
+            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<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+            token.Register(() => tcs.SetResult(null));
+            return tcs.Task;
+        }
+
         private static ResourceCounter GetCounter(long? number)
             => number.HasValue
                 ? ResourceCounter.Quota(number.Value)

+ 0 - 53
src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManagerShutdownExtensions.cs

@@ -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<bool> CloseAllConnectionsAsync(this ConnectionManager connectionManager, CancellationToken token)
-        {
-            var closeTasks = new List<Task>();
-
-            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<bool> AbortAllConnectionsAsync(this ConnectionManager connectionManager)
-        {
-            var abortTasks = new List<Task>();
-
-            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<object>(TaskCreationOptions.RunContinuationsAsynchronously);
-            token.Register(() => tcs.SetResult(null));
-            return tcs.Task;
-        }
-    }
-}

+ 2 - 2
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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
 
         private void WalkCallback(KestrelConnection connection)
         {
-            connection.TransportConnection.TickHeartbeat();
+            connection.TickHeartbeat();
         }
     }
 }

+ 154 - 9
src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs

@@ -1,25 +1,170 @@
 // 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.Threading;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
+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, IConnectionLifetimeNotificationFeature
     {
-        private TaskCompletionSource<object> _executionTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+        private List<(Action<object> handler, object state)> _heartbeatHandlers;
+        private readonly object _heartbeatLock = new object();
 
-        public KestrelConnection(TransportConnection transportConnection)
+        private Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
+        private bool _completed;
+
+        private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource();
+        private readonly TaskCompletionSource<object> _completionTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+        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<IConnectionHeartbeatFeature>(this);
+            connectionContext.Features.Set<IConnectionCompleteFeature>(this);
+            connectionContext.Features.Set<IConnectionLifetimeNotificationFeature>(this);
+            ConnectionClosedRequested = _connectionClosingCts.Token;
+        }
+
+        private ILogger Logger { get; }
+
+        public ConnectionContext TransportConnection { get; set; }
+        public CancellationToken ConnectionClosedRequested { get; set; }
+        public Task ExecutionTask => _completionTcs.Task;
+
+        public void TickHeartbeat()
+        {
+            lock (_heartbeatLock)
+            {
+                if (_heartbeatHandlers == null)
+                {
+                    return;
+                }
+
+                foreach (var (handler, state) in _heartbeatHandlers)
+                {
+                    handler(state);
+                }
+            }
+        }
+
+        public void OnHeartbeat(Action<object> action, object state)
         {
-            TransportConnection = transportConnection;
-            ExecutionTask = _executionTcs.Task;
+            lock (_heartbeatLock)
+            {
+                if (_heartbeatHandlers == null)
+                {
+                    _heartbeatHandlers = new List<(Action<object> handler, object state)>();
+                }
+
+                _heartbeatHandlers.Add((action, state));
+            }
         }
 
-        public TransportConnection TransportConnection { get; }
+        void IConnectionCompleteFeature.OnCompleted(Func<object, Task> callback, object state)
+        {
+            if (_completed)
+            {
+                throw new InvalidOperationException("The connection is already complete.");
+            }
+
+            if (_onCompleted == null)
+            {
+                _onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
+            }
+            _onCompleted.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
+        }
+
+        public Task FireOnCompletedAsync()
+        {
+            if (_completed)
+            {
+                throw new InvalidOperationException("The connection is already complete.");
+            }
 
-        public Task ExecutionTask { get; }
+            _completed = true;
+            var onCompleted = _onCompleted;
 
-        internal void Complete() => _executionTcs.TrySetResult(null);
+            if (onCompleted == null || onCompleted.Count == 0)
+            {
+                return Task.CompletedTask;
+            }
+
+            return CompleteAsyncMayAwait(onCompleted);
+        }
+
+        private Task CompleteAsyncMayAwait(Stack<KeyValuePair<Func<object, Task>, 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<KeyValuePair<Func<object, Task>, 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.");
+                }
+            }
+        }
+
+        public void RequestClose()
+        {
+            try
+            {
+                _connectionClosingCts.Cancel();
+            }
+            catch (ObjectDisposedException)
+            {
+                // There's a race where the token could be disposed
+                // swallow the exception and no-op
+            }
+        }
+
+        public void Complete()
+        {
+            _completionTcs.TrySetResult(null);
+
+            _connectionClosingCts.Dispose();
+        }
     }
 }

+ 5 - 6
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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
         // - 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
         }
 
         [NonEvent]
-        public void ConnectionStop(TransportConnection connection)
+        public void ConnectionStop(ConnectionContext connection)
         {
             if (IsEnabled())
             {

+ 31 - 0
src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
+{
+    internal static class MemoryPoolExtensions
+    {
+        /// <summary>
+        /// Computes a minimum segment size
+        /// </summary>
+        /// <param name="pool"></param>
+        /// <returns></returns>
+        public static int GetMinimumSegmentSize(this MemoryPool<byte> pool)
+        {
+            if (pool == null)
+            {
+                return 4096;
+            }
+
+            return Math.Min(4096, pool.MaxBufferSize);
+        }
+
+        public static int GetMinimumAllocSize(this MemoryPool<byte> pool)
+        {
+            // 1/2 of a segment
+            return pool.GetMinimumSegmentSize() / 2;
+        }
+    }
+}

+ 21 - 32
src/Servers/Kestrel/Core/src/KestrelServer.cs

@@ -6,13 +6,13 @@ using System.Collections.Generic;
 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;
 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;
 
@@ -20,23 +20,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
 {
     public class KestrelServer : IServer
     {
-        private readonly List<ITransport> _transports = new List<ITransport>();
+        private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>();
         private readonly IServerAddressesFeature _serverAddresses;
-        private readonly ITransportFactory _transportFactory;
+        private readonly IConnectionListenerFactory _transportFactory;
 
         private bool _hasStarted;
         private int _stopping;
         private readonly TaskCompletionSource<object> _stoppedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
 
-#pragma warning disable PUB0001 // Pubternal type in public API
-        public KestrelServer(IOptions<KestrelServerOptions> options, ITransportFactory transportFactory, ILoggerFactory loggerFactory)
-#pragma warning restore PUB0001
+        public KestrelServer(IOptions<KestrelServerOptions> 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)
             {
@@ -79,27 +77,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
                 DebuggerWrapper.Singleton,
                 trace);
 
-            // TODO: This logic will eventually move into the IConnectionHandler<T> 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<Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information)),
-                Scheduler = scheduler,
+                Scheduler = PipeScheduler.ThreadPool,
                 SystemClock = heartbeatManager,
                 DateHeaderValueManager = dateHeaderValueManager,
                 ConnectionManager = connectionManager,
@@ -138,12 +120,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
 
                 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)
@@ -152,10 +134,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
                     }
 
                     var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
-                    var transport = _transportFactory.Create(endpoint, connectionDispatcher);
-                    _transports.Add(transport);
+                    var transport = await _transportFactory.BindAsync(options.EndPoint).ConfigureAwait(false);
 
-                    await transport.BindAsync().ConfigureAwait(false);
+                    // Update the endpoint
+                    options.EndPoint = transport.EndPoint;
+                    var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport);
+
+                    _transports.Add((transport, acceptLoopTask));
                 }
 
                 await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
@@ -182,8 +167,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
                 var tasks = new Task[_transports.Count];
                 for (int i = 0; i < _transports.Count; i++)
                 {
-                    tasks[i] = _transports[i].UnbindAsync();
+                    (IConnectionListener listener, Task acceptLoop) = _transports[i];
+                    tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop);
                 }
+
                 await Task.WhenAll(tasks).ConfigureAwait(false);
 
                 if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false))
@@ -198,8 +185,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
 
                 for (int i = 0; i < _transports.Count; i++)
                 {
-                    tasks[i] = _transports[i].StopAsync();
+                    (IConnectionListener listener, Task acceptLoop) = _transports[i];
+                    tasks[i] = listener.DisposeAsync().AsTask();
                 }
+
                 await Task.WhenAll(tasks).ConfigureAwait(false);
 
                 ServiceContext.Heartbeat?.Dispose();

+ 0 - 9
src/Servers/Kestrel/Core/src/KestrelServerOptions.cs

@@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Certificates.Generation;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
         /// </remarks>
         public bool AddServerHeader { get; set; } = true;
 
-        /// <summary>
-        /// Gets or sets a value that determines how Kestrel should schedule user callbacks.
-        /// </summary>
-        /// <remarks>The default mode is <see cref="SchedulingMode.Default"/></remarks>
-#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
-
         /// <summary>
         /// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
         /// </summary>

+ 18 - 73
src/Servers/Kestrel/Core/src/ListenOptions.cs

@@ -1,15 +1,15 @@
-// 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.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;
 using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Core
 {
@@ -17,21 +17,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
     /// Describes either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor for an already open
     /// socket that Kestrel should bind to or open.
     /// </summary>
-    public class ListenOptions : IEndPointInformation, IConnectionBuilder
+    public class ListenOptions : IConnectionBuilder
     {
-        private FileHandleType _handleType;
         internal readonly List<Func<ConnectionDelegate, ConnectionDelegate>> _middleware = new List<Func<ConnectionDelegate, ConnectionDelegate>>();
 
         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)
@@ -41,73 +38,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
 
         internal ListenOptions(ulong fileHandle, FileHandleType handleType)
         {
-            Type = ListenType.FileHandle;
-            FileHandle = fileHandle;
-            switch (handleType)
-            {
-                case FileHandleType.Auto:
-                case FileHandleType.Tcp:
-                case FileHandleType.Pipe:
-                    _handleType = handleType;
-                    break;
-                default:
-                    throw new NotSupportedException();
-            }
+            EndPoint = new FileHandleEndPoint(fileHandle, handleType);
         }
 
-        /// <summary>
-        /// The type of interface being described: either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor.
-        /// </summary>
-#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
-#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));
-                }
-            }
-        }
+        public EndPoint EndPoint { get; internal set; }
 
         // IPEndPoint is mutable so port 0 can be updated to the bound port.
         /// <summary>
         /// The <see cref="IPEndPoint"/> to bind to.
-        /// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.IPEndPoint"/>.
+        /// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="IPEndPoint"/>.
         /// </summary>
-        public IPEndPoint IPEndPoint { get; set; }
+        public IPEndPoint IPEndPoint => EndPoint as IPEndPoint;
 
         /// <summary>
         /// The absolute path to a Unix domain socket to bind to.
-        /// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.SocketPath"/>.
+        /// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="UnixDomainSocketEndPoint"/>.
         /// </summary>
-        public string SocketPath { get; }
+        public string SocketPath => (EndPoint as UnixDomainSocketEndPoint)?.ToString();
 
         /// <summary>
         /// A file descriptor for the socket to open.
-        /// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.FileHandle"/>.
+        /// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="FileHandleEndPoint"/>.
         /// </summary>
-        public ulong FileHandle { get; }
+        public ulong FileHandle => (EndPoint as FileHandleEndPoint)?.FileHandle ?? 0;
 
         /// <summary>
         /// Enables an <see cref="IConnectionAdapter"/> to resolve and use services registered by the application during startup.
@@ -115,14 +68,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
         /// </summary>
         public KestrelServerOptions KestrelServerOptions { get; internal set; }
 
-        /// <summary>
-        /// Set to false to enable Nagle's algorithm for all connections.
-        /// </summary>
-        /// <remarks>
-        /// Defaults to true.
-        /// </remarks>
-        public bool NoDelay { get; set; } = true;
-
         /// <summary>
         /// The protocols enabled on this endpoint.
         /// </summary>
@@ -153,13 +98,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
                 ? "https"
                 : "http";
 
-            switch (Type)
+            switch (EndPoint)
             {
-                case ListenType.IPEndPoint:
+                case IPEndPoint _:
                     return $"{scheme}://{IPEndPoint}";
-                case ListenType.SocketPath:
-                    return $"{scheme}://unix:{SocketPath}";
-                case ListenType.FileHandle:
+                case UnixDomainSocketEndPoint _:
+                    return $"{scheme}://unix:{EndPoint}";
+                case FileHandleEndPoint _:
                     return $"{scheme}://<file handle>";
                 default:
                     throw new InvalidOperationException();

+ 1 - 3
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,9 +76,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
         {
             var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port))
             {
-                HandleType = HandleType,
                 KestrelServerOptions = KestrelServerOptions,
-                NoDelay = NoDelay,
                 Protocols = Protocols,
             };
 

+ 1 - 1
src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj

@@ -19,7 +19,7 @@
   <ItemGroup>
     <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
     <Reference Include="Microsoft.AspNetCore.Http" />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
     <Reference Include="Microsoft.AspNetCore.WebUtilities" />
     <Reference Include="Microsoft.Extensions.Configuration.Binder" />
     <Reference Include="Microsoft.Extensions.Logging.Abstractions" />

+ 8 - 10
src/Servers/Kestrel/Core/test/AddressBinderTests.cs

@@ -4,12 +4,13 @@
 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.AspNetCore.Testing.xunit;
 using Microsoft.Extensions.Logging.Abstractions;
 using Xunit;
 
@@ -53,10 +54,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
         [InlineData("contoso.com")]
         public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host)
         {
-            var options = new KestrelServerOptions();
             var listenOptions = AddressBinder.ParseAddress($"http://{host}", out var https);
             Assert.IsType<AnyIPListenOptions>(listenOptions);
-            Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
+            Assert.IsType<IPEndPoint>(listenOptions.EndPoint);
             Assert.Equal(IPAddress.IPv6Any, listenOptions.IPEndPoint.Address);
             Assert.Equal(80, listenOptions.IPEndPoint.Port);
             Assert.False(https);
@@ -65,21 +65,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
         [Fact]
         public void ParseAddressLocalhost()
         {
-            var options = new KestrelServerOptions();
             var listenOptions = AddressBinder.ParseAddress("http://localhost", out var https);
             Assert.IsType<LocalhostListenOptions>(listenOptions);
-            Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
+            Assert.IsType<IPEndPoint>(listenOptions.EndPoint);
             Assert.Equal(IPAddress.Loopback, listenOptions.IPEndPoint.Address);
             Assert.Equal(80, listenOptions.IPEndPoint.Port);
             Assert.False(https);
         }
 
-        [Fact]
+        [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()
         {
-            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<UnixDomainSocketEndPoint>(listenOptions.EndPoint);
             Assert.Equal("/tmp/kestrel-test.sock", listenOptions.SocketPath);
             Assert.False(https);
         }
@@ -92,9 +91,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
         [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.Equal(ListenType.IPEndPoint, listenOptions.Type);
+            Assert.IsType<IPEndPoint>(listenOptions.EndPoint);
             Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address);
             Assert.Equal(port, listenOptions.IPEndPoint.Port);
             Assert.Equal(isHttps, https);

+ 40 - 22
src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs

@@ -3,13 +3,14 @@
 
 using System;
 using System.Collections.Generic;
-using System.IO.Pipelines;
 using System.Linq;
+using System.Net;
 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.Transport.Abstractions.Internal;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
 using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.Logging;
 using Moq;
@@ -30,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             var connection = new Mock<TransportConnection> { CallBase = true }.Object;
             connection.ConnectionClosed = new CancellationToken(canceled: true);
 
-            dispatcher.OnConnection(connection);
+            _ = dispatcher.Execute(new KestrelConnection(connection, Mock.Of<ILogger>()));
 
             // The scope should be created
             var scopeObjects = ((TestKestrelTrace)serviceContext.Log)
@@ -51,25 +52,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
         }
 
         [Fact]
-        public async Task OnConnectionCompletesTransportPipesAfterReturning()
+        public async Task StartAcceptingConnectionsAsyncLogsIfAcceptAsyncThrows()
         {
             var serviceContext = new TestServiceContext();
-            var dispatcher = new ConnectionDispatcher(serviceContext, _ => Task.CompletedTask);
+            var logger = ((TestKestrelTrace)serviceContext.Log).Logger;
+            logger.ThrowOnCriticalErrors = false;
 
-            var mockConnection = new Mock<TransportConnection> { CallBase = true };
-            mockConnection.Object.ConnectionClosed = new CancellationToken(canceled: true);
-            var mockPipeReader = new Mock<PipeReader>();
-            var mockPipeWriter = new Mock<PipeWriter>();
-            var mockPipe = new Mock<IDuplexPipe>();
-            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;
+            var dispatcher = new ConnectionDispatcher(serviceContext, _ => Task.CompletedTask);
 
-            await dispatcher.OnConnection(connection);
+            await dispatcher.StartAcceptingConnections(new ThrowingListener());
 
-            mockPipeWriter.Verify(m => m.Complete(It.IsAny<Exception>()), Times.Once());
-            mockPipeReader.Verify(m => m.Complete(It.IsAny<Exception>()), Times.Once());
+            Assert.Equal(1, logger.CriticalErrorsLogged);
+            var critical = logger.Messages.SingleOrDefault(m => m.LogLevel == LogLevel.Critical);
+            Assert.NotNull(critical);
+            Assert.IsType<InvalidOperationException>(critical.Exception);
+            Assert.Equal("Unexpected error listening", critical.Exception.Message);
         }
 
         [Fact]
@@ -80,14 +77,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
             var connection = new Mock<TransportConnection> { CallBase = true }.Object;
             connection.ConnectionClosed = new CancellationToken(canceled: true);
-            var completeFeature = connection.Features.Get<IConnectionCompleteFeature>();
+            var kestrelConnection = new KestrelConnection(connection, Mock.Of<ILogger>());
+            var completeFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionCompleteFeature>();
 
             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,21 +98,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
             var connection = new Mock<TransportConnection> { CallBase = true }.Object;
             connection.ConnectionClosed = new CancellationToken(canceled: true);
-            var completeFeature = connection.Features.Get<IConnectionCompleteFeature>();
             var mockLogger = new Mock<ILogger>();
-            connection.Logger = mockLogger.Object;
+            var kestrelConnection = new KestrelConnection(connection, mockLogger.Object);
+            var completeFeature = kestrelConnection.TransportConnection.Features.Get<IConnectionCompleteFeature>();
 
             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();
             Assert.Equal("An error occured running an IConnectionCompleteFeature.OnCompleted callback.", log.Arguments[2].ToString());
             Assert.IsType<InvalidTimeZoneException>(log.Arguments[3]);
         }
+
+        private class ThrowingListener : IConnectionListener
+        {
+            public EndPoint EndPoint { get; set; }
+
+            public ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
+            {
+                throw new InvalidOperationException("Unexpected error listening");
+            }
+
+            public ValueTask DisposeAsync()
+            {
+                return default;
+            }
+
+            public ValueTask UnbindAsync(CancellationToken cancellationToken = default)
+            {
+                return default;
+            }
+        }
     }
 }

+ 1 - 2
src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs

@@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
         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);
 

+ 4 - 3
src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs

@@ -3,8 +3,9 @@
 
 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;
 
@@ -38,9 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             ConnectionManager httpConnectionManager,
             Mock<IKestrelTrace> trace)
         {
-            var mock = new Mock<TransportConnection>();
+            var mock = new Mock<TransportConnection>() { CallBase = true };
             mock.Setup(m => m.ConnectionId).Returns(connectionId);
-            var httpConnection = new KestrelConnection(mock.Object);
+            var httpConnection = new KestrelConnection(mock.Object, Mock.Of<ILogger>());
 
             httpConnectionManager.AddConnection(0, httpConnection);
 

+ 2 - 2
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;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
         [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);

+ 9 - 24
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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             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);
         }
     }
 }

+ 70 - 60
src/Servers/Kestrel/Core/test/KestrelServerTests.cs

@@ -7,11 +7,11 @@ using System.Net;
 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;
 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;
@@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             var mockLoggerFactory = new Mock<ILoggerFactory>();
             var mockLogger = new Mock<ILogger>();
             mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny<string>())).Returns(mockLogger.Object);
-            new KestrelServer(Options.Create<KestrelServerOptions>(null), Mock.Of<ITransportFactory>(), mockLoggerFactory.Object);
+            new KestrelServer(Options.Create<KestrelServerOptions>(null), Mock.Of<IConnectionListenerFactory>(), mockLoggerFactory.Object);
             mockLoggerFactory.Verify(factory => factory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"));
         }
 
@@ -233,21 +233,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             var unbind = new SemaphoreSlim(0);
             var stop = new SemaphoreSlim(0);
 
-            var mockTransport = new Mock<ITransport>();
-            mockTransport
-                .Setup(transport => transport.BindAsync())
-                .Returns(Task.CompletedTask);
-            mockTransport
-                .Setup(transport => transport.UnbindAsync())
-                .Returns(async () => await unbind.WaitAsync());
-            mockTransport
-                .Setup(transport => transport.StopAsync())
-                .Returns(async () => await stop.WaitAsync());
-
-            var mockTransportFactory = new Mock<ITransportFactory>();
+            var mockTransport = new Mock<IConnectionListener>();
+            var mockTransportFactory = new Mock<IConnectionListenerFactory>();
             mockTransportFactory
-                .Setup(transportFactory => transportFactory.Create(It.IsAny<IEndPointInformation>(), It.IsAny<IConnectionDispatcher>()))
-                .Returns(mockTransport.Object);
+                .Setup(transportFactory => transportFactory.BindAsync(It.IsAny<EndPoint>(), It.IsAny<CancellationToken>()))
+                .Returns<EndPoint, CancellationToken>((e, token) =>
+                {
+                    mockTransport
+                        .Setup(transport => transport.AcceptAsync(It.IsAny<CancellationToken>()))
+                        .Returns(new ValueTask<ConnectionContext>((ConnectionContext)null));
+                    mockTransport
+                        .Setup(transport => transport.UnbindAsync(It.IsAny<CancellationToken>()))
+                        .Returns(() => new ValueTask(unbind.WaitAsync()));
+                    mockTransport
+                        .Setup(transport => transport.DisposeAsync())
+                        .Returns(() => new ValueTask(stop.WaitAsync()));
+                    mockTransport
+                        .Setup(transport => transport.EndPoint).Returns(e);
+
+                    return new ValueTask<IConnectionListener>(mockTransport.Object);
+                });
 
             var mockLoggerFactory = new Mock<ILoggerFactory>();
             var mockLogger = new Mock<ILogger>();
@@ -255,9 +260,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             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,8 +273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
             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.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once);
         }
 
         [Fact]
@@ -286,25 +290,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             var unbind = new SemaphoreSlim(0);
             var unbindException = new InvalidOperationException();
 
-            var mockTransport = new Mock<ITransport>();
-            mockTransport
-                .Setup(transport => transport.BindAsync())
-                .Returns(Task.CompletedTask);
-            mockTransport
-                .Setup(transport => transport.UnbindAsync())
-                .Returns(async () =>
+            var mockTransport = new Mock<IConnectionListener>();
+            var mockTransportFactory = new Mock<IConnectionListenerFactory>();
+            mockTransportFactory
+                .Setup(transportFactory => transportFactory.BindAsync(It.IsAny<EndPoint>(), It.IsAny<CancellationToken>()))
+                .Returns<EndPoint, CancellationToken>((e, token) =>
                 {
-                    await unbind.WaitAsync();
-                    throw unbindException;
+                    mockTransport
+                        .Setup(transport => transport.AcceptAsync(It.IsAny<CancellationToken>()))
+                        .Returns(new ValueTask<ConnectionContext>((ConnectionContext)null));
+                    mockTransport
+                        .Setup(transport => transport.UnbindAsync(It.IsAny<CancellationToken>()))
+                        .Returns(async () =>
+                        {
+                            await unbind.WaitAsync();
+                            throw unbindException;
+                        });
+                    mockTransport
+                        .Setup(transport => transport.EndPoint).Returns(e);
+
+                    return new ValueTask<IConnectionListener>(mockTransport.Object);
                 });
-            mockTransport
-                .Setup(transport => transport.StopAsync())
-                .Returns(Task.CompletedTask);
-
-            var mockTransportFactory = new Mock<ITransportFactory>();
-            mockTransportFactory
-                .Setup(transportFactory => transportFactory.Create(It.IsAny<IEndPointInformation>(), It.IsAny<IConnectionDispatcher>()))
-                .Returns(mockTransport.Object);
 
             var mockLoggerFactory = new Mock<ILoggerFactory>();
             var mockLogger = new Mock<ILogger>();
@@ -312,9 +318,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             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);
@@ -327,7 +333,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             Assert.Same(unbindException, await Assert.ThrowsAsync<InvalidOperationException>(() => stopTask2.TimeoutAfter(timeout)));
             Assert.Same(unbindException, await Assert.ThrowsAsync<InvalidOperationException>(() => stopTask3.TimeoutAfter(timeout)));
 
-            mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once);
+            mockTransport.Verify(transport => transport.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once);
         }
 
         [Fact]
@@ -343,21 +349,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
             var unbindTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
 
-            var mockTransport = new Mock<ITransport>();
-            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<ITransportFactory>();
+            var mockTransport = new Mock<IConnectionListener>();
+            var mockTransportFactory = new Mock<IConnectionListenerFactory>();
             mockTransportFactory
-                .Setup(transportFactory => transportFactory.Create(It.IsAny<IEndPointInformation>(), It.IsAny<IConnectionDispatcher>()))
-                .Returns(mockTransport.Object);
+                .Setup(transportFactory => transportFactory.BindAsync(It.IsAny<EndPoint>(), It.IsAny<CancellationToken>()))
+                .Returns<EndPoint, CancellationToken>((e, token) =>
+                {
+                    mockTransport
+                        .Setup(transport => transport.AcceptAsync(It.IsAny<CancellationToken>()))
+                        .Returns(new ValueTask<ConnectionContext>((ConnectionContext)null));
+                    mockTransport
+                        .Setup(transport => transport.UnbindAsync(It.IsAny<CancellationToken>()))
+                        .Returns(new ValueTask(unbindTcs.Task));
+                    mockTransport
+                        .Setup(transport => transport.EndPoint).Returns(e);
+
+                    return new ValueTask<IConnectionListener>(mockTransport.Object);
+                });
 
             var mockLoggerFactory = new Mock<ILoggerFactory>();
             var mockLogger = new Mock<ILogger>();
@@ -384,7 +392,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             await stopTask2.DefaultTimeout();
             await continuationTask.DefaultTimeout();
 
-            mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once);
+            mockTransport.Verify(transport => transport.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once);
         }
 
         [Fact]
@@ -438,11 +446,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             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<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
             {
-                return Mock.Of<ITransport>();
+                var mock = new Mock<IConnectionListener>();
+                mock.Setup(m => m.EndPoint).Returns(endpoint);
+                return new ValueTask<IConnectionListener>(mock.Object);
             }
         }
     }

+ 2 - 2
src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj

@@ -12,12 +12,12 @@
     <Compile Include="$(KestrelSharedSourceRoot)test\*.cs" LinkBase="shared" />
     <Compile Include="$(KestrelSharedSourceRoot)KnownHeaders.cs" LinkBase="shared" />
     <Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
+    <Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
   </ItemGroup>
-
+  
   <ItemGroup>
     <Reference Include="Microsoft.AspNetCore.Http" />
     <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Core" />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" />
     <Reference Include="Microsoft.Extensions.DependencyInjection" />
     <Reference Include="Microsoft.Extensions.Logging" />
   </ItemGroup>

+ 1 - 2
src/Servers/Kestrel/Core/test/OutputProducerTests.cs

@@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Connections;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
         public OutputProducerTests()
         {
-            _memoryPool = KestrelMemoryPool.Create();
+            _memoryPool = MemoryPoolFactory.Create();
         }
 
         public void Dispose()

+ 1 - 39
src/Servers/Kestrel/Core/test/PipeOptionsTests.cs

@@ -1,9 +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.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;
@@ -12,43 +11,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<PipeScheduler>();
-            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<PipeScheduler>();
-            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)]

+ 1 - 2
src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs

@@ -6,7 +6,6 @@ using System.Buffers;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
         private const int _ulongMaxValueLength = 20;
 
         private readonly Pipe _pipe;
-        private readonly MemoryPool<byte> _memoryPool = KestrelMemoryPool.Create();
+        private readonly MemoryPool<byte> _memoryPool = MemoryPoolFactory.Create();
 
         public PipelineExtensionTests()
         {

+ 1 - 2
src/Servers/Kestrel/Core/test/StartLineTests.cs

@@ -10,7 +10,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.Core.Internal.Infrastructure;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 using Moq;
 using Xunit;
 
@@ -500,7 +499,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
 
         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;

+ 1 - 2
src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs

@@ -12,7 +12,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.Core.Internal.Infrastructure;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 using Microsoft.AspNetCore.Testing;
 using Moq;
 
@@ -24,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
 
         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;

+ 2 - 2
src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs

@@ -2,10 +2,10 @@
 // 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;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Hosting
             return hostBuilder.ConfigureServices(services =>
             {
                 // Don't override an already-configured transport
-                services.TryAddSingleton<ITransportFactory, SocketTransportFactory>();
+                services.TryAddSingleton<IConnectionListenerFactory, SocketTransportFactory>();
 
                 services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
                 services.AddSingleton<IServer, KestrelServer>();

+ 0 - 6
src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs

@@ -133,7 +133,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
 
             serverOptions.ConfigureEndpointDefaults(opt =>
             {
-                opt.NoDelay = false;
                 opt.Protocols = HttpProtocols.Http2;
             });
 
@@ -156,13 +155,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
                     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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
 
             serverOptions.ConfigureEndpointDefaults(opt =>
             {
-                opt.NoDelay = false;
                 opt.UseHttps(TestResources.GetTestCertificate());
             });
 
@@ -202,12 +198,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
                     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();
 

+ 2 - 3
src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>netcoreapp3.0</TargetFramework>
@@ -12,7 +12,7 @@
     <Content Include="$(KestrelRoot)Core\src\Internal\Http\HttpHeaders.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
     <Content Include="$(KestrelRoot)Core\src\Internal\Http\HttpProtocol.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
     <Content Include="$(KestrelRoot)Core\src\Internal\Infrastructure\HttpUtilities.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
-    <Content Include="$(KestrelRoot)Transport.Abstractions\src\Internal\TransportConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
+    <Content Include="$(KestrelRoot)..\Connections.Abstractions\src\TransportConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
@@ -20,7 +20,6 @@
   </ItemGroup>
 
   <ItemGroup>
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" />
     <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" />
     <Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
   </ItemGroup>

+ 6 - 6
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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
                 .UseKestrel()
                 .Configure(app => { });
 
-            Assert.IsType<SocketTransportFactory>(hostBuilder.Build().Services.GetService<ITransportFactory>());
+            Assert.IsType<SocketTransportFactory>(hostBuilder.Build().Services.GetService<IConnectionListenerFactory>());
         }
 
         [Fact]
@@ -68,14 +68,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
                 .UseLibuv()
                 .Configure(app => { });
 
-            Assert.IsType<LibuvTransportFactory>(hostBuilder.Build().Services.GetService<ITransportFactory>());
+            Assert.IsType<LibuvTransportFactory>(hostBuilder.Build().Services.GetService<IConnectionListenerFactory>());
 
             var hostBuilderReversed = new WebHostBuilder()
                 .UseLibuv()
                 .UseKestrel()
                 .Configure(app => { });
 
-            Assert.IsType<LibuvTransportFactory>(hostBuilderReversed.Build().Services.GetService<ITransportFactory>());
+            Assert.IsType<LibuvTransportFactory>(hostBuilderReversed.Build().Services.GetService<IConnectionListenerFactory>());
         }
 
         [Fact]
@@ -86,14 +86,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
                 .UseSockets()
                 .Configure(app => { });
 
-            Assert.IsType<SocketTransportFactory>(hostBuilder.Build().Services.GetService<ITransportFactory>());
+            Assert.IsType<SocketTransportFactory>(hostBuilder.Build().Services.GetService<IConnectionListenerFactory>());
 
             var hostBuilderReversed = new WebHostBuilder()
                 .UseSockets()
                 .UseKestrel()
                 .Configure(app => { });
 
-            Assert.IsType<SocketTransportFactory>(hostBuilderReversed.Build().Services.GetService<ITransportFactory>());
+            Assert.IsType<SocketTransportFactory>(hostBuilderReversed.Build().Services.GetService<IConnectionListenerFactory>());
         }
     }
 }

+ 0 - 110
src/Servers/Kestrel/Transport.Abstractions/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.netcoreapp3.0.cs

@@ -1,113 +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 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; }
-    }
-    public static partial class KestrelMemoryPool
-    {
-        public static readonly int MinimumSegmentSize;
-        public static System.Buffers.MemoryPool<byte> Create() { throw null; }
-        public static System.Buffers.MemoryPool<byte> 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.Generic.KeyValuePair<System.Type, object>>, 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 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<object, object> 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 virtual System.Buffers.MemoryPool<byte> MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
-        System.Collections.Generic.IDictionary<object, object> 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<byte> 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.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<object, System.Threading.Tasks.Task> callback, object state) { }
-        void Microsoft.AspNetCore.Connections.Features.IConnectionHeartbeatFeature.OnHeartbeat(System.Action<object> action, object state) { }
-        void Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeFeature.Abort() { }
-        void Microsoft.AspNetCore.Connections.Features.IConnectionLifetimeNotificationFeature.RequestClose() { }
-        TFeature Microsoft.AspNetCore.Http.Features.IFeatureCollection.Get<TFeature>() { throw null; }
-        void Microsoft.AspNetCore.Http.Features.IFeatureCollection.Set<TFeature>(TFeature feature) { }
-        public void OnHeartbeat(System.Action<object> action, object state) { }
-        public void RequestClose() { }
-        System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.Type, object>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type,System.Object>>.GetEnumerator() { throw null; }
-        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
-        public void TickHeartbeat() { }
-    }
-}

+ 0 - 12
src/Servers/Kestrel/Transport.Abstractions/src/Internal/IApplicationTransportFeature.cs

@@ -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; }
-    }
-}

+ 0 - 12
src/Servers/Kestrel/Transport.Abstractions/src/Internal/IConnectionDispatcher.cs

@@ -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);
-    }
-}

+ 0 - 46
src/Servers/Kestrel/Transport.Abstractions/src/Internal/IEndPointInformation.cs

@@ -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
-    {
-        /// <summary>
-        /// The type of interface being described: either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor.
-        /// </summary>
-        ListenType Type { get; }
-
-        // IPEndPoint is mutable so port 0 can be updated to the bound port.
-        /// <summary>
-        /// The <see cref="IPEndPoint"/> to bind to.
-        /// Only set if <see cref="Type"/> is <see cref="ListenType.IPEndPoint"/>.
-        /// </summary>
-        IPEndPoint IPEndPoint { get; set; }
-
-        /// <summary>
-        /// The absolute path to a Unix domain socket to bind to.
-        /// Only set if <see cref="Type"/> is <see cref="ListenType.SocketPath"/>.
-        /// </summary>
-        string SocketPath { get; }
-
-        /// <summary>
-        /// A file descriptor for the socket to open.
-        /// Only set if <see cref="Type"/> is <see cref="ListenType.FileHandle"/>.
-        /// </summary>
-        ulong FileHandle { get; }
-
-        //  HandleType is mutable so it can be re-specified later.
-        /// <summary>
-        /// The type of file descriptor being used.
-        /// Only set if <see cref="Type"/> is <see cref="ListenType.FileHandle"/>.
-        /// </summary>
-        FileHandleType HandleType { get; set; }
-
-        /// <summary>
-        /// Set to false to enable Nagle's algorithm for all connections.
-        /// </summary>
-        bool NoDelay { get; }
-    }
-}

+ 0 - 15
src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransport.cs

@@ -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();
-    }
-}

+ 0 - 10
src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportFactory.cs

@@ -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);
-    }
-}

+ 0 - 14
src/Servers/Kestrel/Transport.Abstractions/src/Internal/ITransportSchedulerFeature.cs

@@ -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; }
-    }
-}

+ 0 - 15
src/Servers/Kestrel/Transport.Abstractions/src/Internal/ListenType.cs

@@ -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
-{
-    /// <summary>
-    /// Enumerates the <see cref="IEndPointInformation"/> types.
-    /// </summary>
-    public enum ListenType
-    {
-        IPEndPoint,
-        SocketPath,
-        FileHandle
-    }
-}

+ 0 - 12
src/Servers/Kestrel/Transport.Abstractions/src/Internal/SchedulingMode.cs

@@ -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
-    }
-}

+ 0 - 188
src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.FeatureCollection.cs

@@ -1,188 +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.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,
-                                               IConnectionTransportFeature,
-                                               IConnectionItemsFeature,
-                                               IMemoryPoolFeature,
-                                               IApplicationTransportFeature,
-                                               ITransportSchedulerFeature,
-                                               IConnectionLifetimeFeature,
-                                               IConnectionHeartbeatFeature,
-                                               IConnectionLifetimeNotificationFeature,
-                                               IConnectionCompleteFeature
-    {
-        // 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<KeyValuePair<Func<object, Task>, object>> _onCompleted;
-        private bool _completed;
-
-        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<byte> IMemoryPoolFeature.MemoryPool => MemoryPool;
-
-        IDuplexPipe IConnectionTransportFeature.Transport
-        {
-            get => Transport;
-            set => Transport = value;
-        }
-
-        IDuplexPipe IApplicationTransportFeature.Application
-        {
-            get => Application;
-            set => Application = value;
-        }
-
-        IDictionary<object, object> IConnectionItemsFeature.Items
-        {
-            get => Items;
-            set => Items = value;
-        }
-
-        PipeScheduler ITransportSchedulerFeature.InputWriterScheduler => InputWriterScheduler;
-        PipeScheduler ITransportSchedulerFeature.OutputReaderScheduler => OutputReaderScheduler;
-
-        CancellationToken IConnectionLifetimeFeature.ConnectionClosed
-        {
-            get => 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();
-
-        void IConnectionHeartbeatFeature.OnHeartbeat(System.Action<object> action, object state)
-        {
-            OnHeartbeat(action, state);
-        }
-
-        void IConnectionCompleteFeature.OnCompleted(Func<object, Task> callback, object state)
-        {
-            if (_completed)
-            {
-                throw new InvalidOperationException("The connection is already complete.");
-            }
-
-            if (_onCompleted == null)
-            {
-                _onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
-            }
-            _onCompleted.Push(new KeyValuePair<Func<object, Task>, 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<KeyValuePair<Func<object, Task>, 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<KeyValuePair<Func<object, Task>, 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.");
-                }
-            }
-        }
-    }
-}

+ 0 - 121
src/Servers/Kestrel/Transport.Abstractions/src/Internal/TransportConnection.cs

@@ -1,121 +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.Collections.Generic;
-using System.IO.Pipelines;
-using System.Net;
-using System.Threading;
-using Microsoft.AspNetCore.Connections;
-using Microsoft.AspNetCore.Http.Features;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
-{
-    public abstract partial class TransportConnection : ConnectionContext
-    {
-        private IDictionary<object, object> _items;
-        private List<(Action<object> handler, object state)> _heartbeatHandlers;
-        private readonly object _heartbeatLock = new object();
-        protected readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource();
-
-        public TransportConnection()
-        {
-            FastReset();
-
-            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 string ConnectionId { get; set; }
-
-        public override IFeatureCollection Features => this;
-
-        protected internal virtual ILogger Logger { get; set; }
-
-        public virtual MemoryPool<byte> MemoryPool { get; }
-        public virtual PipeScheduler InputWriterScheduler { get; }
-        public virtual PipeScheduler OutputReaderScheduler { get; }
-
-        public override IDuplexPipe Transport { get; set; }
-        public IDuplexPipe Application { get; set; }
-
-        public override IDictionary<object, object> Items
-        {
-            get
-            {
-                // Lazily allocate connection metadata
-                return _items ?? (_items = new ConnectionItems());
-            }
-            set
-            {
-                _items = value;
-            }
-        }
-
-        public PipeWriter Input => Application.Output;
-        public PipeReader Output => Application.Input;
-
-        public CancellationToken ConnectionClosed { get; set; }
-
-        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<object> action, object state)
-        {
-            lock (_heartbeatLock)
-            {
-                if (_heartbeatHandlers == null)
-                {
-                    _heartbeatHandlers = new List<(Action<object> 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.
-        // That said, all derived types should override this method should override
-        // this implementation of Abort because canceling pending output reads is not
-        // sufficient to abort the connection if there is backpressure.
-        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
-            }
-        }
-    }
-}

+ 1 - 1
src/Servers/Kestrel/Transport.Abstractions/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <Description>Transport abstractions for the ASP.NET Core Kestrel cross-platform web server.</Description>

+ 2 - 1
src/Servers/Kestrel/Transport.Libuv/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj

@@ -7,8 +7,9 @@
     <Compile Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs" />
     <Reference Include="Libuv"  />
     <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions"  />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions"  />
+    <Reference Include="Microsoft.AspNetCore.Connections.Abstractions"  />
     <Reference Include="Microsoft.Extensions.Logging.Abstractions"  />
     <Reference Include="Microsoft.Extensions.Options"  />
+    <Reference Include="System.Threading.Channels"  />
   </ItemGroup>
 </Project>

+ 3 - 0
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 { } }
     }
 }

+ 64 - 20
src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs

@@ -9,15 +9,14 @@ using System.Net;
 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;
 
 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;
+        private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2;
 
         private static readonly Action<UvStreamHandle, int, object> _readCallback =
             (handle, status, state) => ReadCallback(handle, status, state);
@@ -31,31 +30,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         private volatile ConnectionAbortedException _abortReason;
 
         private MemoryHandle _bufferHandle;
-
-        public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread thread, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint)
+        private Task _processingTask;
+
+        public LibuvConnection(UvStreamHandle socket,
+                               ILibuvTrace log,
+                               LibuvThread thread,
+                               IPEndPoint remoteEndPoint,
+                               IPEndPoint localEndPoint,
+                               PipeOptions inputOptions = null,
+                               PipeOptions outputOptions = null,
+                               long? maxReadBufferSize = null,
+                               long? maxWriteBufferSize = null)
         {
             _socket = socket;
 
-            RemoteAddress = remoteEndPoint?.Address;
-            RemotePort = remoteEndPoint?.Port ?? 0;
-
-            LocalAddress = localEndPoint?.Address;
-            LocalPort = localEndPoint?.Port ?? 0;
+            LocalEndPoint = localEndPoint;
+            RemoteEndPoint = remoteEndPoint;
 
             ConnectionClosed = _connectionClosedTokenSource.Token;
-            Logger = log;
             Log = log;
             Thread = thread;
+
+            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);
+
+            // Set the transport and connection id
+            Transport = pair.Transport;
+            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; }
         public override MemoryPool<byte> MemoryPool => Thread.MemoryPool;
-        public override PipeScheduler InputWriterScheduler => Thread;
-        public override PipeScheduler OutputReaderScheduler => Thread;
 
-        public async Task Start()
+        public void Start()
+        {
+            _processingTask = StartCore();
+        }
+
+        private async Task StartCore()
         {
             try
             {
@@ -96,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                 }
                 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);
@@ -111,7 +134,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
                     // 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<object>(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)
@@ -123,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         public override void Abort(ConnectionAbortedException abortReason)
         {
             _abortReason = abortReason;
-            
+
             // Cancel WriteOutputAsync loop after setting _abortReason.
             Output.CancelPendingRead();
 
@@ -131,11 +169,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             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 async ValueTask DisposeAsync()
         {
+            Transport.Input.Complete();
+            Transport.Output.Complete();
+
+            if (_processingTask != null)
+            {
+                await _processingTask;
+            }
+
             _connectionClosedTokenSource.Dispose();
-            _connectionClosingCts.Dispose();
         }
 
         // Called on Libuv thread

+ 112 - 21
src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransport.cs → src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs

@@ -4,32 +4,34 @@
 using System;
 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.Abstractions.Internal;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
-using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 {
-    internal class LibuvTransport : ITransport
+    internal class LibuvConnectionListener : IConnectionListener
     {
-        private readonly IEndPointInformation _endPointInformation;
+        private readonly List<ListenerContext> _listeners = new List<ListenerContext>();
+        private IAsyncEnumerator<LibuvConnection> _acceptEnumerator;
+        private bool _stopped;
+        private bool _disposed;
 
-        private readonly List<IAsyncDisposable> _listeners = new List<IAsyncDisposable>();
-
-        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; }
@@ -40,7 +42,64 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         public ILibuvTrace Log => TransportContext.Log;
         public LibuvTransportOptions TransportOptions => TransportContext.Options;
 
-        public async Task StopAsync()
+        public EndPoint EndPoint { get; set; }
+
+        public async ValueTask<ConnectionContext> 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 UnbindAsync(CancellationToken cancellationToken = default)
+        {
+            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)
+            {
+                return;
+            }
+
+            _disposed = true;
+
+            await UnbindAsync().ConfigureAwait(false);
+
+            foreach (var listener in _listeners)
+            {
+                await listener.AbortQueuedConnectionAsync().ConfigureAwait(false);
+            }
+
+            _listeners.Clear();
+
+            await StopThreadsAsync().ConfigureAwait(false);
+        }
+
+        internal async Task StopThreadsAsync()
         {
             try
             {
@@ -68,13 +127,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 #endif
         }
 
-        public async Task BindAsync()
+        internal async Task BindAsync()
         {
             // TODO: Move thread management to LibuvTransportFactory
             // 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)
@@ -88,7 +147,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                 {
                     var listener = new Listener(TransportContext);
                     _listeners.Add(listener);
-                    await listener.StartAsync(_endPointInformation, Threads[0]).ConfigureAwait(false);
+                    await listener.StartAsync(EndPoint, Threads[0]).ConfigureAwait(false);
+                    EndPoint = listener.EndPoint;
                 }
                 else
                 {
@@ -97,15 +157,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
                     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);
+                    EndPoint = listenerPrimary.EndPoint;
 
                     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,16 +181,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             }
         }
 
-        public async Task UnbindAsync()
+        private async IAsyncEnumerator<LibuvConnection> AcceptConnections()
         {
-            var disposeTasks = _listeners.Select(listener => listener.DisposeAsync()).ToArray();
+            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;
 
-            if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false))
+            var remainingSlots = slots.Length;
+
+            // Issue parallel accepts on all listeners
+            for (int i = 0; i < remainingSlots; i++)
             {
-                Log.LogError(0, null, "Disposing listeners failed");
+                slots[i] = AcceptAsync(_listeners[i], i);
             }
 
-            _listeners.Clear();
+            while (remainingSlots > 0)
+            {
+                // Calling GetAwaiter().GetResult() is safe because we know the task is completed
+                (var connection, var slot) = (await Task.WhenAny(slots)).GetAwaiter().GetResult();
+
+                // 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;
+                }
+            }
+
+            static async Task<(LibuvConnection, int)> AcceptAsync(ListenerContext listener, int slot)
+            {
+                return (await listener.AcceptAsync(), slot);
+            }
         }
 
         private static async Task<bool> WaitAsync(Task task, TimeSpan timeout)

+ 20 - 17
src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvThread.cs

@@ -20,9 +20,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         // 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 LibuvTransport _transport;
+        private readonly LibuvFunctions _libuv;
         private readonly IHostApplicationLifetime _appLifetime;
         private readonly Thread _thread;
         private readonly TaskCompletionSource<object> _threadTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -40,13 +40,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         private Exception _closeError;
         private readonly ILibuvTrace _log;
 
-        public LibuvThread(LibuvTransport 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<byte> 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 #endif
             QueueCloseHandle = PostCloseHandle;
             QueueCloseAsyncHandle = EnqueueCloseHandle;
-            MemoryPool = transport.TransportOptions.MemoryPoolFactory();
+            MemoryPool = pool;
             WriteReqPool = new WriteReqPool(this, _log);
         }
 
-        // For testing
-        public LibuvThread(LibuvTransport transport, int maxLoops)
-            : this(transport)
-        {
-            _maxLoops = maxLoops;
-        }
-
         public UvLoopHandle Loop { get { return _loop; } }
 
         public MemoryPool<byte> MemoryPool { get; }
@@ -113,13 +112,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                 Post(t => t.AllowStop());
                 if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false))
                 {
+                    _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.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)}.");
                         }
                     }
                 }
@@ -253,7 +256,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
         private void Walk(LibuvFunctions.uv_walk_cb callback, IntPtr arg)
         {
-            _transport.Libuv.walk(
+            _libuv.walk(
                 _loop,
                 callback,
                 arg
@@ -282,7 +285,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                 var tcs = (TaskCompletionSource<int>)parameter;
                 try
                 {
-                    _loop.Init(_transport.Libuv);
+                    _loop.Init(_libuv);
                     _post.Init(_loop, OnPost, EnqueueCloseHandle);
                     _initCompleted = true;
                     tcs.SetResult(0);

+ 0 - 3
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
@@ -13,7 +12,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         public IHostApplicationLifetime AppLifetime { get; set; }
 
         public ILibuvTrace Log { get; set; }
-
-        public IConnectionDispatcher ConnectionDispatcher { get; set; }
     }
 }

+ 11 - 7
src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs

@@ -2,14 +2,17 @@
 // 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.Options;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 {
-    internal class LibuvTransportFactory : ITransportFactory
+    internal class LibuvTransportFactory : IConnectionListenerFactory
     {
         private readonly LibuvTransportContext _baseTransportContext;
 
@@ -31,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                 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 +64,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             };
         }
 
-        public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher dispatcher)
+        public async ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
         {
             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;
         }
     }
 }

+ 25 - 19
src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs

@@ -2,8 +2,10 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
+using System.Net;
+using System.Net.Sockets;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
+using Microsoft.AspNetCore.Connections;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
 using Microsoft.Extensions.Logging;
 
@@ -14,6 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
     /// </summary>
     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)
@@ -25,10 +28,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         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,13 +46,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         /// </summary>
         private UvStreamHandle CreateListenSocket()
         {
-            switch (EndPointInformation.Type)
+            switch (EndPoint)
             {
-                case ListenType.IPEndPoint:
+                case IPEndPoint _:
                     return ListenTcp(useFileHandle: false);
-                case ListenType.SocketPath:
+                case UnixDomainSocketEndPoint _:
                     return ListenPipe(useFileHandle: false);
-                case ListenType.FileHandle:
+                case FileHandleEndPoint _:
                     return ListenHandle();
                 default:
                     throw new NotSupportedException();
@@ -63,18 +66,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             try
             {
                 socket.Init(Thread.Loop, Thread.QueueCloseHandle);
-                socket.NoDelay(EndPointInformation.NoDelay);
+                socket.NoDelay(TransportContext.Options.NoDelay);
 
                 if (!useFileHandle)
                 {
-                    socket.Bind(EndPointInformation.IPEndPoint);
+                    socket.Bind((IPEndPoint)EndPoint);
 
                     // If requested port was "0", replace with assigned dynamic port.
-                    EndPointInformation.IPEndPoint = socket.GetSockIPEndPoint();
+                    EndPoint = socket.GetSockIPEndPoint();
                 }
                 else
                 {
-                    socket.Open((IntPtr)EndPointInformation.FileHandle);
+                    socket.Open((IntPtr)((FileHandleEndPoint)EndPoint).FileHandle);
                 }
             }
             catch
@@ -96,11 +99,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
                 if (!useFileHandle)
                 {
-                    pipe.Bind(EndPointInformation.SocketPath);
+                    // UnixDomainSocketEndPoint.ToString() returns the path
+                    pipe.Bind(EndPoint.ToString());
                 }
                 else
                 {
-                    pipe.Open((IntPtr)EndPointInformation.FileHandle);
+                    pipe.Open((IntPtr)((FileHandleEndPoint)EndPoint).FileHandle);
                 }
             }
             catch
@@ -114,7 +118,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
         private UvStreamHandle ListenHandle()
         {
-            switch (EndPointInformation.HandleType)
+            var handleEndPoint = (FileHandleEndPoint)EndPoint;
+
+            switch (handleEndPoint.FileHandleType)
             {
                 case FileHandleType.Auto:
                     break;
@@ -130,7 +136,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             try
             {
                 handle = ListenTcp(useFileHandle: true);
-                EndPointInformation.HandleType = FileHandleType.Tcp;
+                EndPoint = new FileHandleEndPoint(handleEndPoint.FileHandle, FileHandleType.Tcp);
                 return handle;
             }
             catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP)
@@ -139,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             }
 
             handle = ListenPipe(useFileHandle: true);
-            EndPointInformation.HandleType = FileHandleType.Pipe;
+            EndPoint = new FileHandleEndPoint(handleEndPoint.FileHandle, FileHandleType.Pipe);
             return handle;
         }
 
@@ -186,9 +192,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
         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);
+            HandleConnection(socket);
         }
 
         public virtual async Task DisposeAsync()
@@ -205,6 +209,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
                     listener._closed = true;
 
+                    listener.StopAcceptingConnections();
+
                 }, this).ConfigureAwait(false);
             }
 

+ 67 - 17
src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs

@@ -2,9 +2,14 @@
 // 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;
+using System.Threading;
+using System.Threading.Channels;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
+using Microsoft.AspNetCore.Connections;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
 using Microsoft.Extensions.Logging;
 
@@ -12,6 +17,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 {
     internal class ListenerContext
     {
+        // Single reader, single writer queue since all writes happen from the uv thread and reads happen sequentially
+        private readonly Channel<LibuvConnection> _acceptQueue = Channel.CreateUnbounded<LibuvConnection>(new UnboundedChannelOptions
+        {
+            SingleReader = true,
+            SingleWriter = true
+        });
+
         public ListenerContext(LibuvTransportContext transportContext)
         {
             TransportContext = transportContext;
@@ -19,29 +31,62 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
         public LibuvTransportContext TransportContext { get; set; }
 
-        public IEndPointInformation EndPointInformation { get; set; }
+        public EndPoint EndPoint { get; set; }
 
         public LibuvThread Thread { get; set; }
 
+        public PipeOptions InputOptions { get; set; }
+
+        public PipeOptions OutputOptions { get; set; }
+
+        public async ValueTask<LibuvConnection> AcceptAsync(CancellationToken cancellationToken = default)
+        {
+            while (await _acceptQueue.Reader.WaitToReadAsync())
+            {
+                while (_acceptQueue.Reader.TryRead(out var connection))
+                {
+                    return connection;
+                }
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Aborts all unaccepted connections in the queue
+        /// </summary>
+        /// <returns></returns>
+        public async Task AbortQueuedConnectionAsync()
+        {
+            while (await _acceptQueue.Reader.WaitToReadAsync())
+            {
+                while (_acceptQueue.Reader.TryRead(out var connection))
+                {
+                    // REVIEW: Pass an abort reason?
+                    connection.Abort();
+                }
+            }
+        }
+
         /// <summary>
         /// Creates a socket which can be used to accept an incoming connection.
         /// </summary>
         protected UvStreamHandle CreateAcceptSocket()
         {
-            switch (EndPointInformation.Type)
+            switch (EndPoint)
             {
-                case ListenType.IPEndPoint:
+                case IPEndPoint _:
                     return AcceptTcp();
-                case ListenType.SocketPath:
+                case UnixDomainSocketEndPoint _:
                     return AcceptPipe();
-                case ListenType.FileHandle:
+                case FileHandleEndPoint _:
                     return AcceptHandle();
                 default:
                     throw new InvalidOperationException();
             }
         }
 
-        protected async Task HandleConnectionAsync(UvStreamHandle socket)
+        protected internal void HandleConnection(UvStreamHandle socket)
         {
             try
             {
@@ -63,18 +108,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                     }
                 }
 
-                var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint);
-                var middlewareTask = TransportContext.ConnectionDispatcher.OnConnection(connection);
-                var transportTask = connection.Start();
-
-                await transportTask;
-                await middlewareTask;
+                var options = TransportContext.Options;
+                var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions, options.MaxReadBufferSize, options.MaxWriteBufferSize);
+                connection.Start();
 
-                connection.Dispose();
+                bool accepted = _acceptQueue.Writer.TryWrite(connection);
+                Debug.Assert(accepted, "The connection was not written to the channel!");
             }
             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)}.");
             }
         }
 
@@ -85,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             try
             {
                 socket.Init(Thread.Loop, Thread.QueueCloseHandle);
-                socket.NoDelay(EndPointInformation.NoDelay);
+                socket.NoDelay(TransportContext.Options.NoDelay);
             }
             catch
             {
@@ -113,9 +156,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             return pipe;
         }
 
+        protected void StopAcceptingConnections()
+        {
+            _acceptQueue.Writer.TryComplete();
+        }
+
         private UvStreamHandle AcceptHandle()
         {
-            switch (EndPointInformation.HandleType)
+            var fileHandleEndPoint = (FileHandleEndPoint)EndPoint;
+
+            switch (fileHandleEndPoint.FileHandleType)
             {
                 case FileHandleType.Auto:
                     throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first.");

+ 3 - 3
src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs

@@ -4,9 +4,9 @@
 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;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
 using Microsoft.Extensions.Logging;
 
@@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         public async Task StartAsync(
             string pipeName,
             byte[] pipeMessage,
-            IEndPointInformation endPointInformation,
+            EndPoint endPoint,
             LibuvThread thread)
         {
             _pipeName = pipeName;
@@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
                 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);
         }

+ 8 - 6
src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerSecondary.cs

@@ -2,10 +2,10 @@
 // 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;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
 using Microsoft.Extensions.Logging;
 
@@ -35,14 +35,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
         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,9 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
             {
                 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);
+                HandleConnection(acceptSocket);
             }
             catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode))
             {
@@ -192,11 +190,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
 
                     listener._closed = true;
 
+                    listener.StopAcceptingConnections();
+
                 }, this).ConfigureAwait(false);
             }
             else
             {
                 FreeBuffer();
+
+                StopAcceptingConnections();
             }
         }
     }

+ 15 - 3
src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs

@@ -1,9 +1,9 @@
-// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
+using System.IO.Pipelines;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv
 {
@@ -20,7 +20,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv
         /// </remarks>
         public int ThreadCount { get; set; } = ProcessorThreadCount;
 
-        internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create();
+        /// <summary>
+        /// Set to false to enable Nagle's algorithm for all connections.
+        /// </summary>
+        /// <remarks>
+        /// Defaults to true.
+        /// </remarks>
+        public bool NoDelay { get; set; } = true;
+
+        public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold;
+
+        public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold;
+
+        internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create;
 
         private static int ProcessorThreadCount
         {

+ 8 - 2
src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <Description>Libuv transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
@@ -10,12 +10,18 @@
     <IsShippingPackage>true</IsShippingPackage>
   </PropertyGroup>
 
+  <ItemGroup>
+    <Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
+    <Compile Include="..\..\Core\src\Internal\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
+  </ItemGroup>
+
   <ItemGroup>
     <Reference Include="Libuv" />
     <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
     <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
     <Reference Include="Microsoft.Extensions.Options" />
+    <Reference Include="System.Threading.Channels"  />
   </ItemGroup>
 
 </Project>

+ 3 - 3
src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.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.Libuv;
 using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
 using Microsoft.Extensions.DependencyInjection;
@@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Hosting
         {
             return hostBuilder.ConfigureServices(services =>
             {
-                services.AddSingleton<ITransportFactory, LibuvTransportFactory>();
+                services.AddSingleton<IConnectionListenerFactory, LibuvTransportFactory>();
             });
         }
 

+ 67 - 93
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,39 +19,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         [Fact]
         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 thread = new LibuvThread(transport);
-            Task connectionTask = null;
+            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 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();
+                    listenerContext.HandleConnection(socket);
 
                     mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored);
                     mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
                 }, (object)null);
 
-                var readAwaitable = mockConnectionDispatcher.Input.Reader.ReadAsync();
+                await using var connection = await listenerContext.AcceptAsync();
+
+                var readAwaitable = connection.Transport.Input.ReadAsync();
                 Assert.False(readAwaitable.IsCompleted);
             }
             finally
             {
-                mockConnectionDispatcher.Input.Reader.Complete();
-                mockConnectionDispatcher.Output.Writer.Complete();
-                await connectionTask;
-
                 await thread.StopAsync(TimeSpan.FromSeconds(5));
             }
         }
@@ -59,25 +53,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         [Fact]
         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 thread = new LibuvThread(transport);
-            mockConnectionDispatcher.InputOptions = pool =>
-                new PipeOptions(
-                    pool: pool,
+            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
-
-            mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
+                    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)
+            };
 
-            Task connectionTask = null;
             try
             {
                 await thread.StartAsync();
@@ -85,28 +81,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
                 // Write enough to make sure back pressure will be applied
                 await thread.PostAsync<object>(_ =>
                 {
-                    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();
+                    listenerContext.HandleConnection(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
-                mockConnectionDispatcher.Output.Writer.Complete();
-
-                await connectionTask.DefaultTimeout();
+                await connection.DisposeAsync();
 
                 // Assert that we don't try to start reading
                 Assert.Null(mockLibuv.AllocCallback);
@@ -114,9 +104,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
             }
             finally
             {
-                mockConnectionDispatcher.Input.Reader.Complete();
-                mockConnectionDispatcher.Output.Writer.Complete();
-
                 await thread.StopAsync(TimeSpan.FromSeconds(5));
             }
         }
@@ -124,29 +111,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         [Fact]
         public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied()
         {
-            var mockConnectionDispatcher = new MockConnectionDispatcher();
             var mockLibuv = new MockLibuv();
-            var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher };
-            var transport = new LibuvTransport(mockLibuv, transportContext, null);
-            var thread = new LibuvThread(transport);
+            var transportContext = new TestLibuvTransportContext();
+            var thread = new LibuvThread(mockLibuv, transportContext);
             var mockScheduler = new Mock<PipeScheduler>();
             Action backPressure = null;
             mockScheduler.Setup(m => m.Schedule(It.IsAny<Action<object>>(), It.IsAny<object>())).Callback<Action<object>, object>((a, o) =>
             {
                 backPressure = () => a(o);
             });
-            mockConnectionDispatcher.InputOptions = pool =>
-                new PipeOptions(
-                    pool: pool,
+            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);
-
-            mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
+                    useSynchronizationContext: false),
+                OutputOptions = new PipeOptions(
+                    pool: thread.MemoryPool,
+                    readerScheduler: thread,
+                    writerScheduler: PipeScheduler.Inline,
+                    useSynchronizationContext: false)
+            };
 
-            Task connectionTask = null;
             try
             {
                 await thread.StartAsync();
@@ -154,32 +144,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
                 // Write enough to make sure back pressure will be applied
                 await thread.PostAsync<object>(_ =>
                 {
-                    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();
-
+                    listenerContext.HandleConnection(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 mockConnectionDispatcher.Input.Reader.ReadAsync();
+                var result = await connection.Transport.Input.ReadAsync();
                 // Calling advance will call into our custom scheduler that captures the back pressure
                 // callback
-                mockConnectionDispatcher.Input.Reader.AdvanceTo(result.Buffer.End);
+                connection.Transport.Input.AdvanceTo(result.Buffer.End);
 
                 // Cancel the current pending flush
-                mockConnectionDispatcher.Input.Writer.CancelPendingFlush();
+                connection.Application.Output.CancelPendingFlush();
 
                 // Now release the back pressure
                 await thread.PostAsync(a => a(), backPressure);
@@ -189,19 +175,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
                 Assert.Null(mockLibuv.ReadCallback);
 
                 // Now complete the output writer and wait for the connection to close
-                mockConnectionDispatcher.Output.Writer.Complete();
-
-                await connectionTask.DefaultTimeout();
-
+                await connection.DisposeAsync();
+                
                 // 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));
             }
         }
@@ -209,42 +190,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         [Fact]
         public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled()
         {
-            var mockConnectionDispatcher = new MockConnectionDispatcher();
             var mockLibuv = new MockLibuv();
-            var transportContext = new TestLibuvTransportContext { ConnectionDispatcher = mockConnectionDispatcher };
-            var transport = new LibuvTransport(mockLibuv, transportContext, null);
-            var thread = new LibuvThread(transport);
+            var transportContext = new TestLibuvTransportContext();
+            var thread = new LibuvThread(mockLibuv, transportContext);
+            var listenerContext = new ListenerContext(transportContext)
+            {
+                Thread = thread
+            };
 
-            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();
-
+                    listenerContext.HandleConnection(socket);
+                    
                     var ignored = new LibuvFunctions.uv_buf_t();
                     mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored);
                 }, (object)null);
 
-                var readAwaitable = await mockConnectionDispatcher.Input.Reader.ReadAsync();
+                await using var connection = await listenerContext.AcceptAsync();
+
+                var readAwaitable = await connection.Transport.Input.ReadAsync();
                 Assert.True(readAwaitable.IsCompleted);
             }
             finally
             {
-                mockConnectionDispatcher.Input.Reader.Complete();
-                mockConnectionDispatcher.Output.Writer.Complete();
-                await connectionTask;
-
                 await thread.StopAsync(TimeSpan.FromSeconds(5));
             }
         }
     }
-}
+}

+ 41 - 4
src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs

@@ -14,7 +14,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.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,11 +41,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
 
         public LibuvOutputConsumerTests()
         {
-            _memoryPool = KestrelMemoryPool.Create();
+            _memoryPool = MemoryPoolFactory.Create();
             _mockLibuv = new MockLibuv();
 
-            var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0));
-            _libuvThread = new LibuvThread(libuvTransport, maxLoops: 1);
+            var context = new TestLibuvTransportContext();
+            _libuvThread = new LibuvThread(_mockLibuv, context, maxLoops: 1);
             _libuvThread.StartAsync().Wait();
         }
 
@@ -769,5 +768,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
                 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;
+                }
+            }
+        }
     }
 }

+ 2 - 4
src/Servers/Kestrel/Transport.Libuv/test/LibuvThreadTests.cs

@@ -14,11 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         [Fact]
         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 thread = new LibuvThread(transport);
+            var transportContext = new TestLibuvTransportContext();
+            var thread = new LibuvThread(mockLibuv, transportContext);
             var ranOne = false;
             var ranTwo = false;
             var ranThree = false;

+ 124 - 39
src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs

@@ -1,18 +1,18 @@
 // 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;
 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,71 +23,154 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
 {
     public class LibuvTransportTests
     {
-        public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
-        {
-            new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
-            new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
-            {
-                ConnectionAdapters = { new PassThroughConnectionAdapter() }
-            }
-        };
-
         public static IEnumerable<object[]> OneToTen => Enumerable.Range(1, 10).Select(i => new object[] { i });
 
         [Fact]
         public async Task TransportCanBindAndStop()
         {
             var transportContext = new TestLibuvTransportContext();
-            var transport = new LibuvTransport(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();
-            await transport.StopAsync();
+            await transport.DisposeAsync();
         }
 
         [Fact]
         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 IPEndPoint(IPAddress.Loopback, 0));
 
             await transport.BindAsync();
             await transport.UnbindAsync();
-            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 transportContext = new TestLibuvTransportContext();
+            await using var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0));
 
-            var transportContext = new TestLibuvTransportContext
+            await transport.BindAsync();
+            var endpoint = (IPEndPoint)transport.EndPoint;
+
+            async Task EchoServerAsync()
             {
-                ConnectionDispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build())
-            };
+                while (true)
+                {
+                    await using var connection = await transport.AcceptAsync();
 
-            var transport = new LibuvTransport(transportContext, listenOptions);
+                    if (connection == null)
+                    {
+                        break;
+                    }
 
-            await transport.BindAsync();
+                    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(listenOptions.IPEndPoint.Port))
+            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");
+                await socket.SendAsync(data, SocketFlags.None);
+
                 var buffer = new byte[data.Length];
                 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);
+            }
+
+            await transport.UnbindAsync();
+
+            await serverTask.DefaultTimeout();
+        }
+
+        [Fact]
+        public async Task UnacceptedConnectionsAreAborted()
+        {
+            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))
+                {
+                    try
+                    {
+                        var read = await socket.ReceiveAsync(new byte[10], SocketFlags.None);
+                        Assert.Equal(0, read);
+                    }
+                    catch (SocketException)
+                    {
+                        // The connection can be reset sometimes
+                    }
                 }
             }
 
-            Assert.True(await serviceContext.ConnectionManager.CloseAllConnectionsAsync(new CancellationTokenSource(TestConstants.DefaultTimeout).Token));
+            var connectTask = ConnectAsync();
+
+            await transport.UnbindAsync();
+            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();
+        }
+
+        [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;
+
+            await transport.UnbindAsync();
+            await transport.DisposeAsync();
+
+            await Assert.ThrowsAsync<ObjectDisposedException>(() => transport.AcceptAsync().AsTask());
+        }
+
+        [Fact]
+        public async Task CallingDisposeAsyncWillYieldPendingAccepts()
+        {
+            var transportContext = new TestLibuvTransportContext();
+            await using var transport = new LibuvConnectionListener(transportContext, new IPEndPoint(IPAddress.Loopback, 0));
+
+            await transport.BindAsync();
+
+            var acceptTask = transport.AcceptAsync();
+
             await transport.UnbindAsync();
-            await transport.StopAsync();
+
+            var connection = await acceptTask.DefaultTimeout();
+
+            Assert.Null(connection);
         }
 
         [ConditionalTheory]
@@ -106,13 +189,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
 
             var transportContext = new TestLibuvTransportContext
             {
-                ConnectionDispatcher = new ConnectionDispatcher(serviceContext, listenOptions.Build()),
                 Options = new LibuvTransportOptions { ThreadCount = threadCount }
             };
 
-            var transport = new LibuvTransport(transportContext, listenOptions);
-
+            await using 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);
 
             using (var client = new HttpClient())
             {
@@ -132,12 +217,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
 
             await transport.UnbindAsync();
 
-            if (!await serviceContext.ConnectionManager.CloseAllConnectionsAsync(default).ConfigureAwait(false))
+            await acceptTask;
+
+            if (!await serviceContext.ConnectionManager.CloseAllConnectionsAsync(default))
             {
-                await serviceContext.ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false);
+                await serviceContext.ConnectionManager.AbortAllConnectionsAsync();
             }
-
-            await transport.StopAsync();
         }
     }
 }

+ 97 - 132
src/Servers/Kestrel/Transport.Libuv/test/ListenerPrimaryTests.cs

@@ -2,16 +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.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;
@@ -28,42 +22,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         {
             var libuv = new LibuvFunctions();
 
-            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
+            var endpoint = 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 LibuvTransport(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);
+            var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary);
             await libuvThreadPrimary.StartAsync();
             var listenerPrimary = new ListenerPrimary(transportContextPrimary);
-            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);
-            var address = GetUri(listenOptions);
+            await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary);
+            var address = GetUri(listenerPrimary.EndPoint);
 
-            // 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 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(libuvTransport);
+            var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary);
             await libuvThreadSecondary.StartAsync();
             var listenerSecondary = new ListenerSecondary(transportContextSecondary);
-            await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary);
+            await listenerSecondary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadSecondary);
 
             var maxWait = Task.Delay(TestConstants.DefaultTimeout);
             // wait for ListenerPrimary.ReadCallback to add the secondary pipe
@@ -77,14 +68,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
             }
 
             // Once a secondary listener is added, TCP connections start getting dispatched to it
-            await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" });
+            // This returns the incomplete primary task after the secondary listener got the last
+            // connection
+            var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary);
 
             // 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));
+            ListenerContext currentListener = listenerSecondary;
+            Task<LibuvConnection> expected = primary;
+
+            await AssertRoundRobin(address, listenerPrimary, listenerSecondary, currentListener, expected);
 
             await listenerSecondary.DisposeAsync();
+
             await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5));
 
             await listenerPrimary.DisposeAsync();
@@ -96,48 +91,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         public async Task NonListenerPipeConnectionsAreLoggedAndIgnored()
         {
             var libuv = new LibuvFunctions();
-            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
+            var endpoint = 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 LibuvTransport(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);
+            var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary);
             await libuvThreadPrimary.StartAsync();
             var listenerPrimary = new ListenerPrimary(transportContextPrimary);
-            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);
-            var address = GetUri(listenOptions);
+            await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary);
+            var address = GetUri(listenerPrimary.EndPoint);
 
             // Add secondary listener
-            var libuvThreadSecondary = new LibuvThread(libuvTransport);
+            var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary);
             await libuvThreadSecondary.StartAsync();
             var listenerSecondary = new ListenerSecondary(transportContextSecondary);
-            await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary);
+            await listenerSecondary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadSecondary);
 
             // TCP Connections get round-robined
-            await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" });
-            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
+            var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary);
+
+            // Make sure the pending accept get yields
+            using (var socket = await HttpClientSlim.GetSocket(address))
+            {
+                await (await primary.DefaultTimeout()).DisposeAsync();
+            }
 
             // Create a pipe connection and keep it open without sending any data
             var connectTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -173,9 +156,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
             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 AssertRoundRobin(address, listenerPrimary, listenerSecondary, listenerPrimary);
 
             await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null);
 
@@ -186,9 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
             }
 
             // 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 AssertRoundRobin(address, listenerPrimary, listenerSecondary, listenerPrimary);
 
             await listenerSecondary.DisposeAsync();
             await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5));
@@ -207,45 +186,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
         public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored()
         {
             var libuv = new LibuvFunctions();
-            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
+            var endpoint = 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 LibuvTransport(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);
+            var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary);
             await libuvThreadPrimary.StartAsync();
             var listenerPrimary = new ListenerPrimary(transportContextPrimary);
-            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);
-            var address = GetUri(listenOptions);
+            await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary);
+            var address = GetUri(listenerPrimary.EndPoint);
 
             // Add secondary listener with wrong pipe message
-            var libuvThreadSecondary = new LibuvThread(libuvTransport);
+            var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary);
             await libuvThreadSecondary.StartAsync();
             var listenerSecondary = new ListenerSecondary(transportContextSecondary);
-            await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary);
+            await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), endpoint, libuvThreadSecondary);
 
             // Wait up to 10 seconds for error to be logged
             for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++)
@@ -253,10 +215,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
                 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));
+            // 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 using var connection = await listenerPrimary.AcceptAsync().AsTask().DefaultTimeout();
+            }
 
             await listenerSecondary.DisposeAsync();
             await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5));
@@ -270,73 +235,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
             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)
+
+        private static async Task AssertRoundRobin(Uri address, ListenerPrimary listenerPrimary, ListenerSecondary listenerSecondary, ListenerContext currentListener, Task<LibuvConnection> expected = null, int connections = 4)
         {
-            for (var i = 0; i < maxRetries; i++)
+            for (int i = 0; i < connections; i++)
             {
-                var response = await HttpClientSlim.GetStringAsync(address);
-                if (response == expected)
+                if (currentListener == listenerPrimary)
                 {
-                    return;
+                    expected ??= listenerSecondary.AcceptAsync().AsTask();
+                    currentListener = listenerSecondary;
                 }
-
-                if (allowed != null)
+                else
                 {
-                    Assert.Contains(response, allowed);
+                    expected ??= listenerPrimary.AcceptAsync().AsTask();
+                    currentListener = listenerPrimary;
                 }
 
-                await Task.Delay(retryDelay);
-            }
+                using var socket = await HttpClientSlim.GetSocket(address);
 
-            Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries.");
-        }
+                await using var connection = await expected.DefaultTimeout();
 
-        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}");
+                expected = null;
             }
-
-            var scheme = options.ConnectionAdapters.Any(f => f.IsHttps)
-                ? "https"
-                : "http";
-
-            return new Uri($"{scheme}://{options.IPEndPoint}");
         }
 
-        private class ConnectionBuilder : IConnectionBuilder
+        private static async Task<Task<LibuvConnection>> WaitForSecondaryListener(Uri address, ListenerContext listenerPrimary, ListenerContext listenerSecondary)
         {
-            private readonly List<Func<ConnectionDelegate, ConnectionDelegate>> _components = new List<Func<ConnectionDelegate, ConnectionDelegate>>();
+            int maxRetries = 100;
+            int retryDelay = 100;
 
-            public IServiceProvider ApplicationServices { get; set; }
+            Task<LibuvConnection> primary = null;
+            Task<LibuvConnection> secondary = null;
 
-            public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
+            for (var i = 0; i < maxRetries; i++)
             {
-                _components.Add(middleware);
-                return this;
-            }
+                primary ??= listenerPrimary.AcceptAsync().AsTask();
+                secondary ??= listenerSecondary.AcceptAsync().AsTask();
 
-            public ConnectionDelegate Build()
-            {
-                ConnectionDelegate app = context =>
+                using var _ = await HttpClientSlim.GetSocket(address);
+
+                var task = await Task.WhenAny(primary, secondary);
+
+                if (task == secondary)
                 {
-                    return Task.CompletedTask;
-                };
+                    // Dispose this connection now that we know the seconary listener is working
+                    await (await secondary).DisposeAsync();
 
-                for (int i = _components.Count - 1; i >= 0; i--)
+                    // Return the primary task (it should be incomplete), we do this so that we can
+                    return primary;
+                }
+                else
                 {
-                    var component = _components[i];
-                    app = component(app);
+                    // Dispose the connection
+                    await (await primary).DisposeAsync();
+
+                    primary = null;
                 }
 
-                return app;
+                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}");
         }
     }
 }

+ 0 - 31
src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/MockConnectionDispatcher.cs

@@ -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<MemoryPool<byte>, PipeOptions> InputOptions { get; set; } = pool => new PipeOptions(pool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
-        public Func<MemoryPool<byte>, 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; }
-    }
-}

+ 0 - 1
src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs

@@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers
             var logger = new TestApplicationErrorLogger();
 
             AppLifetime = new LifetimeNotImplemented();
-            ConnectionDispatcher = new MockConnectionDispatcher();
             Log = new LibuvTrace(logger);
             Options = new LibuvTransportOptions { ThreadCount = 1 };
         }

+ 1 - 1
src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj

@@ -6,7 +6,7 @@
   <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
     <Compile Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs" />
     <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions"  />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions"  />
+    <Reference Include="Microsoft.AspNetCore.Connections.Abstractions"  />
     <Reference Include="Microsoft.Extensions.Options"  />
   </ItemGroup>
 </Project>

+ 6 - 3
src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs

@@ -11,15 +11,18 @@ namespace Microsoft.AspNetCore.Hosting
 }
 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<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> 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<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
+        public System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { 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

+ 61 - 24
src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs

@@ -11,19 +11,17 @@ using System.Runtime.InteropServices;
 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, IDisposable
+    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);
 
         private readonly Socket _socket;
-        private readonly PipeScheduler _scheduler;
         private readonly ISocketsTrace _trace;
         private readonly SocketReceiver _receiver;
         private readonly SocketSender _sender;
@@ -32,8 +30,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
         private readonly object _shutdownLock = new object();
         private volatile bool _socketDisposed;
         private volatile Exception _shutdownReason;
-
-        internal SocketConnection(Socket socket, MemoryPool<byte> memoryPool, PipeScheduler scheduler, ISocketsTrace trace)
+        private Task _processingTask;
+
+        internal SocketConnection(Socket socket,
+                                  MemoryPool<byte> memoryPool,
+                                  PipeScheduler scheduler,
+                                  ISocketsTrace trace,
+                                  long? maxReadBufferSize = null,
+                                  long? maxWriteBufferSize = null)
         {
             Debug.Assert(socket != null);
             Debug.Assert(memoryPool != null);
@@ -41,35 +45,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
 
             _socket = socket;
             MemoryPool = memoryPool;
-            _scheduler = scheduler;
-            Logger = trace;
             _trace = trace;
 
-            var localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
-            var remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint;
-
-            LocalAddress = localEndPoint.Address;
-            LocalPort = localEndPoint.Port;
-
-            RemoteAddress = remoteEndPoint.Address;
-            RemotePort = remoteEndPoint.Port;
+            LocalEndPoint = _socket.LocalEndPoint;
+            RemoteEndPoint = _socket.RemoteEndPoint;
 
             ConnectionClosed = _connectionClosedTokenSource.Token;
 
             // 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);
+
+            maxReadBufferSize ??= 0;
+            maxWriteBufferSize ??= 0;
+
+            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);
+
+            // Set the transport and connection id
+            Transport = pair.Transport;
+            Application = pair.Application;
         }
 
+        public PipeWriter Input => Application.Output;
+
+        public PipeReader Output => Application.Input;
+
         public override MemoryPool<byte> MemoryPool { get; }
-        public override PipeScheduler InputWriterScheduler => _scheduler;
-        public override PipeScheduler OutputReaderScheduler => _scheduler;
 
-        public async Task StartAsync()
+        public void Start()
+        {
+            _processingTask = StartAsync();
+        }
+
+        private async Task StartAsync()
         {
             try
             {
@@ -83,7 +98,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
 
                 _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<object>(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)
             {
@@ -101,10 +131,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
         }
 
         // Only called after connection middleware is complete which means the ConnectionClosed token has fired.
-        public void Dispose()
+        public override async ValueTask DisposeAsync()
         {
+            Transport.Input.Complete();
+            Transport.Output.Complete();
+
+            if (_processingTask != null)
+            {
+                await _processingTask;
+            }
+
             _connectionClosedTokenSource.Dispose();
-            _connectionClosingCts.Dispose();
         }
 
         private async Task DoReceive()
@@ -211,7 +248,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
             }
             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)

+ 6 - 1
src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj

@@ -10,9 +10,14 @@
     <NoWarn>CS1591;$(NoWarn)</NoWarn>
   </PropertyGroup>
 
+  <ItemGroup>
+    <Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
+    <Compile Include="..\..\Core\src\Internal\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
+  </ItemGroup>
+
   <ItemGroup>
     <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
-    <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
     <Reference Include="Microsoft.Extensions.Options" />
   </ItemGroup>
 

+ 139 - 0
src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs

@@ -0,0 +1,139 @@
+// 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;
+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<byte> _memoryPool;
+        private readonly int _numSchedulers;
+        private readonly PipeScheduler[] _schedulers;
+        private readonly ISocketsTrace _trace;
+        private Socket _listenSocket;
+        private int _schedulerIndex;
+        private readonly SocketTransportOptions _options;
+
+        public EndPoint EndPoint { get; private set; }
+
+        internal SocketConnectionListener(
+            EndPoint endpoint,
+            SocketTransportOptions options,
+            ISocketsTrace trace)
+        {
+            Debug.Assert(endpoint != null);
+            Debug.Assert(endpoint is IPEndPoint);
+            Debug.Assert(trace != null);
+
+            EndPoint = endpoint;
+            _trace = trace;
+            _options = options;
+            _memoryPool = _options.MemoryPoolFactory();
+            var ioQueueCount = options.IOQueueCount;
+
+            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);
+            }
+
+            // 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
+            if (EndPoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any)
+            {
+                listenSocket.DualMode = true;
+            }
+
+            try
+            {
+                listenSocket.Bind(EndPoint);
+            }
+            catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
+            {
+                throw new AddressInUseException(e.Message, e);
+            }
+
+            EndPoint = listenSocket.LocalEndPoint;
+
+            listenSocket.Listen(512);
+
+            _listenSocket = listenSocket;
+        }
+
+        public async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
+        {
+            while (true)
+            {
+                try
+                {
+                    var acceptSocket = await _listenSocket.AcceptAsync();
+                    acceptSocket.NoDelay = _options.NoDelay;
+
+                    var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize);
+
+                    connection.Start();
+
+                    _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)
+                {
+                    // The connection got reset while it was in the backlog, so we try again.
+                    _trace.ConnectionReset(connectionId: "(null)");
+                }
+            }
+        }
+
+        public ValueTask UnbindAsync(CancellationToken cancellationToken = default)
+        {
+            _listenSocket?.Dispose();
+            return default;
+        }
+
+        public ValueTask DisposeAsync()
+        {
+            _listenSocket?.Dispose();
+            // Dispose the memory pool
+            _memoryPool.Dispose();
+            return default;
+        }
+    }
+}

+ 0 - 205
src/Servers/Kestrel/Transport.Sockets/src/SocketTransport.cs

@@ -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<byte> _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<byte> 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)}.");
-            }
-        }
-    }
-}

+ 13 - 33
src/Servers/Kestrel/Transport.Sockets/src/SocketTransportFactory.cs

@@ -2,66 +2,46 @@
 // 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.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
     {
         private readonly SocketTransportOptions _options;
-        private readonly IHostApplicationLifetime _appLifetime;
         private readonly SocketsTrace _trace;
 
         public SocketTransportFactory(
             IOptions<SocketTransportOptions> 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<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
         {
-            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));
-            }
-
-            return new SocketTransport(endPointInformation, dispatcher, _appLifetime, _options.IOQueueCount, _trace, _options.MemoryPoolFactory());
+            var transport = new SocketConnectionListener(endpoint, _options, _trace);
+            transport.Bind();
+            return new ValueTask<IConnectionListener>(transport);
         }
     }
 }

+ 15 - 3
src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs

@@ -1,9 +1,9 @@
-// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
+using System.IO.Pipelines;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
 {
@@ -17,6 +17,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
         /// </remarks>
         public int IOQueueCount { get; set; } = Math.Min(Environment.ProcessorCount, 16);
 
-        internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create();
+        /// <summary>
+        /// Set to false to enable Nagle's algorithm for all connections.
+        /// </summary>
+        /// <remarks>
+        /// Defaults to true.
+        /// </remarks>
+        public bool NoDelay { get; set; } = true;
+
+        public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold;
+
+        public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold;
+
+        internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create;
     }
 }

+ 3 - 3
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 @@ namespace Microsoft.AspNetCore.Hosting
         {
             return hostBuilder.ConfigureServices(services =>
             {
-                services.AddSingleton<ITransportFactory, SocketTransportFactory>();
+                services.AddSingleton<IConnectionListenerFactory, SocketTransportFactory>();
             });
         }
 

+ 1 - 2
src/Servers/Kestrel/perf/Kestrel.Performance/ChunkWriterBenchmark.cs

@@ -6,7 +6,6 @@ using System.IO.Pipelines;
 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
         [GlobalSetup]
         public void Setup()
         {
-            _memoryPool = KestrelMemoryPool.Create();
+            _memoryPool = MemoryPoolFactory.Create();
             var pipe = new Pipe(new PipeOptions(_memoryPool));
             _reader = pipe.Reader;
             _writer = pipe.Writer;

+ 1 - 2
src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionBenchmark.cs

@@ -10,7 +10,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.Core.Internal.Infrastructure;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Performance
 {
@@ -27,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
         [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);
 

+ 1 - 2
src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs

@@ -9,7 +9,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.Core.Internal.Infrastructure;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Performance
 {
@@ -23,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
         [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);
 

+ 1 - 2
src/Servers/Kestrel/perf/Kestrel.Performance/Http1ReadingBenchmark.cs

@@ -13,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.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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
         [GlobalSetup]
         public void GlobalSetup()
         {
-            _memoryPool = KestrelMemoryPool.Create();
+            _memoryPool = MemoryPoolFactory.Create();
             _http1Connection = MakeHttp1Connection();
         }
 

+ 1 - 2
src/Servers/Kestrel/perf/Kestrel.Performance/Http1WritingBenchmark.cs

@@ -13,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.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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
         [GlobalSetup]
         public void GlobalSetup()
         {
-            _memoryPool = KestrelMemoryPool.Create();
+            _memoryPool = MemoryPoolFactory.Create();
             _http1Connection = MakeHttp1Connection();
         }
 

Some files were not shown because too many files changed in this diff