Serialize NegotiateResponse with IBufferWriter (#1881)

This commit is contained in:
James Newton-King 2018-04-07 15:50:13 +12:00 committed by GitHub
parent f632330d7f
commit acc0b7ad0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 331 additions and 182 deletions

View File

@ -11,6 +11,7 @@ using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Core;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
using Microsoft.Extensions.Logging.Abstractions;

View File

@ -9,7 +9,7 @@ using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
using Microsoft.Extensions.DependencyInjection;

View File

@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
using Microsoft.Extensions.DependencyInjection;

View File

@ -2,7 +2,7 @@ using System;
using System.Buffers;
using System.IO;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks

View File

@ -0,0 +1,55 @@
// 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.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Internal;
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
{
public class NegotiateProtocolBenchmark
{
private NegotiationResponse _negotiateResponse;
private Stream _stream;
[GlobalSetup]
public void GlobalSetup()
{
_negotiateResponse = new NegotiationResponse
{
ConnectionId = "d100338e-8c01-4281-92c2-9a967fdeebcb",
AvailableTransports = new List<AvailableTransport>
{
new AvailableTransport
{
Transport = "WebSockets",
TransferFormats = new List<string>
{
"Text",
"Binary"
}
}
}
};
_stream = Stream.Null;
}
[Benchmark]
public Task WriteResponse_MemoryBufferWriter()
{
var writer = new MemoryBufferWriter();
try
{
NegotiateProtocol.WriteResponse(_negotiateResponse, writer);
return writer.CopyToAsync(_stream);
}
finally
{
writer.Reset();
}
}
}
}

View File

@ -0,0 +1,174 @@
// 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.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Internal
{
internal sealed class MemoryBufferWriter : IBufferWriter<byte>
{
[ThreadStatic]
private static MemoryBufferWriter _cachedInstance;
#if DEBUG
private bool _inUse;
#endif
private readonly int _segmentSize;
private int _bytesWritten;
private List<byte[]> _fullSegments;
private byte[] _currentSegment;
private int _position;
public MemoryBufferWriter(int segmentSize = 2048)
{
_segmentSize = segmentSize;
}
public int Length => _bytesWritten;
public static MemoryBufferWriter Get()
{
var writer = _cachedInstance;
if (writer == null)
{
writer = new MemoryBufferWriter();
}
// Taken off the thread static
_cachedInstance = null;
#if DEBUG
if (writer._inUse)
{
throw new InvalidOperationException("The reader wasn't returned!");
}
writer._inUse = true;
#endif
return writer;
}
public static void Return(MemoryBufferWriter writer)
{
_cachedInstance = writer;
#if DEBUG
writer._inUse = false;
#endif
writer.Reset();
}
public void Reset()
{
if (_fullSegments != null)
{
for (var i = 0; i < _fullSegments.Count; i++)
{
ArrayPool<byte>.Shared.Return(_fullSegments[i]);
}
_fullSegments.Clear();
}
if (_currentSegment != null)
{
ArrayPool<byte>.Shared.Return(_currentSegment);
_currentSegment = null;
}
_bytesWritten = 0;
_position = 0;
}
public void Advance(int count)
{
_bytesWritten += count;
_position += count;
}
public Memory<byte> GetMemory(int sizeHint = 0)
{
// TODO: Use sizeHint
if (_currentSegment == null)
{
_currentSegment = ArrayPool<byte>.Shared.Rent(_segmentSize);
_position = 0;
}
else if (_position == _segmentSize)
{
if (_fullSegments == null)
{
_fullSegments = new List<byte[]>();
}
_fullSegments.Add(_currentSegment);
_currentSegment = ArrayPool<byte>.Shared.Rent(_segmentSize);
_position = 0;
}
return _currentSegment.AsMemory(_position, _currentSegment.Length - _position);
}
public Span<byte> GetSpan(int sizeHint = 0)
{
return GetMemory(sizeHint).Span;
}
public Task CopyToAsync(Stream stream)
{
if (_fullSegments == null)
{
// There is only one segment so write without async
return stream.WriteAsync(_currentSegment, 0, _position);
}
return CopyToSlowAsync(stream);
}
private async Task CopyToSlowAsync(Stream stream)
{
if (_fullSegments != null)
{
// Copy full segments
for (var i = 0; i < _fullSegments.Count - 1; i++)
{
await stream.WriteAsync(_fullSegments[i], 0, _segmentSize);
}
}
await stream.WriteAsync(_currentSegment, 0, _position);
}
public byte[] ToArray()
{
if (_currentSegment == null)
{
return Array.Empty<byte>();
}
var result = new byte[_bytesWritten];
var totalWritten = 0;
if (_fullSegments != null)
{
// Copy full segments
for (var i = 0; i < _fullSegments.Count; i++)
{
_fullSegments[i].CopyTo(result, totalWritten);
totalWritten += _segmentSize;
}
}
// Copy current incomplete segment
_currentSegment.AsSpan(0, _position).CopyTo(result.AsSpan(totalWritten));
return result;
}
}
}

View File

@ -9,7 +9,7 @@ using System.Text;
namespace Microsoft.AspNetCore.SignalR.Internal
{
public class Utf8BufferTextReader : TextReader
internal sealed class Utf8BufferTextReader : TextReader
{
private readonly Decoder _decoder;
private ReadOnlySequence<byte> _utf8Buffer;

View File

@ -9,9 +9,9 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.AspNetCore.SignalR.Internal
namespace Microsoft.AspNetCore.Internal
{
public sealed class Utf8BufferTextWriter : TextWriter
internal sealed class Utf8BufferTextWriter : TextWriter
{
private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

View File

@ -2,6 +2,7 @@
// 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.Collections.Generic;
using System.IO;
using System.Text;
@ -12,44 +13,50 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public static class NegotiateProtocol
{
private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
private const string ConnectionIdPropertyName = "connectionId";
private const string AvailableTransportsPropertyName = "availableTransports";
private const string TransportPropertyName = "transport";
private const string TransferFormatsPropertyName = "transferFormats";
public static void WriteResponse(NegotiationResponse response, Stream output)
public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
{
using (var jsonWriter = JsonUtils.CreateJsonTextWriter(new StreamWriter(output, _utf8NoBom, 1024, leaveOpen: true)))
var textWriter = Utf8BufferTextWriter.Get(output);
try
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName(ConnectionIdPropertyName);
jsonWriter.WriteValue(response.ConnectionId);
jsonWriter.WritePropertyName(AvailableTransportsPropertyName);
jsonWriter.WriteStartArray();
foreach (var availableTransport in response.AvailableTransports)
using (var jsonWriter = JsonUtils.CreateJsonTextWriter(textWriter))
{
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName(TransportPropertyName);
jsonWriter.WriteValue(availableTransport.Transport);
jsonWriter.WritePropertyName(TransferFormatsPropertyName);
jsonWriter.WritePropertyName(ConnectionIdPropertyName);
jsonWriter.WriteValue(response.ConnectionId);
jsonWriter.WritePropertyName(AvailableTransportsPropertyName);
jsonWriter.WriteStartArray();
foreach (var transferFormat in availableTransport.TransferFormats)
foreach (var availableTransport in response.AvailableTransports)
{
jsonWriter.WriteValue(transferFormat);
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName(TransportPropertyName);
jsonWriter.WriteValue(availableTransport.Transport);
jsonWriter.WritePropertyName(TransferFormatsPropertyName);
jsonWriter.WriteStartArray();
foreach (var transferFormat in availableTransport.TransferFormats)
{
jsonWriter.WriteValue(transferFormat);
}
jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
jsonWriter.Flush();
}
jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
jsonWriter.Flush();
}
finally
{
Utf8BufferTextWriter.Return(textWriter);
}
}

View File

@ -4,10 +4,12 @@
<Description>Common primitives for ASP.NET Connection Handlers and clients</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Microsoft.AspNetCore.Http.Connections</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
<Compile Include="..\Common\Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="$(MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion)" />

View File

@ -2,6 +2,7 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.IO;
@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
@ -373,7 +375,7 @@ namespace Microsoft.AspNetCore.Http.Connections
await connectionDelegate(connection);
}
private Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope)
private async Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope)
{
context.Response.ContentType = "application/json";
@ -384,17 +386,27 @@ namespace Microsoft.AspNetCore.Http.Connections
// Connection ID metadata set.
logScope.ConnectionId = connection.ConnectionId;
// Get the bytes for the connection id
var negotiateResponseBuffer = GetNegotiatePayload(connection.ConnectionId, context, options);
// Don't use thread static instance here because writer is used with async
var writer = new MemoryBufferWriter();
Log.NegotiationRequest(_logger);
try
{
// Get the bytes for the connection id
WriteNegotiatePayload(writer, connection.ConnectionId, context, options);
// Write it out to the response with the right content length
context.Response.ContentLength = negotiateResponseBuffer.Length;
return context.Response.Body.WriteAsync(negotiateResponseBuffer, 0, negotiateResponseBuffer.Length);
Log.NegotiationRequest(_logger);
// Write it out to the response with the right content length
context.Response.ContentLength = writer.Length;
await writer.CopyToAsync(context.Response.Body);
}
finally
{
writer.Reset();
}
}
private static byte[] GetNegotiatePayload(string connectionId, HttpContext context, HttpConnectionOptions options)
private static void WriteNegotiatePayload(IBufferWriter<byte> writer, string connectionId, HttpContext context, HttpConnectionOptions options)
{
var response = new NegotiationResponse();
response.ConnectionId = connectionId;
@ -415,10 +427,7 @@ namespace Microsoft.AspNetCore.Http.Connections
response.AvailableTransports.Add(_longPollingAvailableTransport);
}
var ms = new MemoryStream();
NegotiateProtocol.WriteResponse(response, ms);
return ms.ToArray();
NegotiateProtocol.WriteResponse(response, writer);
}
private static bool ServerHasWebSockets(IFeatureCollection features)

View File

@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\MemoryBufferWriter.cs" Link="MemoryBufferWriter.cs" />
<Compile Include="..\Common\PipeWriterStream.cs" Link="PipeWriterStream.cs" />
<Compile Include="..\Common\WebSocketExtensions.cs" Link="WebSocketExtensions.cs" />
<Compile Include="..\Common\StreamExtensions.cs" Link="StreamExtensions.cs" />

View File

@ -1,122 +0,0 @@
// 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.Collections.Generic;
namespace Microsoft.AspNetCore.SignalR.Internal
{
public sealed class MemoryBufferWriter : IBufferWriter<byte>
{
[ThreadStatic]
private static MemoryBufferWriter _cachedInstance;
#if DEBUG
private bool _inUse;
#endif
private readonly int _segmentSize;
private int _bytesWritten;
private List<byte[]> _segments;
private int _position;
private MemoryBufferWriter(int segmentSize = 2048)
{
_segmentSize = segmentSize;
_segments = new List<byte[]>();
}
public static MemoryBufferWriter Get()
{
var writer = _cachedInstance;
if (writer == null)
{
writer = new MemoryBufferWriter();
}
// Taken off the thread static
_cachedInstance = null;
#if DEBUG
if (writer._inUse)
{
throw new InvalidOperationException("The reader wasn't returned!");
}
writer._inUse = true;
#endif
return writer;
}
public static void Return(MemoryBufferWriter writer)
{
_cachedInstance = writer;
#if DEBUG
writer._inUse = false;
#endif
for (var i = 0; i < writer._segments.Count; i++)
{
ArrayPool<byte>.Shared.Return(writer._segments[i]);
}
writer._segments.Clear();
writer._bytesWritten = 0;
writer._position = 0;
}
public Memory<byte> CurrentSegment => _segments[_segments.Count - 1];
public void Advance(int count)
{
_bytesWritten += count;
_position += count;
}
public Memory<byte> GetMemory(int sizeHint = 0)
{
// TODO: Use sizeHint
if (_segments.Count == 0 || _position == _segmentSize)
{
_segments.Add(ArrayPool<byte>.Shared.Rent(_segmentSize));
_position = 0;
}
// Cache property access
var currentSegment = CurrentSegment;
return currentSegment.Slice(_position, currentSegment.Length - _position);
}
public Span<byte> GetSpan(int sizeHint = 0)
{
return GetMemory(sizeHint).Span;
}
public byte[] ToArray()
{
if (_segments.Count == 0)
{
return Array.Empty<byte>();
}
var result = new byte[_bytesWritten];
var totalWritten = 0;
// Copy full segments
for (var i = 0; i < _segments.Count - 1; i++)
{
_segments[i].AsMemory().CopyTo(result.AsMemory(totalWritten, _segmentSize));
totalWritten += _segmentSize;
}
// Copy current incomplete segment
CurrentSegment.Slice(0, _position).CopyTo(result.AsMemory(totalWritten, _position));
return result;
}
}
}

View File

@ -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.Buffers;
using System.IO;
using Microsoft.AspNetCore.Internal;
@ -16,6 +17,22 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
private const string ErrorPropertyName = "error";
private const string TypePropertyName = "type";
public static ReadOnlyMemory<byte> SuccessHandshakeData { get; }
static HandshakeProtocol()
{
var memoryBufferWriter = MemoryBufferWriter.Get();
try
{
WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter);
SuccessHandshakeData = memoryBufferWriter.ToArray();
}
finally
{
MemoryBufferWriter.Return(memoryBufferWriter);
}
}
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
{
var textWriter = Utf8BufferTextWriter.Get(output);

View File

@ -1,6 +1,8 @@
// 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 Microsoft.AspNetCore.Internal;
namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
{
public static class HubProtocolExtensions

View File

@ -9,6 +9,9 @@
<ItemGroup>
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
<Compile Include="..\Common\MemoryBufferWriter.cs" Link="Internal\MemoryBufferWriter.cs" />
<Compile Include="..\Common\Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" />
<Compile Include="..\Common\Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -14,6 +14,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Core;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
@ -24,7 +25,6 @@ namespace Microsoft.AspNetCore.SignalR
public class HubConnectionContext
{
private static readonly Action<object> _abortedCallback = AbortConnection;
private static readonly byte[] _successHandshakeResponseData;
private readonly ConnectionContext _connectionContext;
private readonly ILogger _logger;
@ -36,20 +36,6 @@ namespace Microsoft.AspNetCore.SignalR
private long _lastSendTimestamp = Stopwatch.GetTimestamp();
private byte[] _cachedPingMessage;
static HubConnectionContext()
{
var memoryBufferWriter = MemoryBufferWriter.Get();
try
{
HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter);
_successHandshakeResponseData = memoryBufferWriter.ToArray();
}
finally
{
MemoryBufferWriter.Return(memoryBufferWriter);
}
}
public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory)
{
_connectionContext = connectionContext;
@ -264,7 +250,7 @@ namespace Microsoft.AspNetCore.SignalR
if (message == HandshakeResponseMessage.Empty)
{
// success response is always an empty object so send cached data
_connectionContext.Transport.Output.Write(_successHandshakeResponseData);
_connectionContext.Transport.Output.Write(HandshakeProtocol.SuccessHandshakeData.Span);
}
else
{

View File

@ -4,10 +4,13 @@
<Description>Implements the SignalR Hub Protocol over JSON.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
<Compile Include="..\Common\Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
<Compile Include="..\Common\Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -4,6 +4,10 @@
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Common\MemoryBufferWriter.cs" Link="MemoryBufferWriter.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="..\xunit.runner.json" Link="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@ -10,7 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Newtonsoft.Json;
@ -75,17 +75,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
var s = await ReadSentTextMessageAsync();
byte[] response;
var output = MemoryBufferWriter.Get();
try
{
HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, output);
await Application.Output.WriteAsync(output.ToArray());
response = output.ToArray();
}
finally
{
MemoryBufferWriter.Return(output);
}
await Application.Output.WriteAsync(response);
return s;
}

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
using Xunit;

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.Extensions.Options;
@ -16,7 +17,6 @@ using Xunit;
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{
using Microsoft.AspNetCore.SignalR.Internal;
using static HubMessageHelpers;
public class JsonHubProtocolTests

View File

@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using MsgPack;

View File

@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal;
using Xunit;

View File

@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{
public class TestClient : ITransferFormatFeature, IConnectionHeartbeatFeature, IDisposable
{
private object _heartbeatLock = new object();
private readonly object _heartbeatLock = new object();
private List<(Action<object> handler, object state)> _heartbeatHandlers;
private static int _id;