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>
|
||||
</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче