Introduce IStringDecoder to support custom string pooling on read

This commit is contained in:
Stephan Zehetner 2019-08-07 13:09:14 +02:00 коммит произвёл xinchen
Родитель ebcf34244e
Коммит d3a0117253
10 изменённых файлов: 292 добавлений и 1 удалений

Просмотреть файл

@ -135,6 +135,13 @@
<Link>Transactions\ResourceManager.cs</Link>
</Compile>
<Compile Include="..\src\Net\BufferManager.cs" />
<Compile Include="..\src\Net\IStringDecoder.cs">
<Link>IStringDecoder.cs</Link>
</Compile>
<Compile Include="..\src\Net\WellknownStringDecoder.cs">
<Link>WellknownStringDecoder.cs</Link>
</Compile>
<Compile Include="..\src\Net\TransportWriter.cs">
<Link>Internal\TransportWriter.cs</Link>
</Compile>

Просмотреть файл

@ -292,6 +292,12 @@
</Compile>
<Compile Include="..\src\Session.cs" />
<Compile Include="..\src\Trace.cs" />
<Compile Include="..\src\Net\IStringDecoder.cs">
<Link>IStringDecoder.cs</Link>
</Compile>
<Compile Include="..\src\Net\WellknownStringDecoder.cs">
<Link>WellknownStringDecoder.cs</Link>
</Compile>
<Compile Include="..\src\Types\Decimal.cs">
<Link>Types\Decimal.cs</Link>
</Compile>

Просмотреть файл

@ -141,6 +141,12 @@
<Link>Transactions\ResourceManager.cs</Link>
</Compile>
<Compile Include="..\src\Net\BufferManager.cs" />
<Compile Include="..\src\Net\IStringDecoder.cs">
<Link>IStringDecoder.cs</Link>
</Compile>
<Compile Include="..\src\Net\WellknownStringDecoder.cs">
<Link>WellknownStringDecoder.cs</Link>
</Compile>
<Compile Include="..\src\Net\SocketExtensions.cs">
<Link>Internal\SocketExtensions.cs</Link>
</Compile>

33
src/Net/IStringDecoder.cs Normal file
Просмотреть файл

@ -0,0 +1,33 @@
// ------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this
// file except in compliance with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR
// CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------------------
namespace Amqp
{
using System;
/// <summary>
/// The interface defines the method to decode strings from a binary buffer. This can be used to implement custom string pooling.
/// </summary>
public interface IStringDecoder
{
/// <summary>
/// Reads a string from a buffer.
/// </summary>
/// <param name="buffer">The buffer to read from.</param>
string DecodeString(ArraySegment<byte> buffer);
}
}

Просмотреть файл

@ -0,0 +1,106 @@
namespace Amqp
{
using System;
using System.Collections.Generic;
using System.Text;
/// <summary>
/// Implements a simple IStringDecoder that caches well-known strings. If the encoded bytes of a buffer match the encoded value of a known string, the
/// existing known string instance is returned, avoiding any allocations.
/// Note that this class is not thread-safe, so all calls to AddWellknownString should be made before the StringDecoder is passed to Encoder.StringDecoder.
/// </summary>
public class WellknownStringDecoder : IStringDecoder
{
readonly Dictionary<BufferKey, string> knownStrings = new Dictionary<BufferKey, string>();
/// <summary>
/// Adds a known string.
/// </summary>
/// <param name="knownString">The known string instance</param>
public void AddWellknownString(string knownString)
{
if (knownString == null)
{
throw new ArgumentNullException("knownString");
}
byte[] encodedString = Encoding.UTF8.GetBytes(knownString);
var key = new BufferKey(encodedString);
this.knownStrings[key] = knownString;
}
/// <summary>
/// Performs a lookup based on the contents of the passed byte buffer. If the byte contents match a known string, the cached string instance is returned.
/// Otherwise a new string is created by decoding the buffer contents.
/// </summary>
/// <param name="buffer">The byte array segment to read from</param>
/// <returns>A string instance that match the decoded value of the passed byte buffer.</returns>
public string DecodeString(ArraySegment<byte> buffer)
{
var searchKey = new BufferKey(buffer);
string knownString;
if (this.knownStrings.TryGetValue(searchKey, out knownString))
return knownString;
return Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
}
private struct BufferKey : IEquatable<BufferKey>
{
readonly ArraySegment<byte> encodedString;
public BufferKey(byte[] encodedString)
{
this.encodedString = new ArraySegment<byte>(encodedString);
}
public BufferKey(ArraySegment<byte> encodedString)
{
this.encodedString = encodedString;
}
public bool Equals(BufferKey other)
{
if (this.encodedString.Count != other.encodedString.Count)
return false;
for (int i = 0; i < this.encodedString.Count; i++)
{
if (this.encodedString.Array[this.encodedString.Offset + i] != other.encodedString.Array[other.encodedString.Offset + i])
{
return false;
}
}
return true;
}
public override bool Equals(object obj)
{
if (!(obj is BufferKey))
{
return false;
}
return Equals((BufferKey)obj);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
int endOffset = this.encodedString.Offset + this.encodedString.Count;
for (var i = this.encodedString.Offset; i < endOffset; i++)
{
hash = hash * 31 + this.encodedString.Array[i];
}
return hash;
}
}
}
}
}

Просмотреть файл

