From 08fc87ab6b1da8104dbaf1f0b7b891170151141a Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 21 Mar 2021 10:41:02 -0700 Subject: [PATCH 1/2] Fix some low hanging allocation fruit - Use singleton pipe options - Use Task.Yield instead of AwaitableThreadPool on the server side - Change WebSocketMiddleware handshake helpers to remove enumerator allocations. --- .../WebSockets/src/HandshakeHelpers.cs | 4 +- src/SignalR/SignalR.slnf | 74 +++++++++---------- .../src/HttpConnectionDispatcherOptions.cs | 11 +++ .../src/Internal/HttpConnectionContext.cs | 2 +- .../src/Internal/HttpConnectionDispatcher.cs | 5 +- 5 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs index c37e41b40807..f18de60e0e30 100644 --- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs +++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs @@ -15,7 +15,7 @@ internal static class HandshakeHelpers /// /// Gets request headers needed process the handshake on the server. /// - public static readonly IEnumerable NeededHeaders = new[] + public static readonly string[] NeededHeaders = new[] { HeaderNames.Upgrade, HeaderNames.Connection, @@ -34,7 +34,7 @@ internal static class HandshakeHelpers }; // Verify Method, Upgrade, Connection, version, key, etc.. - public static bool CheckSupportedWebSocketRequest(string method, IEnumerable> headers) + public static bool CheckSupportedWebSocketRequest(string method, List> headers) { bool validUpgrade = false, validConnection = false, validKey = false, validVersion = false; diff --git a/src/SignalR/SignalR.slnf b/src/SignalR/SignalR.slnf index 1de9dbf0ab7a..0976c929b029 100644 --- a/src/SignalR/SignalR.slnf +++ b/src/SignalR/SignalR.slnf @@ -1,72 +1,72 @@ -{ +{ "solution": { "path": "..\\..\\AspNetCore.sln", - "projects" : [ - "src\\SignalR\\samples\\SignalRSamples\\SignalRSamples.csproj", - "src\\SignalR\\samples\\SocialWeather\\SocialWeather.csproj", - "src\\SignalR\\samples\\ClientSample\\ClientSample.csproj", - "src\\SignalR\\samples\\WebSocketSample\\WebSocketSample.csproj", - "src\\SignalR\\samples\\JwtClientSample\\JwtClientSample.csproj", - "src\\SignalR\\clients\\ts\\FunctionalTests\\SignalR.Client.FunctionalTestApp.csproj", + "projects": [ + "src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", + "src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj", + "src\\DataProtection\\DataProtection\\src\\Microsoft.AspNetCore.DataProtection.csproj", + "src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj", + "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", + "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", "src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj", "src\\Hosting\\TestHost\\src\\Microsoft.AspNetCore.TestHost.csproj", + "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj", + "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", + "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", + "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", + "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", + "src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj", + "src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj", + "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", "src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj", + "src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj", "src\\Middleware\\Diagnostics\\src\\Microsoft.AspNetCore.Diagnostics.csproj", + "src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj", "src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj", + "src\\Middleware\\WebSockets\\src\\Microsoft.AspNetCore.WebSockets.csproj", "src\\Security\\Authentication\\Cookies\\src\\Microsoft.AspNetCore.Authentication.Cookies.csproj", "src\\Security\\Authentication\\Core\\src\\Microsoft.AspNetCore.Authentication.csproj", "src\\Security\\Authentication\\JwtBearer\\src\\Microsoft.AspNetCore.Authentication.JwtBearer.csproj", + "src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj", + "src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj", + "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", "src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj", + "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", "src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", - "src\\SignalR\\samples\\JwtSample\\JwtSample.csproj", + "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", + "src\\SignalR\\clients\\csharp\\Client.Core\\src\\Microsoft.AspNetCore.SignalR.Client.Core.csproj", + "src\\SignalR\\clients\\csharp\\Client\\src\\Microsoft.AspNetCore.SignalR.Client.csproj", "src\\SignalR\\clients\\csharp\\Client\\test\\FunctionalTests\\Microsoft.AspNetCore.SignalR.Client.FunctionalTests.csproj", "src\\SignalR\\clients\\csharp\\Client\\test\\UnitTests\\Microsoft.AspNetCore.SignalR.Client.Tests.csproj", "src\\SignalR\\clients\\csharp\\Http.Connections.Client\\src\\Microsoft.AspNetCore.Http.Connections.Client.csproj", + "src\\SignalR\\clients\\ts\\FunctionalTests\\SignalR.Client.FunctionalTestApp.csproj", "src\\SignalR\\common\\Http.Connections.Common\\src\\Microsoft.AspNetCore.Http.Connections.Common.csproj", "src\\SignalR\\common\\Http.Connections\\src\\Microsoft.AspNetCore.Http.Connections.csproj", "src\\SignalR\\common\\Http.Connections\\test\\Microsoft.AspNetCore.Http.Connections.Tests.csproj", + "src\\SignalR\\common\\Protocols.Json\\src\\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj", "src\\SignalR\\common\\Protocols.MessagePack\\src\\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj", + "src\\SignalR\\common\\Protocols.NewtonsoftJson\\src\\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj", "src\\SignalR\\common\\SignalR.Common\\src\\Microsoft.AspNetCore.SignalR.Common.csproj", "src\\SignalR\\common\\SignalR.Common\\test\\Microsoft.AspNetCore.SignalR.Common.Tests.csproj", "src\\SignalR\\common\\testassets\\Tests.Utils\\Microsoft.AspNetCore.SignalR.Tests.Utils.csproj", "src\\SignalR\\perf\\Microbenchmarks\\Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj", + "src\\SignalR\\samples\\ClientSample\\ClientSample.csproj", + "src\\SignalR\\samples\\JwtClientSample\\JwtClientSample.csproj", + "src\\SignalR\\samples\\JwtSample\\JwtSample.csproj", + "src\\SignalR\\samples\\SignalRSamples\\SignalRSamples.csproj", + "src\\SignalR\\samples\\SocialWeather\\SocialWeather.csproj", + "src\\SignalR\\samples\\WebSocketSample\\WebSocketSample.csproj", "src\\SignalR\\server\\Core\\src\\Microsoft.AspNetCore.SignalR.Core.csproj", "src\\SignalR\\server\\SignalR\\src\\Microsoft.AspNetCore.SignalR.csproj", "src\\SignalR\\server\\SignalR\\test\\Microsoft.AspNetCore.SignalR.Tests.csproj", - "src\\SignalR\\common\\Protocols.NewtonsoftJson\\src\\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj", "src\\SignalR\\server\\Specification.Tests\\src\\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj", "src\\SignalR\\server\\StackExchangeRedis\\src\\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj", "src\\SignalR\\server\\StackExchangeRedis\\test\\Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests.csproj", - "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", - "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", - "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", - "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", - "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", - "src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj", - "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", - "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", - "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", - "src\\DataProtection\\DataProtection\\src\\Microsoft.AspNetCore.DataProtection.csproj", - "src\\Middleware\\WebSockets\\src\\Microsoft.AspNetCore.WebSockets.csproj", - "src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj", - "src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj", - "src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", - "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", - "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", - "src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj", - "src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj", - "src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj", - "src\\SignalR\\clients\\csharp\\Client\\src\\Microsoft.AspNetCore.SignalR.Client.csproj", - "src\\SignalR\\clients\\csharp\\Client.Core\\src\\Microsoft.AspNetCore.SignalR.Client.Core.csproj", - "src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj", - "src\\SignalR\\common\\Protocols.Json\\src\\Microsoft.AspNetCore.SignalR.Protocols.Json.csproj", - "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", - "src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj", - "src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj" + "src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj" ] } -} +} \ No newline at end of file diff --git a/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs b/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs index e1f97d718301..b06beae8df5c 100644 --- a/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs +++ b/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.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.Collections.Generic; +using System.IO.Pipelines; using Microsoft.AspNetCore.Authorization; namespace Microsoft.AspNetCore.Http.Connections @@ -15,6 +16,9 @@ public class HttpConnectionDispatcherOptions // There maybe the opportunity for performance gains by tuning this default. private const int DefaultPipeBufferSize = 32768; + private PipeOptions? _transportPipeOptions; + private PipeOptions? _appPipeOptions; + /// /// Initializes a new instance of the class. /// @@ -63,5 +67,12 @@ public HttpConnectionDispatcherOptions() /// The default value is 0, the lowest possible protocol version. /// public int MinimumProtocolVersion { get; set; } = 0; + + // We initialize these lazily based on the state of the options specified here. + // Though these are mutable it's extremely rare that they would be mutated past the + // call to initialize the routerware. + internal PipeOptions TransportPipeOptions => _transportPipeOptions ??= new PipeOptions(pauseWriterThreshold: TransportMaxBufferSize, resumeWriterThreshold: TransportMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false); + + internal PipeOptions AppPipeOptions => _appPipeOptions ??= new PipeOptions(pauseWriterThreshold: ApplicationMaxBufferSize, resumeWriterThreshold: ApplicationMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false); } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs index 39e40f865f6f..c7e6b2316fa5 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs @@ -542,7 +542,7 @@ private async Task ExecuteApplication(ConnectionDelegate connectionDelegate) // Jump onto the thread pool thread so blocking user code doesn't block the setup of the // connection and transport - await AwaitableThreadPool.Yield(); + await Task.Yield(); // Running this in an async method turns sync exceptions into async ones await connectionDelegate(this); diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs index 193e149b9e0a..3472ad0251b0 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs @@ -718,8 +718,9 @@ private static void CloneHttpContext(HttpContext context, HttpConnectionContext private HttpConnectionContext CreateConnection(HttpConnectionDispatcherOptions options, int clientProtocolVersion = 0) { - var transportPipeOptions = new PipeOptions(pauseWriterThreshold: options.TransportMaxBufferSize, resumeWriterThreshold: options.TransportMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false); - var appPipeOptions = new PipeOptions(pauseWriterThreshold: options.ApplicationMaxBufferSize, resumeWriterThreshold: options.ApplicationMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false); + var transportPipeOptions = options.TransportPipeOptions!; + var appPipeOptions = options.AppPipeOptions!; + return _manager.CreateConnection(transportPipeOptions, appPipeOptions, clientProtocolVersion); } From 7fcaa5cb820b62f7e48ba4e91d4b871250105bce Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 21 Mar 2021 10:46:43 -0700 Subject: [PATCH 2/2] No longer nullable --- .../Http.Connections/src/Internal/HttpConnectionDispatcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs index 3472ad0251b0..08da6505493c 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs @@ -718,8 +718,8 @@ private static void CloneHttpContext(HttpContext context, HttpConnectionContext private HttpConnectionContext CreateConnection(HttpConnectionDispatcherOptions options, int clientProtocolVersion = 0) { - var transportPipeOptions = options.TransportPipeOptions!; - var appPipeOptions = options.AppPipeOptions!; + var transportPipeOptions = options.TransportPipeOptions; + var appPipeOptions = options.AppPipeOptions; return _manager.CreateConnection(transportPipeOptions, appPipeOptions, clientProtocolVersion); }