Introduce IStringDecoder to support custom string pooling on read
This commit is contained in:
Родитель
ebcf34244e
Коммит
d3a0117253
|
@ -135,6 +135,13 @@
|
||||||
<Link>Transactions\ResourceManager.cs</Link>
|
<Link>Transactions\ResourceManager.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\src\Net\BufferManager.cs" />
|
<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">
|
<Compile Include="..\src\Net\TransportWriter.cs">
|
||||||
<Link>Internal\TransportWriter.cs</Link>
|
<Link>Internal\TransportWriter.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -292,6 +292,12 @@
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\src\Session.cs" />
|
<Compile Include="..\src\Session.cs" />
|
||||||
<Compile Include="..\src\Trace.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">
|
<Compile Include="..\src\Types\Decimal.cs">
|
||||||
<Link>Types\Decimal.cs</Link>
|
<Link>Types\Decimal.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -141,6 +141,12 @@
|
||||||
<Link>Transactions\ResourceManager.cs</Link>
|
<Link>Transactions\ResourceManager.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\src\Net\BufferManager.cs" />
|
<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">
|
<Compile Include="..\src\Net\SocketExtensions.cs">
|
||||||
<Link>Internal\SocketExtensions.cs</Link>
|
<Link>Internal\SocketExtensions.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -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;
|
static Map knownDescribed;
|
||||||
#if !NETMF
|
#if !NETMF
|
||||||
static Dictionary<ulong, CreateDescribed> knownDescribedByCode;
|
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
|
#endif
|
||||||
|
|
||||||
static Encoder()
|
static Encoder()
|
||||||
|
@ -1659,7 +1669,16 @@ namespace Amqp.Types
|
||||||
#if NETMF || NETFX_CORE
|
#if NETMF || NETFX_CORE
|
||||||
string value = new string(Encoding.UTF8.GetChars(buffer.Buffer, buffer.Offset, count));
|
string value = new string(Encoding.UTF8.GetChars(buffer.Buffer, buffer.Offset, count));
|
||||||
#else
|
#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
|
#endif
|
||||||
buffer.Complete(count);
|
buffer.Complete(count);
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,7 @@
|
||||||
<Link>WebSocketTests.cs</Link>
|
<Link>WebSocketTests.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="AmqpCodecTests.cs" />
|
<Compile Include="AmqpCodecTests.cs" />
|
||||||
|
<Compile Include="WellknownStringDecoderTests.cs" />
|
||||||
<Compile Include="TransactionTests.cs" />
|
<Compile Include="TransactionTests.cs" />
|
||||||
<Compile Include="PerfTests.cs" />
|
<Compile Include="PerfTests.cs" />
|
||||||
<Compile Include="TaskTests.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">
|
<Compile Include="..\Test.Amqp.Net\AmqpCodecTests.cs">
|
||||||
<Link>AmqpCodecTests.cs</Link>
|
<Link>AmqpCodecTests.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Test.Amqp.Net\WellknownStringDecoderTests.cs">
|
||||||
|
<Link>WellknownStringDecoderTests.cs</Link>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Properties\" />
|
<Folder Include="Properties\" />
|
||||||
|
|
|
@ -77,6 +77,9 @@
|
||||||
<Compile Include="..\Test.Amqp.Net\TransactionTests.cs">
|
<Compile Include="..\Test.Amqp.Net\TransactionTests.cs">
|
||||||
<Link>TransactionTests.cs</Link>
|
<Link>TransactionTests.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Test.Amqp.Net\WellknownStringDecoderTests.cs">
|
||||||
|
<Link>WellknownStringDecoderTests.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="..\Common\AmqpSerializerTests.cs">
|
<Compile Include="..\Common\AmqpSerializerTests.cs">
|
||||||
<Link>AmqpSerializerTests.cs</Link>
|
<Link>AmqpSerializerTests.cs</Link>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче