Don't create the span on netstandard (#1721)
- Directly pin the char[] - Changed Utf8BufferTextReader to use the Utf8Decoder - It copies whatever it can into the char buffer allocated in a stateful way (it's more efficient). - Added tests for unicode and ascii reading - Added a thread static cache
This commit is contained in:
parent
ddc0e4fb3a
commit
b8285b8356
|
|
@ -58,7 +58,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
|||
|
||||
private static JsonTextReader CreateJsonTextReader(ReadOnlyMemory<byte> payload)
|
||||
{
|
||||
var textReader = new Utf8BufferTextReader(payload);
|
||||
var textReader = new Utf8BufferTextReader();
|
||||
textReader.SetBuffer(payload);
|
||||
var reader = new JsonTextReader(textReader);
|
||||
reader.ArrayPool = JsonArrayPool<char>.Shared;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,11 +58,19 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
|||
{
|
||||
while (TextMessageParser.TryParseMessage(ref input, out var payload))
|
||||
{
|
||||
var textReader = new Utf8BufferTextReader(payload);
|
||||
var message = ParseMessage(textReader, binder);
|
||||
if (message != null)
|
||||
var textReader = Utf8BufferTextReader.Get(payload);
|
||||
|
||||
try
|
||||
{
|
||||
messages.Add(message);
|
||||
var message = ParseMessage(textReader, binder);
|
||||
if (message != null)
|
||||
{
|
||||
messages.Add(message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Utf8BufferTextReader.Return(textReader);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +111,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
|||
using (var reader = new JsonTextReader(textReader))
|
||||
{
|
||||
reader.ArrayPool = JsonArrayPool<char>.Shared;
|
||||
reader.CloseInput = false;
|
||||
|
||||
JsonUtils.CheckRead(reader);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,54 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
|||
internal class Utf8BufferTextReader : TextReader
|
||||
{
|
||||
private ReadOnlyMemory<byte> _utf8Buffer;
|
||||
private Decoder _decoder;
|
||||
|
||||
public Utf8BufferTextReader(ReadOnlyMemory<byte> utf8Buffer)
|
||||
[ThreadStatic]
|
||||
private static Utf8BufferTextReader _cachedInstance;
|
||||
|
||||
#if DEBUG
|
||||
private bool _inUse;
|
||||
#endif
|
||||
|
||||
public Utf8BufferTextReader()
|
||||
{
|
||||
_decoder = Encoding.UTF8.GetDecoder();
|
||||
}
|
||||
|
||||
public static Utf8BufferTextReader Get(ReadOnlyMemory<byte> utf8Buffer)
|
||||
{
|
||||
var reader = _cachedInstance;
|
||||
if (reader == null)
|
||||
{
|
||||
reader = new Utf8BufferTextReader();
|
||||
}
|
||||
|
||||
// Taken off the the thread static
|
||||
_cachedInstance = null;
|
||||
#if DEBUG
|
||||
if (reader._inUse)
|
||||
{
|
||||
throw new InvalidOperationException("The reader wasn't returned!");
|
||||
}
|
||||
|
||||
reader._inUse = true;
|
||||
#endif
|
||||
reader.SetBuffer(utf8Buffer);
|
||||
return reader;
|
||||
}
|
||||
|
||||
public static void Return(Utf8BufferTextReader reader)
|
||||
{
|
||||
_cachedInstance = reader;
|
||||
#if DEBUG
|
||||
reader._inUse = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetBuffer(ReadOnlyMemory<byte> utf8Buffer)
|
||||
{
|
||||
_utf8Buffer = utf8Buffer;
|
||||
_decoder.Reset();
|
||||
}
|
||||
|
||||
public override int Read(char[] buffer, int index, int count)
|
||||
|
|
@ -25,33 +69,24 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
|||
}
|
||||
|
||||
var source = _utf8Buffer.Span;
|
||||
var destination = new Span<char>(buffer, index, count);
|
||||
var destinationBytesCount = Encoding.UTF8.GetByteCount(buffer, index, count);
|
||||
|
||||
// We have then the destination
|
||||
if (source.Length > destinationBytesCount)
|
||||
{
|
||||
source = source.Slice(0, destinationBytesCount);
|
||||
|
||||
_utf8Buffer = _utf8Buffer.Slice(destinationBytesCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
_utf8Buffer = ReadOnlyMemory<byte>.Empty;
|
||||
}
|
||||
|
||||
var bytesUsed = 0;
|
||||
var charsUsed = 0;
|
||||
#if NETCOREAPP2_1
|
||||
return Encoding.UTF8.GetChars(source, destination);
|
||||
var destination = new Span<char>(buffer, index, count);
|
||||
_decoder.Convert(source, destination, false, out bytesUsed, out charsUsed, out var completed);
|
||||
#else
|
||||
unsafe
|
||||
{
|
||||
fixed (char* destinationChars = &MemoryMarshal.GetReference(destination))
|
||||
fixed (char* destinationChars = &buffer[index])
|
||||
fixed (byte* sourceBytes = &MemoryMarshal.GetReference(source))
|
||||
{
|
||||
return Encoding.UTF8.GetChars(sourceBytes, source.Length, destinationChars, destination.Length);
|
||||
_decoder.Convert(sourceBytes, source.Length, destinationChars, count, false, out bytesUsed, out charsUsed, out var completed);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
_utf8Buffer = _utf8Buffer.Slice(bytesUsed);
|
||||
|
||||
return charsUsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Tests.Utils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Tests.Utils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Common.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// 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 System.Text;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||
{
|
||||
public class Utf8BufferTextReaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReadingWhenCharBufferBigEnough()
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
||||
var reader = new Utf8BufferTextReader();
|
||||
reader.SetBuffer(buffer);
|
||||
|
||||
var chars = new char[1024];
|
||||
int read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal("Hello World", new string(chars, 0, read));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadingUnicodeWhenCharBufferBigEnough()
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes("a\u00E4\u00E4\u00a9o");
|
||||
var reader = new Utf8BufferTextReader();
|
||||
reader.SetBuffer(buffer);
|
||||
|
||||
var chars = new char[1024];
|
||||
int read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(5, read);
|
||||
Assert.Equal("a\u00E4\u00E4\u00a9o", new string(chars, 0, read));
|
||||
|
||||
read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadingWhenCharBufferBigEnoughAndNotStartingFromZero()
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
||||
var reader = new Utf8BufferTextReader();
|
||||
reader.SetBuffer(buffer);
|
||||
|
||||
var chars = new char[1024];
|
||||
int read = reader.Read(chars, 10, chars.Length - 10);
|
||||
|
||||
Assert.Equal(11, read);
|
||||
Assert.Equal("Hello World", new string(chars, 10, read));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadingWhenBufferTooSmall()
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
||||
var reader = new Utf8BufferTextReader();
|
||||
reader.SetBuffer(buffer);
|
||||
|
||||
var chars = new char[5];
|
||||
int read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(5, read);
|
||||
Assert.Equal("Hello", new string(chars, 0, read));
|
||||
|
||||
read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(5, read);
|
||||
Assert.Equal(" Worl", new string(chars, 0, read));
|
||||
|
||||
read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(1, read);
|
||||
Assert.Equal("d", new string(chars, 0, read));
|
||||
|
||||
read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(0, read);
|
||||
|
||||
read = reader.Read(chars, 0, 1);
|
||||
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadingUnicodeWhenBufferTooSmall()
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes("\u00E4\u00E4\u00E5");
|
||||
var reader = new Utf8BufferTextReader();
|
||||
reader.SetBuffer(buffer);
|
||||
|
||||
var chars = new char[2];
|
||||
int read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(2, read);
|
||||
Assert.Equal("\u00E4\u00E4", new string(chars, 0, read));
|
||||
|
||||
read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(1, read);
|
||||
Assert.Equal("\u00E5", new string(chars, 0, read));
|
||||
|
||||
read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
Assert.Equal(0, read);
|
||||
|
||||
read = reader.Read(chars, 0, 1);
|
||||
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue