Reuse headers to prevent values moving to higher GC gens

This commit is contained in:
Ben Adams 2016-02-01 09:40:10 +00:00
parent dfcd6a6227
commit c293bbbd1a
11 changed files with 311 additions and 71 deletions

View File

@ -34,8 +34,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private static readonly byte[] _bytesHttpVersion1_1 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0");
private static readonly byte[] _bytesSpace = Encoding.ASCII.GetBytes(" ");
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
private static readonly byte[] _bytesDate = Encoding.ASCII.GetBytes("Date: ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
@ -46,8 +44,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private readonly object _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object();
protected readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders();
private readonly FrameResponseHeaders _responseHeaders = new FrameResponseHeaders();
private Headers _frameHeaders;
private Streams _frameStreams;
protected List<KeyValuePair<Func<object, Task>, object>> _onStarting;
@ -60,10 +59,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
protected CancellationTokenSource _abortedCts;
protected CancellationToken? _manuallySetRequestAbortToken;
internal FrameRequestStream _requestBody;
internal FrameResponseStream _responseBody;
internal FrameDuplexStream _duplexStream;
protected bool _responseStarted;
protected bool _keepAlive;
private bool _autoChunk;
@ -92,12 +87,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_localEndPoint = localEndPoint;
_prepareRequest = prepareRequest;
_pathBase = context.ServerAddress.PathBase;
if (ReuseStreams)
{
_requestBody = new FrameRequestStream();
_responseBody = new FrameResponseStream(this);
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
}
FrameControl = this;
Reset();
@ -197,8 +186,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
get { return _responseStarted; }
}
protected FrameRequestHeaders FrameRequestHeaders => _frameHeaders.RequestHeaders;
public Frame InitializeHeaders()
{
_frameHeaders = HttpComponentFactory.CreateHeaders(DateHeaderValueManager);
RequestHeaders = _frameHeaders.RequestHeaders;
ResponseHeaders = _frameHeaders.ResponseHeaders;
return this;
}
public void InitializeStreams(MessageBody messageBody)
{
_frameStreams = HttpComponentFactory.CreateStreams(this);
RequestBody = _frameStreams.RequestBody.StartAcceptingReads(messageBody);
ResponseBody = _frameStreams.ResponseBody.StartAcceptingWrites();
DuplexStream = _frameStreams.DuplexStream;
}
public void PauseStreams()
{
_frameStreams.RequestBody.PauseAcceptingReads();
_frameStreams.ResponseBody.PauseAcceptingWrites();
}
public void ResumeStreams()
{
_frameStreams.RequestBody.ResumeAcceptingReads();
_frameStreams.ResponseBody.ResumeAcceptingWrites();
}
public void StopStreams()
{
_frameStreams.RequestBody.StopAcceptingReads();
_frameStreams.ResponseBody.StopAcceptingWrites();
}
public void Reset()
{
ResetComponents(poolingPermitted: true);
_onStarting = null;
_onCompleted = null;
@ -207,8 +236,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_autoChunk = false;
_applicationException = null;
_requestHeaders.Reset();
ResetResponseHeaders();
ResetFeatureCollection();
Scheme = null;
@ -218,13 +245,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Path = null;
QueryString = null;
_httpVersion = HttpVersionType.Unknown;
RequestHeaders = _requestHeaders;
RequestBody = null;
StatusCode = 200;
ReasonPhrase = null;
ResponseHeaders = _responseHeaders;
ResponseBody = null;
DuplexStream = null;
var httpConnectionFeature = this as IHttpConnectionFeature;
httpConnectionFeature.RemoteIpAddress = _remoteEndPoint?.Address;
@ -239,15 +261,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_abortedCts = null;
}
public void ResetResponseHeaders()
protected void ResetComponents(bool poolingPermitted)
{
_responseHeaders.Reset();
_responseHeaders.SetRawDate(
DateHeaderValueManager.GetDateHeaderValue(),
DateHeaderValueManager.GetDateHeaderValueBytes());
_responseHeaders.SetRawServer(
"Kestrel",
_bytesServer);
if (_frameHeaders != null)
{
RequestHeaders = null;
ResponseHeaders = null;
var frameHeaders = _frameHeaders;
_frameHeaders = null;
HttpComponentFactory.DisposeHeaders(frameHeaders, poolingPermitted);
}
if (_frameStreams != null)
{
RequestBody = null;
ResponseBody = null;
DuplexStream = null;
var frameStreams = _frameStreams;
_frameStreams = null;
HttpComponentFactory.DisposeStreams(frameStreams, poolingPermitted: (poolingPermitted && ReuseStreams));
}
}
/// <summary>
@ -292,8 +325,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
_requestProcessingStopping = true;
_requestBody?.Abort();
_responseBody?.Abort();
_frameStreams?.RequestBody.Abort();
_frameStreams?.ResponseBody.Abort();
try
{
@ -560,8 +593,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
StatusCode = 500;
ReasonPhrase = null;
ResetResponseHeaders();
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
var responseHeaders = _frameHeaders.ResponseHeaders;
responseHeaders.Reset();
responseHeaders.SetRawDate(
DateHeaderValueManager.GetDateHeaderValue(),
DateHeaderValueManager.GetDateHeaderValueBytes());
responseHeaders.SetRawServer(
"Kestrel",
Headers.BytesServer);
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
ResponseHeaders = responseHeaders;
}
}
@ -615,9 +657,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
bool appCompleted)
{
var end = SocketOutput.ProducingStart();
var responseHeaders = _frameHeaders.ResponseHeaders;
if (_keepAlive)
{
foreach (var connectionValue in _responseHeaders.HeaderConnection)
foreach (var connectionValue in responseHeaders.HeaderConnection)
{
if (connectionValue.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
{
@ -626,7 +669,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
}
if (_keepAlive && !_responseHeaders.HasTransferEncoding && !_responseHeaders.HasContentLength)
if (_keepAlive && !responseHeaders.HasTransferEncoding && !responseHeaders.HasContentLength)
{
if (appCompleted)
{
@ -636,7 +679,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
// Since the app has completed and we are only now generating
// the headers we can safely set the Content-Length to 0.
_responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
}
}
else
@ -644,7 +687,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if (_httpVersion == HttpVersionType.Http1_1)
{
_autoChunk = true;
_responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
}
else
{
@ -653,18 +696,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
}
if (_keepAlive == false && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_1)
if (_keepAlive == false && responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_1)
{
_responseHeaders.SetRawConnection("close", _bytesConnectionClose);
responseHeaders.SetRawConnection("close", _bytesConnectionClose);
}
else if (_keepAlive && _responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_0)
else if (_keepAlive && responseHeaders.HasConnection == false && _httpVersion == HttpVersionType.Http1_0)
{
_responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive);
}
end.CopyFrom(_httpVersion == HttpVersionType.Http1_1 ? _bytesHttpVersion1_1 : _bytesHttpVersion1_0);
end.CopyFrom(statusBytes);
_responseHeaders.CopyTo(ref end);
responseHeaders.CopyTo(ref end);
end.CopyFrom(_bytesEndHeaders, 0, _bytesEndHeaders.Length);
SocketOutput.ProducingComplete(end);

View File

@ -3737,6 +3737,51 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
protected override void ClearFast()
{
if (((_bits & 1L) != 0)) _CacheControl = default(StringValues);
if (((_bits & 2L) != 0)) _Connection = default(StringValues);
if (((_bits & 4L) != 0)) _Date = default(StringValues);
if (((_bits & 8L) != 0)) _KeepAlive = default(StringValues);
if (((_bits & 16L) != 0)) _Pragma = default(StringValues);
if (((_bits & 32L) != 0)) _Trailer = default(StringValues);
if (((_bits & 64L) != 0)) _TransferEncoding = default(StringValues);
if (((_bits & 128L) != 0)) _Upgrade = default(StringValues);
if (((_bits & 256L) != 0)) _Via = default(StringValues);
if (((_bits & 512L) != 0)) _Warning = default(StringValues);
if (((_bits & 1024L) != 0)) _Allow = default(StringValues);
if (((_bits & 2048L) != 0)) _ContentLength = default(StringValues);
if (((_bits & 4096L) != 0)) _ContentType = default(StringValues);
if (((_bits & 8192L) != 0)) _ContentEncoding = default(StringValues);
if (((_bits & 16384L) != 0)) _ContentLanguage = default(StringValues);
if (((_bits & 32768L) != 0)) _ContentLocation = default(StringValues);
if (((_bits & 65536L) != 0)) _ContentMD5 = default(StringValues);
if (((_bits & 131072L) != 0)) _ContentRange = default(StringValues);
if (((_bits & 262144L) != 0)) _Expires = default(StringValues);
if (((_bits & 524288L) != 0)) _LastModified = default(StringValues);
if (((_bits & 1048576L) != 0)) _Accept = default(StringValues);
if (((_bits & 2097152L) != 0)) _AcceptCharset = default(StringValues);
if (((_bits & 4194304L) != 0)) _AcceptEncoding = default(StringValues);
if (((_bits & 8388608L) != 0)) _AcceptLanguage = default(StringValues);
if (((_bits & 16777216L) != 0)) _Authorization = default(StringValues);
if (((_bits & 33554432L) != 0)) _Cookie = default(StringValues);
if (((_bits & 67108864L) != 0)) _Expect = default(StringValues);
if (((_bits & 134217728L) != 0)) _From = default(StringValues);
if (((_bits & 268435456L) != 0)) _Host = default(StringValues);
if (((_bits & 536870912L) != 0)) _IfMatch = default(StringValues);
if (((_bits & 1073741824L) != 0)) _IfModifiedSince = default(StringValues);
if (((_bits & 2147483648L) != 0)) _IfNoneMatch = default(StringValues);
if (((_bits & 4294967296L) != 0)) _IfRange = default(StringValues);
if (((_bits & 8589934592L) != 0)) _IfUnmodifiedSince = default(StringValues);
if (((_bits & 17179869184L) != 0)) _MaxForwards = default(StringValues);
if (((_bits & 34359738368L) != 0)) _ProxyAuthorization = default(StringValues);
if (((_bits & 68719476736L) != 0)) _Referer = default(StringValues);
if (((_bits & 137438953472L) != 0)) _Range = default(StringValues);
if (((_bits & 274877906944L) != 0)) _TE = default(StringValues);
if (((_bits & 549755813888L) != 0)) _Translate = default(StringValues);
if (((_bits & 1099511627776L) != 0)) _UserAgent = default(StringValues);
if (((_bits & 2199023255552L) != 0)) _Origin = default(StringValues);
if (((_bits & 4398046511104L) != 0)) _AccessControlRequestMethod = default(StringValues);
if (((_bits & 8796093022208L) != 0)) _AccessControlRequestHeaders = default(StringValues);
_bits = 0;
MaybeUnknown?.Clear();
}
@ -8618,6 +8663,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
protected override void ClearFast()
{
_bits = 0;
MaybeUnknown?.Clear();
}

View File

@ -52,7 +52,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
await SocketInput;
}
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders))
InitializeHeaders();
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, FrameRequestHeaders))
{
if (SocketInput.RemoteIntakeFin)
{
@ -63,20 +65,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if (!_requestProcessingStopping)
{
var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this);
var messageBody = MessageBody.For(HttpVersion, FrameRequestHeaders, this);
_keepAlive = messageBody.RequestKeepAlive;
// _duplexStream may be null if flag switched while running
if (!ReuseStreams || _duplexStream == null)
{
_requestBody = new FrameRequestStream();
_responseBody = new FrameResponseStream(this);
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
}
RequestBody = _requestBody.StartAcceptingReads(messageBody);
ResponseBody = _responseBody.StartAcceptingWrites();
DuplexStream = _duplexStream;
InitializeStreams(messageBody);
_abortedCts = null;
_manuallySetRequestAbortToken = null;
@ -101,8 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
await FireOnStarting();
}
_requestBody.PauseAcceptingReads();
_responseBody.PauseAcceptingWrites();
PauseStreams();
if (_onCompleted != null)
{
@ -114,23 +105,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
// If _requestAbort is set, the connection has already been closed.
if (Volatile.Read(ref _requestAborted) == 0)
{
_responseBody.ResumeAcceptingWrites();
ResumeStreams();
await ProduceEnd();
if (_keepAlive)
{
_requestBody.ResumeAcceptingReads();
// Finish reading the request body in case the app did not.
await messageBody.Consume();
}
}
_requestBody.StopAcceptingReads();
_responseBody.StopAcceptingWrites();
StopStreams();
}
if (!_keepAlive)
{
ResetComponents(poolingPermitted: true);
return;
}
}
@ -144,6 +135,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
finally
{
// Error occurred, do not return components to pool
ResetComponents(poolingPermitted: false);
try
{
_abortedCts = null;

View File

@ -11,12 +11,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
class FrameResponseStream : Stream
{
private readonly FrameContext _context;
private FrameContext _context;
private FrameStreamState _state;
public FrameResponseStream(FrameContext context)
public FrameResponseStream()
{
_context = context;
_state = FrameStreamState.Closed;
}
@ -125,6 +124,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
}
public void Initialize(FrameContext context)
{
_context = context;
}
public void Uninitialize()
{
_context = null;
_state = FrameStreamState.Closed;
}
private Task ValidateState(CancellationToken cancellationToken)
{
switch (_state)

View File

@ -0,0 +1,116 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using Microsoft.AspNetCore.Server.Kestrel.Http;
using System.Text;
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
{
class HttpComponentFactory : IHttpComponentFactory
{
private const int _maxPooledComponents = 128;
private ConcurrentQueue<Streams> _streamPool = new ConcurrentQueue<Streams>();
private ConcurrentQueue<Headers> _headerPool = new ConcurrentQueue<Headers>();
public Streams CreateStreams(FrameContext owner)
{
Streams streams;
if (!_streamPool.TryDequeue(out streams))
{
streams = new Streams();
}
streams.Initialize(owner);
return streams;
}
public void DisposeStreams(Streams streams, bool poolingPermitted)
{
if (poolingPermitted && _streamPool.Count < _maxPooledComponents)
{
streams.Uninitialize();
_streamPool.Enqueue(streams);
}
}
public Headers CreateHeaders(DateHeaderValueManager dateValueManager)
{
Headers headers;
if (!_headerPool.TryDequeue(out headers))
{
headers = new Headers();
}
headers.Initialize(dateValueManager);
return headers;
}
public void DisposeHeaders(Headers headers, bool poolingPermitted)
{
if (poolingPermitted && _headerPool.Count < _maxPooledComponents)
{
headers.Uninitialize();
_headerPool.Enqueue(headers);
}
}
}
internal class Headers
{
public static readonly byte[] BytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
public readonly FrameRequestHeaders RequestHeaders = new FrameRequestHeaders();
public readonly FrameResponseHeaders ResponseHeaders = new FrameResponseHeaders();
public Headers()
{
RequestHeaders = new FrameRequestHeaders();
ResponseHeaders = new FrameResponseHeaders();
}
public void Initialize(DateHeaderValueManager dateValueManager)
{
ResponseHeaders.SetRawDate(
dateValueManager.GetDateHeaderValue(),
dateValueManager.GetDateHeaderValueBytes());
ResponseHeaders.SetRawServer("Kestrel", BytesServer);
}
public void Uninitialize()
{
RequestHeaders.Reset();
ResponseHeaders.Reset();
}
}
internal class Streams
{
public readonly FrameRequestStream RequestBody;
public readonly FrameResponseStream ResponseBody;
public readonly FrameDuplexStream DuplexStream;
public Streams()
{
RequestBody = new FrameRequestStream();
ResponseBody = new FrameResponseStream();
DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody);
}
public void Initialize(FrameContext renter)
{
ResponseBody.Initialize(renter);
}
public void Uninitialize()
{
ResponseBody.Uninitialize();
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Server.Kestrel.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
{
interface IHttpComponentFactory
{
Streams CreateStreams(FrameContext owner);
void DisposeStreams(Streams streams, bool poolingPermitted);
Headers CreateHeaders(DateHeaderValueManager dateValueManager);
void DisposeHeaders(Headers headers, bool poolingPermitted);
}
}

View File

@ -56,6 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
var information = (KestrelServerInformation)Features.Get<IKestrelServerInformation>();
var dateHeaderValueManager = new DateHeaderValueManager();
var trace = new KestrelTrace(_logger);
var componentFactory = new HttpComponentFactory();
var engine = new KestrelEngine(new ServiceContext
{
FrameFactory = (context, remoteEP, localEP, prepareRequest) =>
@ -68,7 +69,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
DateHeaderValueManager = dateHeaderValueManager,
ConnectionFilter = information.ConnectionFilter,
NoDelay = information.NoDelay,
ReuseStreams = information.ReuseStreams
ReuseStreams = information.ReuseStreams,
HttpComponentFactory = componentFactory
});
_disposables.Push(engine);

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
ConnectionFilter = context.ConnectionFilter;
NoDelay = context.NoDelay;
ReuseStreams = context.ReuseStreams;
HttpComponentFactory = context.HttpComponentFactory;
}
public IApplicationLifetime AppLifetime { get; set; }
@ -44,5 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public bool NoDelay { get; set; }
public bool ReuseStreams { get; set; }
internal IHttpComponentFactory HttpComponentFactory { get; set; }
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Http;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Primitives;
using Xunit;
@ -18,9 +19,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var connectionContext = new ConnectionContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
HttpComponentFactory = new HttpComponentFactory()
};
var frame = new Frame<object>(application: null, context: connectionContext);
var frame = new Frame<object>(application: null, context: connectionContext)
.InitializeHeaders();
IDictionary<string, StringValues> headers = frame.ResponseHeaders;
Assert.Equal(2, headers.Count);
@ -46,10 +50,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var connectionContext = new ConnectionContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
HttpComponentFactory = new HttpComponentFactory()
};
var frame = new Frame<object>(application: null, context: connectionContext);
var frame = new Frame<object>(application: null, context: connectionContext)
.InitializeHeaders();
Assert.True(frame.ResponseHeaders.Count > 0);
frame.ResponseHeaders.Clear();

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Log = new TestKestrelTrace();
ThreadPool = new LoggingThreadPool(Log);
DateHeaderValueManager = new TestDateHeaderValueManager();
HttpComponentFactory = new HttpComponentFactory();
}
public RequestDelegate App

View File

@ -366,7 +366,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
return MaybeUnknown?.Remove(key) ?? false;
}}
protected override void ClearFast()
{{
{{{(loop.ClassName != "FrameRequestHeaders" ? "" : Each(loop.Headers, header => $@"
if ({header.TestBit()}) _{header.Identifier} = default(StringValues);"))}
_bits = 0;
MaybeUnknown?.Clear();
}}