diff --git a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj index 333ff3f1017d..ca04b50a3098 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj +++ b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj @@ -13,6 +13,7 @@ + diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs index 3038059d4a17..756f69d7482d 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsMessageParser.cs @@ -15,8 +15,8 @@ internal class ServerSentEventsMessageParser private const byte ByteLF = (byte)'\n'; private const byte ByteColon = (byte)':'; - private static readonly byte[] _dataPrefix = Encoding.UTF8.GetBytes("data: "); - private static readonly byte[] _sseLineEnding = Encoding.UTF8.GetBytes("\r\n"); + private static ReadOnlySpan _dataPrefix => new byte[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' }; + private static ReadOnlySpan _sseLineEnding => new byte[] { (byte)'\r', (byte)'\n' }; private static readonly byte[] _newLine = Encoding.UTF8.GetBytes(Environment.NewLine); private InternalParseState _internalParserState = InternalParseState.ReadMessagePayload; diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs index d473f5c93cc0..b5950ee53603 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs @@ -13,24 +13,25 @@ namespace Microsoft.AspNetCore.Http.Connections { public static class NegotiateProtocol { + // Use C#7.3's ReadOnlySpan optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/ private const string ConnectionIdPropertyName = "connectionId"; - private static readonly byte[] ConnectionIdPropertyNameBytes = Encoding.UTF8.GetBytes(ConnectionIdPropertyName); + private static ReadOnlySpan ConnectionIdPropertyNameBytes => new byte[] { (byte)'c', (byte)'o', (byte)'n', (byte)'n', (byte)'e', (byte)'c', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'I', (byte)'d' }; private const string UrlPropertyName = "url"; - private static readonly byte[] UrlPropertyNameBytes = Encoding.UTF8.GetBytes(UrlPropertyName); + private static ReadOnlySpan UrlPropertyNameBytes => new byte[] { (byte)'u', (byte)'r', (byte)'l' }; private const string AccessTokenPropertyName = "accessToken"; - private static readonly byte[] AccessTokenPropertyNameBytes = Encoding.UTF8.GetBytes(AccessTokenPropertyName); + private static ReadOnlySpan AccessTokenPropertyNameBytes => new byte[] { (byte)'a', (byte)'c', (byte)'c', (byte)'e', (byte)'s', (byte)'s', (byte)'T', (byte)'o', (byte)'k', (byte)'e', (byte)'n' }; private const string AvailableTransportsPropertyName = "availableTransports"; - private static readonly byte[] AvailableTransportsPropertyNameBytes = Encoding.UTF8.GetBytes(AvailableTransportsPropertyName); + private static ReadOnlySpan AvailableTransportsPropertyNameBytes => new byte[] { (byte)'a', (byte)'v', (byte)'a', (byte)'i', (byte)'l', (byte)'a', (byte)'b', (byte)'l', (byte)'e', (byte)'T', (byte)'r', (byte)'a', (byte)'n', (byte)'s', (byte)'p', (byte)'o', (byte)'r', (byte)'t', (byte)'s' }; private const string TransportPropertyName = "transport"; - private static readonly byte[] TransportPropertyNameBytes = Encoding.UTF8.GetBytes(TransportPropertyName); + private static ReadOnlySpan TransportPropertyNameBytes => new byte[] { (byte)'t', (byte)'r', (byte)'a', (byte)'n', (byte)'s', (byte)'p', (byte)'o', (byte)'r', (byte)'t' }; private const string TransferFormatsPropertyName = "transferFormats"; - private static readonly byte[] TransferFormatsPropertyNameBytes = Encoding.UTF8.GetBytes(TransferFormatsPropertyName); + private static ReadOnlySpan TransferFormatsPropertyNameBytes => new byte[] { (byte)'t', (byte)'r', (byte)'a', (byte)'n', (byte)'s', (byte)'f', (byte)'e', (byte)'r', (byte)'F', (byte)'o', (byte)'r', (byte)'m', (byte)'a', (byte)'t', (byte)'s' }; private const string ErrorPropertyName = "error"; - private static readonly byte[] ErrorPropertyNameBytes = Encoding.UTF8.GetBytes(ErrorPropertyName); + private static ReadOnlySpan ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' }; // Used to detect ASP.NET SignalR Server connection attempt private const string ProtocolVersionPropertyName = "ProtocolVersion"; - private static readonly byte[] ProtocolVersionPropertyNameBytes = Encoding.UTF8.GetBytes(ProtocolVersionPropertyName); + private static ReadOnlySpan ProtocolVersionPropertyNameBytes => new byte[] { (byte)'P', (byte)'r', (byte)'o', (byte)'t', (byte)'o', (byte)'c', (byte)'o', (byte)'l', (byte)'V', (byte)'e', (byte)'r', (byte)'s', (byte)'i', (byte)'o', (byte)'n' }; public static void WriteResponse(NegotiationResponse response, IBufferWriter output) { @@ -114,15 +115,15 @@ public static NegotiationResponse ParseResponse(ReadOnlySpan content) if (memberName.SequenceEqual(UrlPropertyNameBytes)) { - url = reader.ReadAsString(UrlPropertyNameBytes); + url = reader.ReadAsString(UrlPropertyName); } else if (memberName.SequenceEqual(AccessTokenPropertyNameBytes)) { - accessToken = reader.ReadAsString(AccessTokenPropertyNameBytes); + accessToken = reader.ReadAsString(AccessTokenPropertyName); } else if (memberName.SequenceEqual(ConnectionIdPropertyNameBytes)) { - connectionId = reader.ReadAsString(ConnectionIdPropertyNameBytes); + connectionId = reader.ReadAsString(ConnectionIdPropertyName); } else if (memberName.SequenceEqual(AvailableTransportsPropertyNameBytes)) { @@ -144,7 +145,7 @@ public static NegotiationResponse ParseResponse(ReadOnlySpan content) } else if (memberName.SequenceEqual(ErrorPropertyNameBytes)) { - error = reader.ReadAsString(ErrorPropertyNameBytes); + error = reader.ReadAsString(ErrorPropertyName); } else if (memberName.SequenceEqual(ProtocolVersionPropertyNameBytes)) { @@ -215,7 +216,7 @@ private static AvailableTransport ParseAvailableTransport(ref Utf8JsonReader rea if (memberName.SequenceEqual(TransportPropertyNameBytes)) { - availableTransport.Transport = reader.ReadAsString(TransportPropertyNameBytes); + availableTransport.Transport = reader.ReadAsString(TransportPropertyName); } else if (memberName.SequenceEqual(TransferFormatsPropertyNameBytes)) { diff --git a/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj b/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj index 42845dcee4d1..c8cbe9569d4c 100644 --- a/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj +++ b/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj @@ -13,6 +13,7 @@ + diff --git a/src/SignalR/common/Shared/PipeWriterStream.cs b/src/SignalR/common/Shared/PipeWriterStream.cs index 10bfa18b9609..c569d09ed2d1 100644 --- a/src/SignalR/common/Shared/PipeWriterStream.cs +++ b/src/SignalR/common/Shared/PipeWriterStream.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; namespace System.IO.Pipelines { diff --git a/src/SignalR/common/Shared/SystemTextJsonExtensions.cs b/src/SignalR/common/Shared/SystemTextJsonExtensions.cs index 68ff75b50eea..5b67925e0859 100644 --- a/src/SignalR/common/Shared/SystemTextJsonExtensions.cs +++ b/src/SignalR/common/Shared/SystemTextJsonExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using System.Text; using System.Text.Json; @@ -70,19 +71,19 @@ public static void Skip(this ref Utf8JsonReader reader) } } - public static string ReadAsString(this ref Utf8JsonReader reader, byte[] propertyName) + public static string ReadAsString(this ref Utf8JsonReader reader, string propertyName) { reader.Read(); if (reader.TokenType != JsonTokenType.String) { - throw new InvalidDataException($"Expected '{Encoding.UTF8.GetString(propertyName)}' to be of type {JsonTokenType.String}."); + throw new InvalidDataException($"Expected '{propertyName}' to be of type {JsonTokenType.String}."); } return reader.GetString(); } - public static int? ReadAsInt32(this ref Utf8JsonReader reader, byte[] propertyName) + public static int? ReadAsInt32(this ref Utf8JsonReader reader, string propertyName) { reader.Read(); @@ -93,7 +94,7 @@ public static string ReadAsString(this ref Utf8JsonReader reader, byte[] propert if (reader.TokenType != JsonTokenType.Number) { - throw new InvalidDataException($"Expected '{Encoding.UTF8.GetString(propertyName)}' to be of type {JsonTokenType.Number}."); + throw new InvalidDataException($"Expected '{propertyName}' to be of type {JsonTokenType.Number}."); } return reader.GetInt32(); diff --git a/src/SignalR/common/Shared/ValueTaskExtensions.cs b/src/SignalR/common/Shared/ValueTaskExtensions.cs new file mode 100644 index 000000000000..0c8c8e4669af --- /dev/null +++ b/src/SignalR/common/Shared/ValueTaskExtensions.cs @@ -0,0 +1,28 @@ +// 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 System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Internal +{ + internal static class ValueTaskExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task GetAsTask(this in ValueTask valueTask) + { + // Try to avoid the allocation from AsTask + if (valueTask.IsCompletedSuccessfully) + { + // Signal consumption to the IValueTaskSource + valueTask.GetAwaiter().GetResult(); + return Task.CompletedTask; + } + else + { + return valueTask.AsTask(); + } + } + } +} diff --git a/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs b/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs index 0b4cd7cefb55..c591757a9a46 100644 --- a/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs +++ b/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs @@ -17,16 +17,17 @@ namespace Microsoft.AspNetCore.SignalR.Protocol /// public static class HandshakeProtocol { + // Use C#7.3's ReadOnlySpan optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/ private const string ProtocolPropertyName = "protocol"; - private static readonly byte[] ProtocolPropertyNameBytes = Encoding.UTF8.GetBytes(ProtocolPropertyName); + private static ReadOnlySpan ProtocolPropertyNameBytes => new byte[] { (byte)'p', (byte)'r', (byte)'o', (byte)'t', (byte)'o', (byte)'c', (byte)'o', (byte)'l' }; private const string ProtocolVersionPropertyName = "version"; - private static readonly byte[] ProtocolVersionPropertyNameBytes = Encoding.UTF8.GetBytes(ProtocolVersionPropertyName); + private static ReadOnlySpan ProtocolVersionPropertyNameBytes => new byte[] { (byte)'v', (byte)'e', (byte)'r', (byte)'s', (byte)'i', (byte)'o', (byte)'n' }; private const string MinorVersionPropertyName = "minorVersion"; - private static readonly byte[] MinorVersionPropertyNameBytes = Encoding.UTF8.GetBytes(MinorVersionPropertyName); + private static ReadOnlySpan MinorVersionPropertyNameBytes => new byte[] { (byte)'m', (byte)'i', (byte)'n', (byte)'o', (byte)'r', (byte)'V', (byte)'e', (byte)'r', (byte)'s', (byte)'i', (byte)'o', (byte)'n' }; private const string ErrorPropertyName = "error"; - private static readonly byte[] ErrorPropertyNameBytes = Encoding.UTF8.GetBytes(ErrorPropertyName); + private static ReadOnlySpan ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' }; private const string TypePropertyName = "type"; - private static readonly byte[] TypePropertyNameBytes = Encoding.UTF8.GetBytes(TypePropertyName); + private static ReadOnlySpan TypePropertyNameBytes => new byte[] { (byte)'t', (byte)'y', (byte)'p', (byte)'e' }; private static ConcurrentDictionary> _messageCache = new ConcurrentDictionary>(); @@ -128,11 +129,11 @@ public static bool TryParseResponseMessage(ref ReadOnlySequence buffer, ou } else if (memberName.SequenceEqual(ErrorPropertyNameBytes)) { - error = reader.ReadAsString(ErrorPropertyNameBytes); + error = reader.ReadAsString(ErrorPropertyName); } else if (memberName.SequenceEqual(MinorVersionPropertyNameBytes)) { - minorVersion = reader.ReadAsInt32(MinorVersionPropertyNameBytes); + minorVersion = reader.ReadAsInt32(MinorVersionPropertyName); } else { @@ -183,11 +184,11 @@ public static bool TryParseRequestMessage(ref ReadOnlySequence buffer, out if (memberName.SequenceEqual(ProtocolPropertyNameBytes)) { - protocol = reader.ReadAsString(ProtocolPropertyNameBytes); + protocol = reader.ReadAsString(ProtocolPropertyName); } else if (memberName.SequenceEqual(ProtocolVersionPropertyNameBytes)) { - protocolVersion = reader.ReadAsInt32(ProtocolVersionPropertyNameBytes); + protocolVersion = reader.ReadAsInt32(ProtocolVersionPropertyName); } else { diff --git a/src/SignalR/server/Core/src/StreamTracker.cs b/src/SignalR/server/Core/src/StreamTracker.cs index a507fde54be1..b9b7b1dd248e 100644 --- a/src/SignalR/server/Core/src/StreamTracker.cs +++ b/src/SignalR/server/Core/src/StreamTracker.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Threading.Channels; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; namespace Microsoft.AspNetCore.SignalR