diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs index 6f95d242a591..a3275335a6e2 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs @@ -63,7 +63,8 @@ public async ValueTask ConnectAsync(EndPoint endpoint, Cancel _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize, - _options.WaitForDataBeforeAllocatingBuffer); + _options.WaitForDataBeforeAllocatingBuffer, + _options.UnsafePreferInlineScheduling); socketConnection.Start(); return socketConnection; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs index d8f6146d37e5..73e5e6f1e4c8 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs @@ -37,11 +37,12 @@ internal sealed class SocketConnection : TransportConnection internal SocketConnection(Socket socket, MemoryPool memoryPool, - PipeScheduler scheduler, + PipeScheduler transportScheduler, ISocketsTrace trace, long? maxReadBufferSize = null, long? maxWriteBufferSize = null, - bool waitForData = true) + bool waitForData = true, + bool useInlineSchedulers = false) { Debug.Assert(socket != null); Debug.Assert(memoryPool != null); @@ -60,7 +61,15 @@ internal SocketConnection(Socket socket, // On *nix platforms, Sockets already dispatches to the ThreadPool. // Yes, the IOQueues are still used for the PipeSchedulers. This is intentional. // https://github.com/aspnet/KestrelHttpServer/issues/2573 - var awaiterScheduler = IsWindows ? scheduler : PipeScheduler.Inline; + var awaiterScheduler = IsWindows ? transportScheduler : PipeScheduler.Inline; + + var applicationScheduler = PipeScheduler.ThreadPool; + if (useInlineSchedulers) + { + transportScheduler = PipeScheduler.Inline; + awaiterScheduler = PipeScheduler.Inline; + applicationScheduler = PipeScheduler.Inline; + } _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); @@ -68,8 +77,8 @@ internal SocketConnection(Socket socket, 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 inputOptions = new PipeOptions(MemoryPool, applicationScheduler, transportScheduler, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, transportScheduler, applicationScheduler, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index a98ec0436315..103a06c4d9e1 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -124,7 +124,8 @@ public async ValueTask AcceptAsync(CancellationToken cancella } var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, - _options.MaxReadBufferSize, _options.MaxWriteBufferSize, _options.WaitForDataBeforeAllocatingBuffer); + _options.MaxReadBufferSize, _options.MaxWriteBufferSize, _options.WaitForDataBeforeAllocatingBuffer, + _options.UnsafePreferInlineScheduling); connection.Start(); diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index 957876ca5953..dc48442f6a2a 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -44,6 +44,18 @@ public class SocketTransportOptions public long? MaxWriteBufferSize { get; set; } = 64 * 1024; + /// + /// Inline application and transport continuations instead of dispatching to the threadpool. + /// + /// + /// This will run application code on the IO thread which is why this is unsafe. + /// It is recommended to set the DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS environment variable to '1' when using this setting to also inline the completions + /// at the runtime layer as well. + /// This setting can make performance worse if there is expensive work that will end up holding onto the IO thread for longer than needed. + /// Test to make sure this setting helps performance. + /// + public bool UnsafePreferInlineScheduling { get; set; } + internal Func> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create; } }