Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Request header StringCache #411

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,25 @@ public abstract partial class Frame : FrameContext, IFrameControl
private readonly Action<IFeatureCollection> _prepareRequest;

private readonly string _pathBase;
protected readonly IStringCache _stringCache;

public Frame(ConnectionContext context)
: this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
: this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null, stringCache: null)
{
}

public Frame(ConnectionContext context,
IPEndPoint remoteEndPoint,
IPEndPoint localEndPoint,
Action<IFeatureCollection> prepareRequest)
Action<IFeatureCollection> prepareRequest,
IStringCache stringCache)
: base(context)
{
_remoteEndPoint = remoteEndPoint;
_localEndPoint = localEndPoint;
_prepareRequest = prepareRequest;
_pathBase = context.ServerAddress.PathBase;
_stringCache = stringCache;

FrameControl = this;
Reset();
Expand Down Expand Up @@ -702,7 +705,7 @@ protected bool TakeStartLine(SocketInput input)
{
return false;
}
var method = begin.GetAsciiString(scan);
var method = begin.GetAsciiString(scan, _stringCache);

scan.Take();
begin = scan;
Expand All @@ -726,7 +729,7 @@ protected bool TakeStartLine(SocketInput input)
{
return false;
}
queryString = begin.GetAsciiString(scan);
queryString = begin.GetAsciiString(scan, _stringCache);
}

scan.Take();
Expand All @@ -735,7 +738,7 @@ protected bool TakeStartLine(SocketInput input)
{
return false;
}
var httpVersion = begin.GetAsciiString(scan);
var httpVersion = begin.GetAsciiString(scan, _stringCache);

scan.Take();
if (scan.Take() != '\n')
Expand All @@ -756,7 +759,7 @@ protected bool TakeStartLine(SocketInput input)
else
{
// URI wasn't encoded, parse as ASCII
requestUrlPath = pathBegin.GetAsciiString(pathEnd);
requestUrlPath = pathBegin.GetAsciiString(pathEnd, _stringCache);
}

consumed = scan;
Expand Down Expand Up @@ -809,7 +812,7 @@ private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatche
return true;
}

public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders, IStringCache stringCache)
{
var scan = input.ConsumingStart();
var consumed = scan;
Expand Down Expand Up @@ -900,7 +903,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
}

var name = beginName.GetArraySegment(endName);
var value = beginValue.GetAsciiString(endValue);
var value = beginValue.GetAsciiString(endValue, stringCache);
if (wrapping)
{
value = value.Replace("\r\n", " ");
Expand Down
12 changes: 8 additions & 4 deletions src/Microsoft.AspNet.Server.Kestrel/Http/FrameOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNet.Server.Kestrel.Http
Expand All @@ -16,16 +17,17 @@ public class Frame<TContext> : Frame

public Frame(IHttpApplication<TContext> application,
ConnectionContext context)
: this(application, context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
: this(application, context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null, stringCache: null)
{
}

public Frame(IHttpApplication<TContext> application,
ConnectionContext context,
IPEndPoint remoteEndPoint,
IPEndPoint localEndPoint,
Action<IFeatureCollection> prepareRequest)
: base(context, remoteEndPoint, localEndPoint, prepareRequest)
Action<IFeatureCollection> prepareRequest,
IStringCache stringCache)
: base(context, remoteEndPoint, localEndPoint, prepareRequest, stringCache)
{
_application = application;
}
Expand All @@ -42,6 +44,8 @@ public override async Task RequestProcessingAsync()
{
while (!_requestProcessingStopping)
{
_stringCache?.MarkStart();

while (!_requestProcessingStopping && !TakeStartLine(SocketInput))
{
if (SocketInput.RemoteIntakeFin)
Expand All @@ -51,7 +55,7 @@ public override async Task RequestProcessingAsync()
await SocketInput;
}

while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders))
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders, _stringCache))
{
if (SocketInput.RemoteIntakeFin)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ public interface IKestrelServerInformation

bool NoDelay { get; set; }

bool StringCacheOnConnection { get; set; }

int StringCacheMaxStrings { get; set; }

int StringCacheMaxStringLength { get; set; }

IConnectionFilter ConnectionFilter { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
{
public interface IStringCache
{
void MarkStart();
unsafe string GetString(char* data, uint hash, int length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,80 +11,128 @@ public static class MemoryPoolIterator2Extensions
private const int _maxStackAllocBytes = 16384;

private static Encoding _utf8 = Encoding.UTF8;
private static uint _startHash;

private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset, int length)
static MemoryPoolIterator2Extensions()
{
using (var rnd = System.Security.Cryptography.RandomNumberGenerator.Create())
{
var randomBytes = new byte[8];
rnd.GetBytes(randomBytes);
_startHash =
(randomBytes[0]) |
(((uint)randomBytes[1]) << 8) |
(((uint)randomBytes[2]) << 16) |
(((uint)randomBytes[3]) << 24);
}
}

private static unsafe string GetAsciiStringStack(byte* input, int length, IStringCache stringCache)
{
// avoid declaring other local vars, or doing work with stackalloc
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
char* output = stackalloc char[length];

return GetAsciiStringImplementation(output, input, inputOffset, length);
return GetAsciiStringImplementation(output, input, length, stringCache);
}
private static unsafe string GetAsciiStringImplementation(char* output, byte[] input, int inputOffset, int length)
private static unsafe string GetAsciiStringImplementation(char* outputStart, byte* input, int length, IStringCache stringCache)
{
for (var i = 0; i < length; i++)
var hash = _startHash;

var output = outputStart;
var i = 0;
var lengthMinusSpan = length - 3;
for (; i < lengthMinusSpan; i += 4)
{
// span hashing with fix https://github.com/dotnet/corefxlab/pull/455
hash = hash * 31 + *((uint*)input);

*(output) = (char)*(input);
*(output + 1) = (char)*(input + 1);
*(output + 2) = (char)*(input + 2);
*(output + 3) = (char)*(input + 3);
output += 4;
input += 4;
}
for (; i < length; i++)
{
output[i] = (char)input[inputOffset + i];
hash = hash * 31 + *((char*)input);
*(output++) = (char)*(input++);
}

return new string(output, 0, length);
return stringCache?.GetString(outputStart, hash, length) ?? new string(outputStart, 0, length);
}

private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length)
private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, IStringCache stringCache)
{
// avoid declaring other local vars, or doing work with stackalloc
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
char* output = stackalloc char[length];

return GetAsciiStringImplementation(output, start, end, inputOffset, length);
return GetAsciiStringImplementation(output, start, end, inputOffset, length, stringCache);
}

private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length)
private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, IStringCache stringCache)
{
var buffer = new char[length];

fixed (char* output = buffer)
{
return GetAsciiStringImplementation(output, start, end, inputOffset, length);
return GetAsciiStringImplementation(output, start, end, inputOffset, length, stringCache);
}
}

