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