@ -72,6 +72,16 @@ namespace Amqp.Types
static Map knownDescribed;
#if !NETMF
static Dictionary<ulong, CreateDescribed> knownDescribedByCode;
static IStringDecoder stringDecoder;
/// <summary>
/// Gets or sets the custom IStringDecoder that is used to read strings from a byte buffer.
/// </summary>
public static IStringDecoder StringDecoder
{
get { return stringDecoder; }
set { stringDecoder = value; }
}
#endif
static Encoder()
@ -1659,7 +1669,16 @@ namespace Amqp.Types
#if NETMF || NETFX_CORE
string value = new string(Encoding.UTF8.GetChars(buffer.Buffer, buffer.Offset, count));
#else
string value = Encoding.UTF8.GetString(buffer.Buffer, buffer.Offset, count);
string value;
IStringDecoder localDecoder = Encoder.stringDecoder; //store in local variable to prevent NullReferenceException if stringDecoder is changed after the null check
if (localDecoder == null)
{
value = Encoding.UTF8.GetString(buffer.Buffer, buffer.Offset, count);
}
else
{
value = localDecoder.DecodeString(new ArraySegment<byte>(buffer.Buffer, buffer.Offset, count));
}
#endif
buffer.Complete(count);

Просмотреть файл

@ -125,6 +125,7 @@
<Link>WebSocketTests.cs</Link>
</Compile>
<Compile Include="AmqpCodecTests.cs" />
<Compile Include="WellknownStringDecoderTests.cs" />
<Compile Include="TransactionTests.cs" />
<Compile Include="PerfTests.cs" />
<Compile Include="TaskTests.cs" />

Просмотреть файл

@ -0,0 +1,107 @@
// ------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this
// file except in compliance with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR
// CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------------------
using System;
using System.Text;
using Amqp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Encoder = Amqp.Types.Encoder;
namespace Test.Amqp
{
[TestClass]
public class WellknownStringDecoderTests
{
[TestMethod]
public void UnknownString_CreatesNewInstance()
{
WellknownStringDecoder decoder = new WellknownStringDecoder();
byte[] encodedBytes = Encoding.UTF8.GetBytes("abc");
string decodedString1 = decoder.DecodeString(new ArraySegment<byte>(encodedBytes));
string decodedString2 = decoder.DecodeString(new ArraySegment<byte>(encodedBytes));
Assert.IsTrue(!ReferenceEquals(decodedString1, decodedString2));
}
[TestMethod]
public void KnownString_SameInstanceReturned()
{
WellknownStringDecoder decoder = new WellknownStringDecoder();
decoder.AddWellknownString("abc");
byte[] encodedBytes = Encoding.UTF8.GetBytes("abc");
string decodedString1 = decoder.DecodeString(new ArraySegment<byte>(encodedBytes));
string decodedString2 = decoder.DecodeString(new ArraySegment<byte>(encodedBytes));
Assert.IsTrue(ReferenceEquals(decodedString1, decodedString2));
}
[TestMethod]
public void KnownString_SameStringCanBeAddedTwice()
{
WellknownStringDecoder decoder = new WellknownStringDecoder();
decoder.AddWellknownString("abc");
decoder.AddWellknownString("abc");
}
[TestMethod]
public void KnownString_SameInstanceReturnedWithDifferentByteOffset()
{
WellknownStringDecoder decoder = new WellknownStringDecoder();
decoder.AddWellknownString("abc");
byte[] encodedBytes1 = Encoding.UTF8.GetBytes("abc");
byte[] encodedBytes2 = Encoding.UTF8.GetBytes("12abc");
string decodedString1 = decoder.DecodeString(new ArraySegment<byte>(encodedBytes1));
string decodedString2 = decoder.DecodeString(new ArraySegment<byte>(encodedBytes2, 2, 3));
Assert.IsTrue(ReferenceEquals(decodedString1, decodedString2));
}
[TestMethod]
public void KnownString_UsedInEncoder()
{
try
{
WellknownStringDecoder decoder = new WellknownStringDecoder();
decoder.AddWellknownString("abc");
Encoder.StringDecoder = decoder;
ByteBuffer buffer = new ByteBuffer(128, true);
Encoder.WriteString(buffer, "abc", true);
Encoder.WriteString(buffer, "abc", true);
buffer.Seek(0);
string decodedString1 = Encoder.ReadString(buffer, Encoder.ReadFormatCode(buffer));
string decodedString2 = Encoder.ReadString(buffer, Encoder.ReadFormatCode(buffer));
Assert.IsTrue(ReferenceEquals(decodedString1, decodedString2));
}
finally
{
Encoder.StringDecoder = null;
}
}
}
}

Просмотреть файл

@ -109,6 +109,9 @@
<Compile Include="..\Test.Amqp.Net\AmqpCodecTests.cs">
<Link>AmqpCodecTests.cs</Link>
</Compile>
<Compile Include="..\Test.Amqp.Net\WellknownStringDecoderTests.cs">
<Link>WellknownStringDecoderTests.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />

Просмотреть файл

@ -77,6 +77,9 @@
<Compile Include="..\Test.Amqp.Net\TransactionTests.cs">
<Link>TransactionTests.cs</Link>
</Compile>
<Compile Include="..\Test.Amqp.Net\WellknownStringDecoderTests.cs">
<Link>WellknownStringDecoderTests.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\Common\AmqpSerializerTests.cs">
<Link>AmqpSerializerTests.cs</Link>