private static unsafe string GetAsciiStringImplementation(char* output, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length)
private static unsafe string GetAsciiStringImplementation(char* outputStart, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, IStringCache stringCache)
{
var outputOffset = 0;
var hash = _startHash;

var output = outputStart;

var block = start;
var remaining = length;

var endBlock = end.Block;
var endIndex = end.Index;

while (true)
while (remaining > 0)
{
int following = (block != endBlock ? block.End : endIndex) - inputOffset;

if (following > 0)
{
var input = block.Array;
for (var i = 0; i < following; i++)
fixed (byte* blockStart = block.Array)
{
output[i + outputOffset] = (char)input[i + inputOffset];
}
var input = blockStart + inputOffset;
var i = 0;
var followingMinusSpan = following - 3;
for (; i < followingMinusSpan; i += 4)
{
// span hashing with fix https://github.com/dotnet/corefxlab/pull/455
hash = hash * 31 + *((uint*)input);

*(output) = (char)*(input);
*(output + 1) = (char)*(input + 1);
*(output + 2) = (char)*(input + 2);
*(output + 3) = (char)*(input + 3);
output += 4;
input += 4;
}
for (; i < following; i++)
{
hash = hash * 31 + *((char*)input);
*(output++) = (char)*(input++);
}
}
remaining -= following;
outputOffset += following;
}

if (remaining == 0)
{
return new string(output, 0, length);
}

block = block.Next;
inputOffset = block.Start;
inputOffset = block?.Start??0;
}
return stringCache?.GetString(outputStart, hash, length) ?? new string(outputStart, 0, length);
}

public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end)
public unsafe static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end, IStringCache stringCache)
{
if (start.IsDefault || end.IsDefault)
{
Expand All @@ -93,20 +141,28 @@ public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIt

var length = start.GetLength(end);

if (length <= 0)
{
return default(string);
}

// Bytes out of the range of ascii are treated as "opaque data"
// and kept in string as a char value that casts to same input byte value
// https://tools.ietf.org/html/rfc7230#section-3.2.4
if (end.Block == start.Block)
{
return GetAsciiStringStack(start.Block.Array, start.Index, length);
fixed (byte* input = start.Block.Array)
{
return GetAsciiStringStack(input + start.Index, length, stringCache);
}
}

if (length > _maxStackAllocBytes)
{
return GetAsciiStringHeap(start.Block, end, start.Index, length);
return GetAsciiStringHeap(start.Block, end, start.Index, length, stringCache);
}

return GetAsciiStringStack(start.Block, end, start.Index, length);
return GetAsciiStringStack(start.Block, end, start.Index, length, stringCache);
}

public static string GetUtf8String(this MemoryPoolIterator2 start, MemoryPoolIterator2 end)
Expand All @@ -120,9 +176,14 @@ public static string GetUtf8String(this MemoryPoolIterator2 start, MemoryPoolIte
return _utf8.GetString(start.Block.Array, start.Index, end.Index - start.Index);
}

var decoder = _utf8.GetDecoder();

var length = start.GetLength(end);

if (length <= 0)
{
return default(string);
}

var decoder = _utf8.GetDecoder();
var charLength = length * 2;
var chars = new char[charLength];
var charIndex = 0;
Expand Down
Loading