Reuse Utf8JsonWriter (#9607)

This commit is contained in:
BrennanConroy 2019-04-21 11:49:03 -07:00 committed by GitHub
parent 0303c9e90b
commit c300c8c97c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 208 additions and 108 deletions

View File

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" /> <Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)SystemTextJsonExtensions.cs" Link="Internal\SystemTextJsonExtensions.cs" /> <Compile Include="$(SignalRSharedSourceRoot)SystemTextJsonExtensions.cs" Link="Internal\SystemTextJsonExtensions.cs" />
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="Internal\ReusableUtf8JsonWriter.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -6,7 +6,6 @@ using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Internal;
@ -31,65 +30,73 @@ namespace Microsoft.AspNetCore.Http.Connections
private static ReadOnlySpan<byte> ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' }; private static ReadOnlySpan<byte> ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' };
// Used to detect ASP.NET SignalR Server connection attempt // Used to detect ASP.NET SignalR Server connection attempt
private const string ProtocolVersionPropertyName = "ProtocolVersion";
private static ReadOnlySpan<byte> 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' }; private static ReadOnlySpan<byte> 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<byte> output) public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
{ {
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true }); var reusableWriter = ReusableUtf8JsonWriter.Get(output);
writer.WriteStartObject();
if (!string.IsNullOrEmpty(response.Url)) try
{ {
writer.WriteString(UrlPropertyNameBytes, response.Url); var writer = reusableWriter.GetJsonWriter();
} writer.WriteStartObject();
if (!string.IsNullOrEmpty(response.AccessToken)) if (!string.IsNullOrEmpty(response.Url))
{
writer.WriteString(AccessTokenPropertyNameBytes, response.AccessToken);
}
if (!string.IsNullOrEmpty(response.ConnectionId))
{
writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId);
}
writer.WriteStartArray(AvailableTransportsPropertyNameBytes);
if (response.AvailableTransports != null)
{
foreach (var availableTransport in response.AvailableTransports)
{ {
writer.WriteStartObject(); writer.WriteString(UrlPropertyNameBytes, response.Url);
if (availableTransport.Transport != null)
{
writer.WriteString(TransportPropertyNameBytes, availableTransport.Transport);
}
else
{
// Might be able to remove this after https://github.com/dotnet/corefx/issues/34632 is resolved
writer.WriteNull(TransportPropertyNameBytes);
}
writer.WriteStartArray(TransferFormatsPropertyNameBytes);
if (availableTransport.TransferFormats != null)
{
foreach (var transferFormat in availableTransport.TransferFormats)
{
writer.WriteStringValue(transferFormat);
}
}
writer.WriteEndArray();
writer.WriteEndObject();
} }
if (!string.IsNullOrEmpty(response.AccessToken))
{
writer.WriteString(AccessTokenPropertyNameBytes, response.AccessToken);
}
if (!string.IsNullOrEmpty(response.ConnectionId))
{
writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId);
}
writer.WriteStartArray(AvailableTransportsPropertyNameBytes);
if (response.AvailableTransports != null)
{
foreach (var availableTransport in response.AvailableTransports)
{
writer.WriteStartObject();
if (availableTransport.Transport != null)
{
writer.WriteString(TransportPropertyNameBytes, availableTransport.Transport);
}
else
{
// Might be able to remove this after https://github.com/dotnet/corefx/issues/34632 is resolved
writer.WriteNull(TransportPropertyNameBytes);
}
writer.WriteStartArray(TransferFormatsPropertyNameBytes);
if (availableTransport.TransferFormats != null)
{
foreach (var transferFormat in availableTransport.TransferFormats)
{
writer.WriteStringValue(transferFormat);
}
}
writer.WriteEndArray();
writer.WriteEndObject();
}
}
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
}
finally
{
ReusableUtf8JsonWriter.Return(reusableWriter);
} }
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
} }
public static NegotiationResponse ParseResponse(ReadOnlySpan<byte> content) public static NegotiationResponse ParseResponse(ReadOnlySpan<byte> content)

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>Implements the SignalR Hub Protocol using System.Text.Json.</Description> <Description>Implements the SignalR Hub Protocol using System.Text.Json.</Description>
@ -15,6 +15,7 @@
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" /> <Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" />
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" /> <Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" /> <Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="ReusableUtf8JsonWriter.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -444,49 +444,57 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
private void WriteMessageCore(HubMessage message, IBufferWriter<byte> stream) private void WriteMessageCore(HubMessage message, IBufferWriter<byte> stream)
{ {
using var writer = new Utf8JsonWriter(stream); var reusableWriter = ReusableUtf8JsonWriter.Get(stream);
writer.WriteStartObject(); try
switch (message)
{ {
case InvocationMessage m: var writer = reusableWriter.GetJsonWriter();
WriteMessageType(writer, HubProtocolConstants.InvocationMessageType); writer.WriteStartObject();
WriteHeaders(writer, m); switch (message)
WriteInvocationMessage(m, writer); {
break; case InvocationMessage m:
case StreamInvocationMessage m: WriteMessageType(writer, HubProtocolConstants.InvocationMessageType);
WriteMessageType(writer, HubProtocolConstants.StreamInvocationMessageType); WriteHeaders(writer, m);
WriteHeaders(writer, m); WriteInvocationMessage(m, writer);
WriteStreamInvocationMessage(m, writer); break;
break; case StreamInvocationMessage m:
case StreamItemMessage m: WriteMessageType(writer, HubProtocolConstants.StreamInvocationMessageType);
WriteMessageType(writer, HubProtocolConstants.StreamItemMessageType); WriteHeaders(writer, m);
WriteHeaders(writer, m); WriteStreamInvocationMessage(m, writer);
WriteStreamItemMessage(m, writer); break;
break; case StreamItemMessage m:
case CompletionMessage m: WriteMessageType(writer, HubProtocolConstants.StreamItemMessageType);
WriteMessageType(writer, HubProtocolConstants.CompletionMessageType); WriteHeaders(writer, m);
WriteHeaders(writer, m); WriteStreamItemMessage(m, writer);
WriteCompletionMessage(m, writer); break;
break; case CompletionMessage m:
case CancelInvocationMessage m: WriteMessageType(writer, HubProtocolConstants.CompletionMessageType);
WriteMessageType(writer, HubProtocolConstants.CancelInvocationMessageType); WriteHeaders(writer, m);
WriteHeaders(writer, m); WriteCompletionMessage(m, writer);
WriteCancelInvocationMessage(m, writer); break;
break; case CancelInvocationMessage m:
case PingMessage _: WriteMessageType(writer, HubProtocolConstants.CancelInvocationMessageType);
WriteMessageType(writer, HubProtocolConstants.PingMessageType); WriteHeaders(writer, m);
break; WriteCancelInvocationMessage(m, writer);
case CloseMessage m: break;
WriteMessageType(writer, HubProtocolConstants.CloseMessageType); case PingMessage _:
WriteCloseMessage(m, writer); WriteMessageType(writer, HubProtocolConstants.PingMessageType);
break; break;
default: case CloseMessage m:
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}"); WriteMessageType(writer, HubProtocolConstants.CloseMessageType);
WriteCloseMessage(m, writer);
break;
default:
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}");
}
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
}
finally
{
ReusableUtf8JsonWriter.Return(reusableWriter);
} }
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
} }
private void WriteHeaders(Utf8JsonWriter writer, HubInvocationMessage message) private void WriteHeaders(Utf8JsonWriter writer, HubInvocationMessage message)

View File

@ -0,0 +1,64 @@
// 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.Text.Json;
namespace Microsoft.AspNetCore.Internal
{
internal sealed class ReusableUtf8JsonWriter
{
[ThreadStatic]
private static ReusableUtf8JsonWriter _cachedInstance;
private readonly Utf8JsonWriter _writer;
#if DEBUG
private bool _inUse;
#endif
public ReusableUtf8JsonWriter(IBufferWriter<byte> stream)
{
_writer = new Utf8JsonWriter(stream, new JsonWriterOptions() { SkipValidation = true });
}
public static ReusableUtf8JsonWriter Get(IBufferWriter<byte> stream)
{
var writer = _cachedInstance;
if (writer == null)
{
writer = new ReusableUtf8JsonWriter(stream);
}
// Taken off the thread static
_cachedInstance = null;
#if DEBUG
if (writer._inUse)
{
throw new InvalidOperationException("The writer wasn't returned!");
}
writer._inUse = true;
#endif
writer._writer.Reset(stream);
return writer;
}
public static void Return(ReusableUtf8JsonWriter writer)
{
_cachedInstance = writer;
writer._writer.Reset();
#if DEBUG
writer._inUse = false;
#endif
}
public Utf8JsonWriter GetJsonWriter()
{
return _writer;
}
}
}

View File

@ -16,6 +16,7 @@
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="Internal\TextMessageParser.cs" /> <Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="Internal\TextMessageParser.cs" />
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" /> <Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" />
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" /> <Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="Internal\ReusableUtf8JsonWriter.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -60,14 +60,23 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
/// <param name="output">The output writer.</param> /// <param name="output">The output writer.</param>
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output) public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
{ {
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true }); var reusableWriter = ReusableUtf8JsonWriter.Get(output);
writer.WriteStartObject(); try
writer.WriteString(ProtocolPropertyNameBytes, requestMessage.Protocol); {
writer.WriteNumber(ProtocolVersionPropertyNameBytes, requestMessage.Version); var writer = reusableWriter.GetJsonWriter();
writer.WriteEndObject();
writer.Flush(); writer.WriteStartObject();
Debug.Assert(writer.CurrentDepth == 0); writer.WriteString(ProtocolPropertyNameBytes, requestMessage.Protocol);
writer.WriteNumber(ProtocolVersionPropertyNameBytes, requestMessage.Version);
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
}
finally
{
ReusableUtf8JsonWriter.Return(reusableWriter);
}
TextMessageFormatter.WriteRecordSeparator(output); TextMessageFormatter.WriteRecordSeparator(output);
} }
@ -79,19 +88,28 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
/// <param name="output">The output writer.</param> /// <param name="output">The output writer.</param>
public static void WriteResponseMessage(HandshakeResponseMessage responseMessage, IBufferWriter<byte> output) public static void WriteResponseMessage(HandshakeResponseMessage responseMessage, IBufferWriter<byte> output)
{ {
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true }); var reusableWriter = ReusableUtf8JsonWriter.Get(output);
writer.WriteStartObject(); try
if (!string.IsNullOrEmpty(responseMessage.Error))
{ {
writer.WriteString(ErrorPropertyNameBytes, responseMessage.Error); var writer = reusableWriter.GetJsonWriter();
writer.WriteStartObject();
if (!string.IsNullOrEmpty(responseMessage.Error))
{
writer.WriteString(ErrorPropertyNameBytes, responseMessage.Error);
}
writer.WriteNumber(MinorVersionPropertyNameBytes, responseMessage.MinorVersion);
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
}
finally
{
ReusableUtf8JsonWriter.Return(reusableWriter);
} }
writer.WriteNumber(MinorVersionPropertyNameBytes, responseMessage.MinorVersion);
writer.WriteEndObject();
writer.Flush();
Debug.Assert(writer.CurrentDepth == 0);
TextMessageFormatter.WriteRecordSeparator(output); TextMessageFormatter.WriteRecordSeparator(output);
} }