Add GetMessageBytes to IHubProtocol (#1915)

This commit is contained in:
Ben Adams 2018-04-10 15:14:09 +01:00 committed by David Fowler
parent 31dfe91962
commit 8a3516284e
16 changed files with 298 additions and 14 deletions

View File

@ -69,6 +69,11 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
public void WriteMessage(HubMessage message, IBufferWriter<byte> output) public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
{ {
} }
public byte[] GetMessageBytes(HubMessage message)
{
return HubProtocolExtensions.GetMessageBytes(this, message);
}
} }
public class NoErrorHubConnectionContext : HubConnectionContext public class NoErrorHubConnectionContext : HubConnectionContext

View File

@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
break; break;
} }
_binaryInput = _hubProtocol.WriteToArray(_hubMessage); _binaryInput = _hubProtocol.GetMessageBytes(_hubMessage);
_binder = new TestBinder(_hubMessage); _binder = new TestBinder(_hubMessage);
} }
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
[Benchmark] [Benchmark]
public void WriteSingleMessage() public void WriteSingleMessage()
{ {
var bytes = _hubProtocol.WriteToArray(_hubMessage); var bytes = _hubProtocol.GetMessageBytes(_hubMessage);
if (bytes.Length != _binaryInput.Length) if (bytes.Length != _binaryInput.Length)
{ {
throw new InvalidOperationException("Failed to write message"); throw new InvalidOperationException("Failed to write message");

View File

@ -194,6 +194,11 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
_innerProtocol.WriteMessage(message, output); _innerProtocol.WriteMessage(message, output);
} }
public byte[] GetMessageBytes(HubMessage message)
{
return HubProtocolExtensions.GetMessageBytes(this, message);
}
public bool IsVersionSupported(int version) public bool IsVersionSupported(int version)
{ {
return _innerProtocol.IsVersionSupported(version); return _innerProtocol.IsVersionSupported(version);

View File

@ -143,6 +143,11 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
{ {
output.Write(_fixedOutput); output.Write(_fixedOutput);
} }
public byte[] GetMessageBytes(HubMessage message)
{
return HubProtocolExtensions.GetMessageBytes(this, message);
}
} }
} }
} }

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
} }
_parser = new ServerSentEventsMessageParser(); _parser = new ServerSentEventsMessageParser();
_rawData = hubProtocol.WriteToArray(hubMessage); _rawData = hubProtocol.GetMessageBytes(hubMessage);
var ms = new MemoryStream(); var ms = new MemoryStream();
ServerSentEventsMessageFormatter.WriteMessage(_rawData, ms); ServerSentEventsMessageFormatter.WriteMessage(_rawData, ms);
_sseFormattedData = ms.ToArray(); _sseFormattedData = ms.ToArray();

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@ -217,6 +218,35 @@ namespace Microsoft.AspNetCore.Internal
return result; return result;
} }
public void CopyTo(Span<byte> span)
{
Debug.Assert(span.Length >= _bytesWritten);
if (_currentSegment == null)
{
return;
}
var totalWritten = 0;
if (_fullSegments != null)
{
// Copy full segments
var count = _fullSegments.Count;
for (var i = 0; i < count; i++)
{
var segment = _fullSegments[i];
segment.AsSpan().CopyTo(span.Slice(totalWritten));
totalWritten += segment.Length;
}
}
// Copy current incomplete segment
_currentSegment.AsSpan(0, _position).CopyTo(span.Slice(totalWritten));
Debug.Assert(_bytesWritten == totalWritten + _position);
}
public override void Flush() { } public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();

View File

@ -10,15 +10,21 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
{ {
public static void WriteLengthPrefix(long length, IBufferWriter<byte> output) public static void WriteLengthPrefix(long length, IBufferWriter<byte> output)
{ {
// This code writes length prefix of the message as a VarInt. Read the comment in
// the BinaryMessageParser.TryParseMessage for details.
Span<byte> lenBuffer = stackalloc byte[5]; Span<byte> lenBuffer = stackalloc byte[5];
var lenNumBytes = WriteLengthPrefix(length, lenBuffer);
output.Write(lenBuffer.Slice(0, lenNumBytes));
}
public static int WriteLengthPrefix(long length, Span<byte> output)
{
// This code writes length prefix of the message as a VarInt. Read the comment in
// the BinaryMessageParser.TryParseMessage for details.
var lenNumBytes = 0; var lenNumBytes = 0;
do do
{ {
ref var current = ref lenBuffer[lenNumBytes]; ref var current = ref output[lenNumBytes];
current = (byte)(length & 0x7f); current = (byte)(length & 0x7f);
length >>= 7; length >>= 7;
if (length > 0) if (length > 0)
@ -29,7 +35,20 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
} }
while (length > 0); while (length > 0);
output.Write(lenBuffer.Slice(0, lenNumBytes)); return lenNumBytes;
}
public static int LengthPrefixLength(long length)
{
var lenNumBytes = 0;
do
{
length >>= 7;
lenNumBytes++;
}
while (length > 0);
return lenNumBytes;
} }
} }
} }

