Skip to content

Commit c300c8c

Browse files
Reuse Utf8JsonWriter (#9607)
1 parent 0303c9e commit c300c8c

File tree

7 files changed

+201
-101
lines changed

7 files changed

+201
-101
lines changed

src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<ItemGroup>
1313
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
1414
<Compile Include="$(SignalRSharedSourceRoot)SystemTextJsonExtensions.cs" Link="Internal\SystemTextJsonExtensions.cs" />
15+
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="Internal\ReusableUtf8JsonWriter.cs" />
1516
</ItemGroup>
1617

1718
<ItemGroup>

src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.IO;
9-
using System.Text;
109
using System.Text.Json;
1110
using Microsoft.AspNetCore.Internal;
1211

@@ -31,65 +30,73 @@ public static class NegotiateProtocol
3130
private static ReadOnlySpan<byte> ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' };
3231

3332
// Used to detect ASP.NET SignalR Server connection attempt
34-
private const string ProtocolVersionPropertyName = "ProtocolVersion";
3533
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' };
3634

3735
public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
3836
{
39-
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true });
40-
writer.WriteStartObject();
37+
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
4138

42-
if (!string.IsNullOrEmpty(response.Url))
39+
try
4340
{
44-
writer.WriteString(UrlPropertyNameBytes, response.Url);
45-
}
41+
var writer = reusableWriter.GetJsonWriter();
42+
writer.WriteStartObject();
4643

47-
if (!string.IsNullOrEmpty(response.AccessToken))
48-
{
49-
writer.WriteString(AccessTokenPropertyNameBytes, response.AccessToken);
50-
}
44+
if (!string.IsNullOrEmpty(response.Url))
45+
{
46+
writer.WriteString(UrlPropertyNameBytes, response.Url);
47+
}
5148

52-
if (!string.IsNullOrEmpty(response.ConnectionId))
53-
{
54-
writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId);
55-
}
49+
if (!string.IsNullOrEmpty(response.AccessToken))
50+
{
51+
writer.WriteString(AccessTokenPropertyNameBytes, response.AccessToken);
52+
}
53+
54+
if (!string.IsNullOrEmpty(response.ConnectionId))
55+
{
56+
writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId);
57+
}
5658

57-
writer.WriteStartArray(AvailableTransportsPropertyNameBytes);
59+
writer.WriteStartArray(AvailableTransportsPropertyNameBytes);
5860

59-
if (response.AvailableTransports != null)
60-
{
61-
foreach (var availableTransport in response.AvailableTransports)
61+
if (response.AvailableTransports != null)
6262
{
63-
writer.WriteStartObject();
64-
if (availableTransport.Transport != null)
65-
{
66-
writer.WriteString(TransportPropertyNameBytes, availableTransport.Transport);
67-
}
68-
else
63+
foreach (var availableTransport in response.AvailableTransports)
6964
{
70-
// Might be able to remove this after https://github.com/dotnet/corefx/issues/34632 is resolved
71-
writer.WriteNull(TransportPropertyNameBytes);
72-
}
73-
writer.WriteStartArray(TransferFormatsPropertyNameBytes);
65+
writer.WriteStartObject();
66+
if (availableTransport.Transport != null)
67+
{
68+
writer.WriteString(TransportPropertyNameBytes, availableTransport.Transport);
69+
}
70+
else
71+
{
72+
// Might be able to remove this after https://github.com/dotnet/corefx/issues/34632 is resolved
73+
writer.WriteNull(TransportPropertyNameBytes);
74+
}
75+
writer.WriteStartArray(TransferFormatsPropertyNameBytes);
7476

75-
if (availableTransport.TransferFormats != null)
76-
{
77-
foreach (var transferFormat in availableTransport.TransferFormats)
77+
if (availableTransport.TransferFormats != null)
7878
{
79-
writer.WriteStringValue(transferFormat);
79+
foreach (var transferFormat in availableTransport.TransferFormats)
80+
{
81+
writer.WriteStringValue(transferFormat);
82+
}
8083
}
81-
}
8284

83-
writer.WriteEndArray();
84-
writer.WriteEndObject();
85+
writer.WriteEndArray();
86+
writer.WriteEndObject();
87+
}
8588
}
86-
}
8789

88-
writer.WriteEndArray();
89-
writer.WriteEndObject();
90+
writer.WriteEndArray();
91+
writer.WriteEndObject();
9092

91-
writer.Flush();
92-
Debug.Assert(writer.CurrentDepth == 0);
93+
writer.Flush();
94+
Debug.Assert(writer.CurrentDepth == 0);
95+
}
96+
finally
97+
{
98+
ReusableUtf8JsonWriter.Return(reusableWriter);
99+
}
93100
}
94101

95102
public static NegotiationResponse ParseResponse(ReadOnlySpan<byte> content)

src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>Implements the SignalR Hub Protocol using System.Text.Json.</Description>
@@ -15,6 +15,7 @@
1515
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" />
1616
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
1717
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
18+
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="ReusableUtf8JsonWriter.cs" />
1819
</ItemGroup>
1920

2021
<ItemGroup>

src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -444,49 +444,57 @@ private Dictionary<string, string> ReadHeaders(ref Utf8JsonReader reader)
444444

445445
private void WriteMessageCore(HubMessage message, IBufferWriter<byte> stream)
446446
{
447-
using var writer = new Utf8JsonWriter(stream);
448-
449-
writer.WriteStartObject();
450-
switch (message)
451-
{
452-
case InvocationMessage m:
453-
WriteMessageType(writer, HubProtocolConstants.InvocationMessageType);
454-
WriteHeaders(writer, m);
455-
WriteInvocationMessage(m, writer);
456-
break;
457-
case StreamInvocationMessage m:
458-
WriteMessageType(writer, HubProtocolConstants.StreamInvocationMessageType);
459-
WriteHeaders(writer, m);
460-
WriteStreamInvocationMessage(m, writer);
461-
break;
462-
case StreamItemMessage m:
463-
WriteMessageType(writer, HubProtocolConstants.StreamItemMessageType);
464-
WriteHeaders(writer, m);
465-
WriteStreamItemMessage(m, writer);
466-
break;
467-
case CompletionMessage m:
468-
WriteMessageType(writer, HubProtocolConstants.CompletionMessageType);
469-
WriteHeaders(writer, m);
470-
WriteCompletionMessage(m, writer);
471-
break;
472-
case CancelInvocationMessage m:
473-
WriteMessageType(writer, HubProtocolConstants.CancelInvocationMessageType);
474-
WriteHeaders(writer, m);
475-
WriteCancelInvocationMessage(m, writer);
476-
break;
477-
case PingMessage _:
478-
WriteMessageType(writer, HubProtocolConstants.PingMessageType);
479-
break;
480-
case CloseMessage m:
481-
WriteMessageType(writer, HubProtocolConstants.CloseMessageType);
482-
WriteCloseMessage(m, writer);
483-
break;
484-
default:
485-
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}");
486-
}
487-
writer.WriteEndObject();
488-
writer.Flush();
489-
Debug.Assert(writer.CurrentDepth == 0);
447+
var reusableWriter = ReusableUtf8JsonWriter.Get(stream);
448+
449+
try
450+
{
451+
var writer = reusableWriter.GetJsonWriter();
452+
writer.WriteStartObject();
453+
switch (message)
454+
{
455+
case InvocationMessage m:
456+
WriteMessageType(writer, HubProtocolConstants.InvocationMessageType);
457+
WriteHeaders(writer, m);
458+
WriteInvocationMessage(m, writer);
459+
break;
460+
case StreamInvocationMessage m:
461+
WriteMessageType(writer, HubProtocolConstants.StreamInvocationMessageType);
462+
WriteHeaders(writer, m);
463+
WriteStreamInvocationMessage(m, writer);
464+
break;
465+
case StreamItemMessage m:
466+
WriteMessageType(writer, HubProtocolConstants.StreamItemMessageType);
467+
WriteHeaders(writer, m);
468+
WriteStreamItemMessage(m, writer);
469+
break;
470+
case CompletionMessage m:
471+
WriteMessageType(writer, HubProtocolConstants.CompletionMessageType);
472+
WriteHeaders(writer, m);
473+
WriteCompletionMessage(m, writer);
474+
break;
475+
case CancelInvocationMessage m:
476+
WriteMessageType(writer, HubProtocolConstants.CancelInvocationMessageType);
477+
WriteHeaders(writer, m);
478+
WriteCancelInvocationMessage(m, writer);
479+
break;
480+
case PingMessage _:
481+
WriteMessageType(writer, HubProtocolConstants.PingMessageType);
482+
break;
483+
case CloseMessage m:
484+
WriteMessageType(writer, HubProtocolConstants.CloseMessageType);
485+
WriteCloseMessage(m, writer);
486+
break;
487+
default:
488+
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}");
489+
}
490+
writer.WriteEndObject();
491+
writer.Flush();
492+
Debug.Assert(writer.CurrentDepth == 0);
493+
}
494+
finally
495+
{
496+
ReusableUtf8JsonWriter.Return(reusableWriter);
497+
}
490498
}
491499

