From 12e2f3057756069f0ff4431fb530a5c259767efe Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 15 Nov 2016 12:01:13 +0000 Subject: [PATCH] MemoryPoolIterator byref structs --- .../Internal/Http/Frame.cs | 14 +- .../Infrastructure/MemoryPoolIterator.cs | 161 ++++++++++++++++++ .../MemoryPoolIteratorExtensions.cs | 155 +---------------- .../AsciiDecoding.cs | 8 +- .../UrlPathDecoder.cs | 8 +- 5 files changed, 179 insertions(+), 167 deletions(-) diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs index 399976939c..59891a7355 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs @@ -975,7 +975,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty); } - method = begin.GetAsciiString(scan); + method = begin.GetAsciiString(ref scan); if (method == null) { @@ -1031,7 +1031,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http RejectRequest(RequestRejectionReason.InvalidRequestLine, Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty); } - queryString = begin.GetAsciiString(scan); + queryString = begin.GetAsciiString(ref scan); } var queryEnd = scan; @@ -1081,16 +1081,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http if (needDecode) { // Read raw target before mutating memory. - rawTarget = pathBegin.GetAsciiString(queryEnd); + rawTarget = pathBegin.GetAsciiString(ref queryEnd); // URI was encoded, unescape and then parse as utf8 pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd); - requestUrlPath = pathBegin.GetUtf8String(pathEnd); + requestUrlPath = pathBegin.GetUtf8String(ref pathEnd); } else { // URI wasn't encoded, parse as ASCII - requestUrlPath = pathBegin.GetAsciiString(pathEnd); + requestUrlPath = pathBegin.GetAsciiString(ref pathEnd); if (queryString.Length == 0) { @@ -1100,7 +1100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http } else { - rawTarget = pathBegin.GetAsciiString(queryEnd); + rawTarget = pathBegin.GetAsciiString(ref queryEnd); } } @@ -1341,7 +1341,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http } while (ch != ByteCR); var name = beginName.GetArraySegment(endName); - var value = beginValue.GetAsciiString(endValue); + var value = beginValue.GetAsciiString(ref endValue); consumed = scan; requestHeaders.Append(name.Array, name.Offset, name.Count, value); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs index f41a6542f0..78f782d39e 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs @@ -5,7 +5,9 @@ using System; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; +using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure { @@ -19,6 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure 0x02ul << 40 | 0x01ul << 48 ) + 1; + private static readonly Encoding _utf8 = Encoding.UTF8; private static readonly int _vectorSpan = Vector.Count; private MemoryPoolBlock _block; @@ -1026,6 +1029,164 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure _index = blockIndex; } + public unsafe string GetAsciiString(ref MemoryPoolIterator end) + { + if (IsDefault || end.IsDefault) + { + return null; + } + + var length = GetLength(end); + + if (length == 0) + { + return null; + } + + var inputOffset = Index; + var block = Block; + + var asciiString = new string('\0', length); + + fixed (char* outputStart = asciiString) + { + var output = outputStart; + var remaining = length; + + var endBlock = end.Block; + var endIndex = end.Index; + + var outputOffset = 0; + while (true) + { + int following = (block != endBlock ? block.End : endIndex) - inputOffset; + + if (following > 0) + { + if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following)) + { + throw BadHttpRequestException.GetException(RequestRejectionReason.NonAsciiOrNullCharactersInInputString); + } + + outputOffset += following; + remaining -= following; + } + + if (remaining == 0) + { + break; + } + + block = block.Next; + inputOffset = block.Start; + } + } + + return asciiString; + } + + public string GetUtf8String(ref MemoryPoolIterator end) + { + if (IsDefault || end.IsDefault) + { + return default(string); + } + if (end.Block == Block) + { + return _utf8.GetString(Block.Array, Index, end.Index - Index); + } + + var decoder = _utf8.GetDecoder(); + + var length = GetLength(end); + var charLength = length; + // Worse case is 1 byte = 1 char + var chars = new char[charLength]; + var charIndex = 0; + + var block = Block; + var index = Index; + var remaining = length; + while (true) + { + int bytesUsed; + int charsUsed; + bool completed; + var following = block.End - index; + if (remaining <= following) + { + decoder.Convert( + block.Array, + index, + remaining, + chars, + charIndex, + charLength - charIndex, + true, + out bytesUsed, + out charsUsed, + out completed); + return new string(chars, 0, charIndex + charsUsed); + } + else if (block.Next == null) + { + decoder.Convert( + block.Array, + index, + following, + chars, + charIndex, + charLength - charIndex, + true, + out bytesUsed, + out charsUsed, + out completed); + return new string(chars, 0, charIndex + charsUsed); + } + else + { + decoder.Convert( + block.Array, + index, + following, + chars, + charIndex, + charLength - charIndex, + false, + out bytesUsed, + out charsUsed, + out completed); + charIndex += charsUsed; + remaining -= following; + block = block.Next; + index = block.Start; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArraySegment GetArraySegment(MemoryPoolIterator end) + { + if (IsDefault || end.IsDefault) + { + return default(ArraySegment); + } + if (end.Block == Block) + { + return new ArraySegment(Block.Array, Index, end.Index - Index); + } + + return GetArraySegmentMultiBlock(ref end); + } + + private ArraySegment GetArraySegmentMultiBlock(ref MemoryPoolIterator end) + { + var length = GetLength(end); + var array = new byte[length]; + CopyTo(array, 0, length, out length); + return new ArraySegment(array, 0, length); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector GetVector(byte vectorByte) { diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs index d59f689662..0b7e988af3 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; @@ -71,62 +72,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure } } - public unsafe static string GetAsciiString(this MemoryPoolIterator start, MemoryPoolIterator end) - { - if (start.IsDefault || end.IsDefault) - { - return null; - } - - var length = start.GetLength(end); - - if (length == 0) - { - return null; - } - - var inputOffset = start.Index; - var block = start.Block; - - var asciiString = new string('\0', length); - - fixed (char* outputStart = asciiString) - { - var output = outputStart; - var remaining = length; - - var endBlock = end.Block; - var endIndex = end.Index; - - var outputOffset = 0; - while (true) - { - int following = (block != endBlock ? block.End : endIndex) - inputOffset; - - if (following > 0) - { - if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following)) - { - throw BadHttpRequestException.GetException(RequestRejectionReason.NonAsciiOrNullCharactersInInputString); - } - - outputOffset += following; - remaining -= following; - } - - if (remaining == 0) - { - break; - } - - block = block.Next; - inputOffset = block.Start; - } - } - - return asciiString; - } - public static string GetAsciiStringEscaped(this MemoryPoolIterator start, MemoryPoolIterator end, int maxChars) { var sb = new StringBuilder(); @@ -147,102 +92,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure return sb.ToString(); } - public static string GetUtf8String(this MemoryPoolIterator start, MemoryPoolIterator end) - { - if (start.IsDefault || end.IsDefault) - { - return default(string); - } - if (end.Block == start.Block) - { - return _utf8.GetString(start.Block.Array, start.Index, end.Index - start.Index); - } - - var decoder = _utf8.GetDecoder(); - - var length = start.GetLength(end); - var charLength = length; - // Worse case is 1 byte = 1 char - var chars = new char[charLength]; - var charIndex = 0; - - var block = start.Block; - var index = start.Index; - var remaining = length; - while (true) - { - int bytesUsed; - int charsUsed; - bool completed; - var following = block.End - index; - if (remaining <= following) - { - decoder.Convert( - block.Array, - index, - remaining, - chars, - charIndex, - charLength - charIndex, - true, - out bytesUsed, - out charsUsed, - out completed); - return new string(chars, 0, charIndex + charsUsed); - } - else if (block.Next == null) - { - decoder.Convert( - block.Array, - index, - following, - chars, - charIndex, - charLength - charIndex, - true, - out bytesUsed, - out charsUsed, - out completed); - return new string(chars, 0, charIndex + charsUsed); - } - else - { - decoder.Convert( - block.Array, - index, - following, - chars, - charIndex, - charLength - charIndex, - false, - out bytesUsed, - out charsUsed, - out completed); - charIndex += charsUsed; - remaining -= following; - block = block.Next; - index = block.Start; - } - } - } - - public static ArraySegment GetArraySegment(this MemoryPoolIterator start, MemoryPoolIterator end) - { - if (start.IsDefault || end.IsDefault) - { - return default(ArraySegment); - } - if (end.Block == start.Block) - { - return new ArraySegment(start.Block.Array, start.Index, end.Index - start.Index); - } - - var length = start.GetLength(end); - var array = new byte[length]; - start.CopyTo(array, 0, length, out length); - return new ArraySegment(array, 0, length); - } - public static ArraySegment PeekArraySegment(this MemoryPoolIterator iter) { if (iter.IsDefault || iter.IsEnd) @@ -283,6 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure /// The iterator from which to start the known string lookup. /// A reference to a pre-allocated known string, if the input matches any. /// true if the input matches a known string, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetKnownMethod(this MemoryPoolIterator begin, out string knownMethod) { knownMethod = null; @@ -323,6 +173,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure /// The iterator from which to start the known string lookup. /// A reference to a pre-allocated known string, if the input matches any. /// true if the input matches a known string, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetKnownVersion(this MemoryPoolIterator begin, out string knownVersion) { knownVersion = null; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs index 87990788c6..220ae3d80b 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var begin = mem.GetIterator(); var end = GetIterator(begin, byteRange.Length); - var s = begin.GetAsciiString(end); + var s = begin.GetAsciiString(ref end); Assert.Equal(s.Length, byteRange.Length); @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var begin = mem.GetIterator(); var end = GetIterator(begin, byteRange.Length); - Assert.Throws(() => begin.GetAsciiString(end)); + Assert.Throws(() => begin.GetAsciiString(ref end)); pool.Return(mem); } @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var begin = mem0.GetIterator(); var end = GetIterator(begin, expectedByteRange.Length); - var s = begin.GetAsciiString(end); + var s = begin.GetAsciiString(ref end); Assert.Equal(s.Length, expectedByteRange.Length); @@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var begin = mem0.GetIterator(); var end = GetIterator(begin, expectedByteRange.Length); - var s = begin.GetAsciiString(end); + var s = begin.GetAsciiString(ref end); Assert.Equal(expectedByteRange.Length, s.Length); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs index c33c8e5168..0acfc27fe8 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs @@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var end = GetIterator(begin, rawLength); var end2 = UrlPathDecoder.Unescape(begin, end); - var result = begin.GetUtf8String(end2); + var result = begin.GetUtf8String(ref end2); Assert.Equal(expectLength, result.Length); Assert.Equal(expect, result); @@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var end = GetIterator(begin, raw.Length); var result = UrlPathDecoder.Unescape(begin, end); - Assert.Equal(expect, begin.GetUtf8String(result)); + Assert.Equal(expect, begin.GetUtf8String(ref result)); } private void PositiveAssert(MemoryPoolBlock mem, string raw) @@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var end = GetIterator(begin, raw.Length); var result = UrlPathDecoder.Unescape(begin, end); - Assert.NotEqual(raw.Length, begin.GetUtf8String(result).Length); + Assert.NotEqual(raw.Length, begin.GetUtf8String(ref result).Length); } private void NegativeAssert(MemoryPoolBlock mem, string raw) @@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var end = GetIterator(begin, raw.Length); var resultEnd = UrlPathDecoder.Unescape(begin, end); - var result = begin.GetUtf8String(resultEnd); + var result = begin.GetUtf8String(ref resultEnd); Assert.Equal(raw, result); } }