View File

@ -7,7 +7,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
{ {
public static class HubProtocolExtensions public static class HubProtocolExtensions
{ {
public static byte[] WriteToArray(this IHubProtocol hubProtocol, HubMessage message) // Would work as default interface impl
public static byte[] GetMessageBytes(this IHubProtocol hubProtocol, HubMessage message)
{ {
var writer = MemoryBufferWriter.Get(); var writer = MemoryBufferWriter.Get();
try try

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Buffers; using System.Buffers;
using System.IO;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.SignalR.Internal.Protocol namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
@ -19,6 +18,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
void WriteMessage(HubMessage message, IBufferWriter<byte> output); void WriteMessage(HubMessage message, IBufferWriter<byte> output);
byte[] GetMessageBytes(HubMessage message);
bool IsVersionSupported(int version); bool IsVersionSupported(int version);
} }
} }

View File

@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.SignalR
transferFormatFeature.ActiveFormat = Protocol.TransferFormat; transferFormatFeature.ActiveFormat = Protocol.TransferFormat;
} }
_cachedPingMessage = Protocol.WriteToArray(PingMessage.Instance); _cachedPingMessage = Protocol.GetMessageBytes(PingMessage.Instance);
UserIdentifier = userIdProvider.GetUserId(this); UserIdentifier = userIdProvider.GetUserId(this);

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
"This message was received from another server that did not have the requested protocol available."); "This message was received from another server that did not have the requested protocol available.");
} }
serialized = protocol.WriteToArray(Message); serialized = protocol.GetMessageBytes(Message);
SetCache(protocol.Name, serialized); SetCache(protocol.Name, serialized);
} }
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
{ {
writer.Write(protocol.Name); writer.Write(protocol.Name);
var buffer = protocol.WriteToArray(message); var buffer = protocol.GetMessageBytes(message);
writer.Write(buffer.Length); writer.Write(buffer.Length);
writer.Write(buffer); writer.Write(buffer);
} }

View File

@ -82,6 +82,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
TextMessageFormatter.WriteRecordSeparator(output); TextMessageFormatter.WriteRecordSeparator(output);
} }
public byte[] GetMessageBytes(HubMessage message)
{
return HubProtocolExtensions.GetMessageBytes(this, message);
}
private HubMessage ParseMessage(Utf8BufferTextReader textReader, IInvocationBinder binder) private HubMessage ParseMessage(Utf8BufferTextReader textReader, IInvocationBinder binder)
{ {
try try

View File

@ -303,6 +303,34 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
} }
} }
public byte[] GetMessageBytes(HubMessage message)
{
var writer = MemoryBufferWriter.Get();
try
{
// Write message to a buffer so we can get its length
WriteMessageCore(message, writer);
var dataLength = writer.Length;
var prefixLength = BinaryMessageFormatter.LengthPrefixLength(writer.Length);
var array = new byte[dataLength + prefixLength];
var span = array.AsSpan();
// Write length then message to output
var written = BinaryMessageFormatter.WriteLengthPrefix(writer.Length, span);
Debug.Assert(written == prefixLength);
writer.CopyTo(span.Slice(prefixLength));
return array;
}
finally
{
MemoryBufferWriter.Return(writer);
}
}
private void WriteMessageCore(HubMessage message, Stream packer) private void WriteMessageCore(HubMessage message, Stream packer)
{ {
switch (message) switch (message)

View File

@ -167,6 +167,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
throw _error; throw _error;
} }
} }
public byte[] GetMessageBytes(HubMessage message)
{
return HubProtocolExtensions.GetMessageBytes(this, message);
}
} }
} }
} }

View File

@ -34,6 +34,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void WritingNotingGivesEmptyData_CopyTo()
{
using (var bufferWriter = new MemoryBufferWriter())
{
Assert.Equal(0, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Empty(data);
}
}
[Fact] [Fact]
public void WriteByteWorksAsFirstCall() public void WriteByteWorksAsFirstCall()
{ {
@ -48,6 +60,21 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void WriteByteWorksAsFirstCall_CopyTo()
{
using (var bufferWriter = new MemoryBufferWriter())
{
bufferWriter.WriteByte(234);
Assert.Equal(1, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(234, data[0]);
}
}
[Fact] [Fact]
public void WriteByteWorksIfFirstByteInNewSegment() public void WriteByteWorksIfFirstByteInNewSegment()
{ {
@ -67,6 +94,27 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void WriteByteWorksIfFirstByteInNewSegment_CopyTo()
{
var inputSize = MinimumSegmentSize;
var input = Enumerable.Range(0, inputSize).Select(i => (byte)i).ToArray();
using (var bufferWriter = new MemoryBufferWriter(MinimumSegmentSize))
{
bufferWriter.Write(input, 0, input.Length);
Assert.Equal(16, bufferWriter.Length);
bufferWriter.WriteByte(16);
Assert.Equal(17, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(input, data.Take(16));
Assert.Equal(16, data[16]);
}
}
[Fact] [Fact]
public void WriteByteWorksIfSegmentHasSpace() public void WriteByteWorksIfSegmentHasSpace()
{ {
@ -88,6 +136,28 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void WriteByteWorksIfSegmentHasSpace_CopyTo()
{
var input = new byte[] { 11, 12, 13 };
using (var bufferWriter = new MemoryBufferWriter())
{
bufferWriter.Write(input, 0, input.Length);
bufferWriter.WriteByte(14);
Assert.Equal(4, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(11, data[0]);
Assert.Equal(12, data[1]);
Assert.Equal(13, data[2]);
Assert.Equal(14, data[3]);
}
}
[Fact] [Fact]
public void ToArrayWithExactlyFullSegmentsWorks() public void ToArrayWithExactlyFullSegmentsWorks()
{ {
@ -104,6 +174,24 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void ToArrayWithExactlyFullSegmentsWorks_CopyTo()
{
var inputSize = MinimumSegmentSize * 2;
var input = Enumerable.Range(0, inputSize).Select(i => (byte)i).ToArray();
using (var bufferWriter = new MemoryBufferWriter(MinimumSegmentSize))
{
bufferWriter.Write(input, 0, input.Length);
Assert.Equal(input.Length, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(input, data);
}
}
[Fact] [Fact]
public void ToArrayWithSomeFullSegmentsWorks() public void ToArrayWithSomeFullSegmentsWorks()
{ {
@ -120,6 +208,23 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void ToArrayWithSomeFullSegmentsWorks_CopyTo()
{
var inputSize = (MinimumSegmentSize * 2) + 1;
var input = Enumerable.Range(0, inputSize).Select(i => (byte)i).ToArray();
using (var bufferWriter = new MemoryBufferWriter(MinimumSegmentSize))
{
bufferWriter.Write(input, 0, input.Length);
Assert.Equal(input.Length, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(input, data);
}
}
[Fact] [Fact]
public async Task CopyToAsyncWithExactlyFullSegmentsWorks() public async Task CopyToAsyncWithExactlyFullSegmentsWorks()
{ {
@ -177,6 +282,34 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void CopyToWithExactlyFullSegmentsWorks_CopyTo()
{
var inputSize = MinimumSegmentSize * 2;
var input = Enumerable.Range(0, inputSize).Select(i => (byte)i).ToArray();
using (var bufferWriter = new MemoryBufferWriter(MinimumSegmentSize))
{
bufferWriter.Write(input, 0, input.Length);
Assert.Equal(input.Length, bufferWriter.Length);
using (var destination = new MemoryBufferWriter())
{
bufferWriter.CopyTo(destination);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(input, data);
Array.Clear(data, 0, data.Length);
destination.CopyTo(data);
Assert.Equal(input, data);
}
}
}
[Fact] [Fact]
public void CopyToWithSomeFullSegmentsWorks() public void CopyToWithSomeFullSegmentsWorks()
{ {
@ -197,6 +330,34 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
[Fact]
public void CopyToWithSomeFullSegmentsWorks_CopyTo()
{
var inputSize = (MinimumSegmentSize * 2) + 1;
var input = Enumerable.Range(0, inputSize).Select(i => (byte)i).ToArray();
using (var bufferWriter = new MemoryBufferWriter(MinimumSegmentSize))
{
bufferWriter.Write(input, 0, input.Length);
Assert.Equal(input.Length, bufferWriter.Length);
using (var destination = new MemoryBufferWriter())
{
bufferWriter.CopyTo(destination);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(input, data);
Array.Clear(data, 0, data.Length);
destination.CopyTo(data);
Assert.Equal(input, data);
}
}
}
#if NETCOREAPP2_1 #if NETCOREAPP2_1
[Fact] [Fact]
public void WriteSpanWorksAtNonZeroOffset() public void WriteSpanWorksAtNonZeroOffset()
@ -216,6 +377,25 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
Assert.Equal(4, data[3]); Assert.Equal(4, data[3]);
} }
} }
[Fact]
public void WriteSpanWorksAtNonZeroOffset_CopyTo()
{
using (var bufferWriter = new MemoryBufferWriter())
{
bufferWriter.WriteByte(1);
bufferWriter.Write(new byte[] { 2, 3, 4 }.AsSpan());
Assert.Equal(4, bufferWriter.Length);
var data = new byte[bufferWriter.Length];
bufferWriter.CopyTo(data);
Assert.Equal(1, data[0]);
Assert.Equal(2, data[1]);
Assert.Equal(3, data[2]);
Assert.Equal(4, data[3]);
}
}
#endif #endif
[Fact] [Fact]

View File

@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public async Task<string> SendHubMessageAsync(HubMessage message) public async Task<string> SendHubMessageAsync(HubMessage message)
{ {
var payload = _protocol.WriteToArray(message); var payload = _protocol.GetMessageBytes(message);
await Connection.Application.Output.WriteAsync(payload); await Connection.Application.Output.WriteAsync(payload);
return message is HubInvocationMessage hubMessage ? hubMessage.InvocationId : null; return message is HubInvocationMessage hubMessage ? hubMessage.InvocationId : null;