492500
private void WriteHeaders(Utf8JsonWriter writer, HubInvocationMessage message)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Text.Json;
7+
8+
namespace Microsoft.AspNetCore.Internal
9+
{
10+
internal sealed class ReusableUtf8JsonWriter
11+
{
12+
[ThreadStatic]
13+
private static ReusableUtf8JsonWriter _cachedInstance;
14+
15+
private readonly Utf8JsonWriter _writer;
16+
17+
#if DEBUG
18+
private bool _inUse;
19+
#endif
20+
21+
public ReusableUtf8JsonWriter(IBufferWriter<byte> stream)
22+
{
23+
_writer = new Utf8JsonWriter(stream, new JsonWriterOptions() { SkipValidation = true });
24+
}
25+
26+
public static ReusableUtf8JsonWriter Get(IBufferWriter<byte> stream)
27+
{
28+
var writer = _cachedInstance;
29+
if (writer == null)
30+
{
31+
writer = new ReusableUtf8JsonWriter(stream);
32+
}
33+
34+
// Taken off the thread static
35+
_cachedInstance = null;
36+
#if DEBUG
37+
if (writer._inUse)
38+
{
39+
throw new InvalidOperationException("The writer wasn't returned!");
40+
}
41+
42+
writer._inUse = true;
43+
#endif
44+
writer._writer.Reset(stream);
45+
return writer;
46+
}
47+
48+
public static void Return(ReusableUtf8JsonWriter writer)
49+
{
50+
_cachedInstance = writer;
51+
52+
writer._writer.Reset();
53+
54+
#if DEBUG
55+
writer._inUse = false;
56+
#endif
57+
}
58+
59+
public Utf8JsonWriter GetJsonWriter()
60+
{
61+
return _writer;
62+
}
63+
}
64+
}

src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="Internal\TextMessageParser.cs" />
1717
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" />
1818
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
19+
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="Internal\ReusableUtf8JsonWriter.cs" />
1920
</ItemGroup>
2021

2122
<ItemGroup>

src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,23 @@ public static ReadOnlySpan<byte> GetSuccessfulHandshake(IHubProtocol protocol)
6060
/// <param name="output">The output writer.</param>
6161
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
6262
{
63-
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true });
63+
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
6464

65-
writer.WriteStartObject();
66-
writer.WriteString(ProtocolPropertyNameBytes, requestMessage.Protocol);
67-
writer.WriteNumber(ProtocolVersionPropertyNameBytes, requestMessage.Version);
68-
writer.WriteEndObject();
69-
writer.Flush();
70-
Debug.Assert(writer.CurrentDepth == 0);
65+
try
66+
{
67+
var writer = reusableWriter.GetJsonWriter();
68+
69+
writer.WriteStartObject();
70+
writer.WriteString(ProtocolPropertyNameBytes, requestMessage.Protocol);
71+
writer.WriteNumber(ProtocolVersionPropertyNameBytes, requestMessage.Version);
72+
writer.WriteEndObject();
73+
writer.Flush();
74+
Debug.Assert(writer.CurrentDepth == 0);
75+
}
76+
finally
77+
{
78+
ReusableUtf8JsonWriter.Return(reusableWriter);
79+
}
7180

7281
TextMessageFormatter.WriteRecordSeparator(output);
7382
}
@@ -79,19 +88,28 @@ public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, I
7988
/// <param name="output">The output writer.</param>
8089
public static void WriteResponseMessage(HandshakeResponseMessage responseMessage, IBufferWriter<byte> output)
8190
{
82-
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true });
91+
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
8392

84-
writer.WriteStartObject();
85-
if (!string.IsNullOrEmpty(responseMessage.Error))
93+
try
8694
{
87-
writer.WriteString(ErrorPropertyNameBytes, responseMessage.Error);
88-
}
95+
var writer = reusableWriter.GetJsonWriter();
8996

90-
writer.WriteNumber(MinorVersionPropertyNameBytes, responseMessage.MinorVersion);
97+
writer.WriteStartObject();
98+
if (!string.IsNullOrEmpty(responseMessage.Error))
99+
{
100+
writer.WriteString(ErrorPropertyNameBytes, responseMessage.Error);
101+
}
102+
103+
writer.WriteNumber(MinorVersionPropertyNameBytes, responseMessage.MinorVersion);
91104

92-
writer.WriteEndObject();
93-
writer.Flush();
94-
Debug.Assert(writer.CurrentDepth == 0);
105+
writer.WriteEndObject();
106+
writer.Flush();
107+
Debug.Assert(writer.CurrentDepth == 0);
108+
}
109+
finally
110+
{
111+
ReusableUtf8JsonWriter.Return(reusableWriter);
112+
}
95113

96114
TextMessageFormatter.WriteRecordSeparator(output);
97115
}

0 commit comments

Comments
 (0)