From c0f88ebdc1fa5f42043d38152f5a0992afad842a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 23 Feb 2018 00:44:38 +0000 Subject: [PATCH] Faster IFeatureCollection.Get (#2290) --- .../HttpProtocolFeatureCollection.cs | 96 ++-- .../Internal/Http/Http1Connection.cs | 2 +- .../Http/HttpProtocol.FeatureCollection.cs | 14 +- .../Internal/Http/HttpProtocol.Generated.cs | 440 +++++++++++++----- .../Internal/Http2/Http2Stream.cs | 2 +- .../Internal/TransportConnection.Features.cs | 130 ++++-- .../HttpProtocolFeatureCollectionTests.cs | 224 +++++++++ .../HttpProtocolFeatureCollection.cs | 106 ++++- 8 files changed, 755 insertions(+), 259 deletions(-) create mode 100644 test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs diff --git a/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs index ddf7fa0554..69f07faf3e 100644 --- a/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs +++ b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs @@ -4,6 +4,9 @@ using System; using System.Buffers; using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -14,65 +17,63 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { public class HttpProtocolFeatureCollection { - private readonly Http1Connection _http1Connection; - private IFeatureCollection _collection; + private readonly IFeatureCollection _collection; - [Benchmark(Baseline = true)] - public IHttpRequestFeature GetFirstViaFastFeature() + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpRequestFeature GetViaTypeOf_First() { - return (IHttpRequestFeature)GetFastFeature(typeof(IHttpRequestFeature)); + return (IHttpRequestFeature)_collection[typeof(IHttpRequestFeature)]; } [Benchmark] - public IHttpRequestFeature GetFirstViaType() - { - return (IHttpRequestFeature)Get(typeof(IHttpRequestFeature)); - } - - [Benchmark] - public IHttpRequestFeature GetFirstViaExtension() - { - return _collection.GetType(); - } - - [Benchmark] - public IHttpRequestFeature GetFirstViaGeneric() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpRequestFeature GetViaGeneric_First() { return _collection.Get(); } [Benchmark] - public IHttpSendFileFeature GetLastViaFastFeature() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpSendFileFeature GetViaTypeOf_Last() { - return (IHttpSendFileFeature)GetFastFeature(typeof(IHttpSendFileFeature)); + return (IHttpSendFileFeature)_collection[typeof(IHttpSendFileFeature)]; } [Benchmark] - public IHttpSendFileFeature GetLastViaType() - { - return (IHttpSendFileFeature)Get(typeof(IHttpSendFileFeature)); - } - - [Benchmark] - public IHttpSendFileFeature GetLastViaExtension() - { - return _collection.GetType(); - } - - [Benchmark] - public IHttpSendFileFeature GetLastViaGeneric() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpSendFileFeature GetViaGeneric_Last() { return _collection.Get(); } - private object Get(Type type) + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaTypeOf_Custom() { - return _collection[type]; + return (IHttpCustomFeature)_collection[typeof(IHttpCustomFeature)]; } - private object GetFastFeature(Type type) + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaGeneric_Custom() { - return _http1Connection.FastFeatureGet(type); + return _collection.Get(); + } + + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaTypeOf_NotFound() + { + return (IHttpNotFoundFeature)_collection[typeof(IHttpNotFoundFeature)]; + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaGeneric_NotFound() + { + return _collection.Get(); } public HttpProtocolFeatureCollection() @@ -99,21 +100,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance http1Connection.Reset(); - _http1Connection = http1Connection; + _collection = http1Connection; } - [IterationSetup] - public void Setup() + private class SendFileFeature : IHttpSendFileFeature { - _collection = _http1Connection; + public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation) + { + throw new NotImplementedException(); + } } - } - public static class IFeatureCollectionExtensions - { - public static T GetType(this IFeatureCollection collection) + private interface IHttpCustomFeature + { + } + + private interface IHttpNotFoundFeature { - return (T)collection[typeof(T)]; } } + } diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.cs index a09b17e139..a66179fa4a 100644 --- a/src/Kestrel.Core/Internal/Http/Http1Connection.cs +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs @@ -400,7 +400,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected override void OnReset() { - FastFeatureSet(typeof(IHttpUpgradeFeature), this); + ResetIHttpUpgradeFeature(); _requestTimedOut = false; _requestTargetForm = HttpRequestTarget.Unknown; diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs index e4fe29168f..cc0337a833 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -243,20 +243,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http set => MinResponseDataRate = value; } - object IFeatureCollection.this[Type key] + protected void ResetIHttpUpgradeFeature() { - get => FastFeatureGet(key) ?? ConnectionFeatures[key]; - set => FastFeatureSet(key, value); + _currentIHttpUpgradeFeature = this; } - TFeature IFeatureCollection.Get() + protected void ResetIHttp2StreamIdFeature() { - return (TFeature)(FastFeatureGet(typeof(TFeature)) ?? ConnectionFeatures[typeof(TFeature)]); - } - - void IFeatureCollection.Set(TFeature instance) - { - FastFeatureSet(typeof(TFeature), instance); + _currentIHttp2StreamIdFeature = this; } void IHttpResponseFeature.OnStarting(Func callback, object state) diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs index 319d36865b..3bac0a0778 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs @@ -82,205 +82,389 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _currentIHttpSendFileFeature = null; } - internal object FastFeatureGet(Type key) + object IFeatureCollection.this[Type key] { - if (key == IHttpRequestFeatureType) + get { - return _currentIHttpRequestFeature; + object feature = null; + if (key == IHttpRequestFeatureType) + { + feature = _currentIHttpRequestFeature; + } + else if (key == IHttpResponseFeatureType) + { + feature = _currentIHttpResponseFeature; + } + else if (key == IHttpRequestIdentifierFeatureType) + { + feature = _currentIHttpRequestIdentifierFeature; + } + else if (key == IServiceProvidersFeatureType) + { + feature = _currentIServiceProvidersFeature; + } + else if (key == IHttpRequestLifetimeFeatureType) + { + feature = _currentIHttpRequestLifetimeFeature; + } + else if (key == IHttpConnectionFeatureType) + { + feature = _currentIHttpConnectionFeature; + } + else if (key == IHttpAuthenticationFeatureType) + { + feature = _currentIHttpAuthenticationFeature; + } + else if (key == IQueryFeatureType) + { + feature = _currentIQueryFeature; + } + else if (key == IFormFeatureType) + { + feature = _currentIFormFeature; + } + else if (key == IHttpUpgradeFeatureType) + { + feature = _currentIHttpUpgradeFeature; + } + else if (key == IHttp2StreamIdFeatureType) + { + feature = _currentIHttp2StreamIdFeature; + } + else if (key == IResponseCookiesFeatureType) + { + feature = _currentIResponseCookiesFeature; + } + else if (key == IItemsFeatureType) + { + feature = _currentIItemsFeature; + } + else if (key == ITlsConnectionFeatureType) + { + feature = _currentITlsConnectionFeature; + } + else if (key == IHttpWebSocketFeatureType) + { + feature = _currentIHttpWebSocketFeature; + } + else if (key == ISessionFeatureType) + { + feature = _currentISessionFeature; + } + else if (key == IHttpMaxRequestBodySizeFeatureType) + { + feature = _currentIHttpMaxRequestBodySizeFeature; + } + else if (key == IHttpMinRequestBodyDataRateFeatureType) + { + feature = _currentIHttpMinRequestBodyDataRateFeature; + } + else if (key == IHttpMinResponseDataRateFeatureType) + { + feature = _currentIHttpMinResponseDataRateFeature; + } + else if (key == IHttpBodyControlFeatureType) + { + feature = _currentIHttpBodyControlFeature; + } + else if (key == IHttpSendFileFeatureType) + { + feature = _currentIHttpSendFileFeature; + } + else if (MaybeExtra != null) + { + feature = ExtraFeatureGet(key); + } + + return feature ?? ConnectionFeatures[key]; } - if (key == IHttpResponseFeatureType) + + set { - return _currentIHttpResponseFeature; + _featureRevision++; + + if (key == IHttpRequestFeatureType) + { + _currentIHttpRequestFeature = value; + } + else if (key == IHttpResponseFeatureType) + { + _currentIHttpResponseFeature = value; + } + else if (key == IHttpRequestIdentifierFeatureType) + { + _currentIHttpRequestIdentifierFeature = value; + } + else if (key == IServiceProvidersFeatureType) + { + _currentIServiceProvidersFeature = value; + } + else if (key == IHttpRequestLifetimeFeatureType) + { + _currentIHttpRequestLifetimeFeature = value; + } + else if (key == IHttpConnectionFeatureType) + { + _currentIHttpConnectionFeature = value; + } + else if (key == IHttpAuthenticationFeatureType) + { + _currentIHttpAuthenticationFeature = value; + } + else if (key == IQueryFeatureType) + { + _currentIQueryFeature = value; + } + else if (key == IFormFeatureType) + { + _currentIFormFeature = value; + } + else if (key == IHttpUpgradeFeatureType) + { + _currentIHttpUpgradeFeature = value; + } + else if (key == IHttp2StreamIdFeatureType) + { + _currentIHttp2StreamIdFeature = value; + } + else if (key == IResponseCookiesFeatureType) + { + _currentIResponseCookiesFeature = value; + } + else if (key == IItemsFeatureType) + { + _currentIItemsFeature = value; + } + else if (key == ITlsConnectionFeatureType) + { + _currentITlsConnectionFeature = value; + } + else if (key == IHttpWebSocketFeatureType) + { + _currentIHttpWebSocketFeature = value; + } + else if (key == ISessionFeatureType) + { + _currentISessionFeature = value; + } + else if (key == IHttpMaxRequestBodySizeFeatureType) + { + _currentIHttpMaxRequestBodySizeFeature = value; + } + else if (key == IHttpMinRequestBodyDataRateFeatureType) + { + _currentIHttpMinRequestBodyDataRateFeature = value; + } + else if (key == IHttpMinResponseDataRateFeatureType) + { + _currentIHttpMinResponseDataRateFeature = value; + } + else if (key == IHttpBodyControlFeatureType) + { + _currentIHttpBodyControlFeature = value; + } + else if (key == IHttpSendFileFeatureType) + { + _currentIHttpSendFileFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } } - if (key == IHttpRequestIdentifierFeatureType) - { - return _currentIHttpRequestIdentifierFeature; - } - if (key == IServiceProvidersFeatureType) - { - return _currentIServiceProvidersFeature; - } - if (key == IHttpRequestLifetimeFeatureType) - { - return _currentIHttpRequestLifetimeFeature; - } - if (key == IHttpConnectionFeatureType) - { - return _currentIHttpConnectionFeature; - } - if (key == IHttpAuthenticationFeatureType) - { - return _currentIHttpAuthenticationFeature; - } - if (key == IQueryFeatureType) - { - return _currentIQueryFeature; - } - if (key == IFormFeatureType) - { - return _currentIFormFeature; - } - if (key == IHttpUpgradeFeatureType) - { - return _currentIHttpUpgradeFeature; - } - if (key == IHttp2StreamIdFeatureType) - { - return _currentIHttp2StreamIdFeature; - } - if (key == IResponseCookiesFeatureType) - { - return _currentIResponseCookiesFeature; - } - if (key == IItemsFeatureType) - { - return _currentIItemsFeature; - } - if (key == ITlsConnectionFeatureType) - { - return _currentITlsConnectionFeature; - } - if (key == IHttpWebSocketFeatureType) - { - return _currentIHttpWebSocketFeature; - } - if (key == ISessionFeatureType) - { - return _currentISessionFeature; - } - if (key == IHttpMaxRequestBodySizeFeatureType) - { - return _currentIHttpMaxRequestBodySizeFeature; - } - if (key == IHttpMinRequestBodyDataRateFeatureType) - { - return _currentIHttpMinRequestBodyDataRateFeature; - } - if (key == IHttpMinResponseDataRateFeatureType) - { - return _currentIHttpMinResponseDataRateFeature; - } - if (key == IHttpBodyControlFeatureType) - { - return _currentIHttpBodyControlFeature; - } - if (key == IHttpSendFileFeatureType) - { - return _currentIHttpSendFileFeature; - } - return ExtraFeatureGet(key); } - protected void FastFeatureSet(Type key, object feature) + void IFeatureCollection.Set(TFeature feature) { _featureRevision++; - - if (key == IHttpRequestFeatureType) + if (typeof(TFeature) == typeof(IHttpRequestFeature)) { _currentIHttpRequestFeature = feature; - return; } - if (key == IHttpResponseFeatureType) + else if (typeof(TFeature) == typeof(IHttpResponseFeature)) { _currentIHttpResponseFeature = feature; - return; } - if (key == IHttpRequestIdentifierFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature)) { _currentIHttpRequestIdentifierFeature = feature; - return; } - if (key == IServiceProvidersFeatureType) + else if (typeof(TFeature) == typeof(IServiceProvidersFeature)) { _currentIServiceProvidersFeature = feature; - return; } - if (key == IHttpRequestLifetimeFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature)) { _currentIHttpRequestLifetimeFeature = feature; - return; } - if (key == IHttpConnectionFeatureType) + else if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { _currentIHttpConnectionFeature = feature; - return; } - if (key == IHttpAuthenticationFeatureType) + else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { _currentIHttpAuthenticationFeature = feature; - return; } - if (key == IQueryFeatureType) + else if (typeof(TFeature) == typeof(IQueryFeature)) { _currentIQueryFeature = feature; - return; } - if (key == IFormFeatureType) + else if (typeof(TFeature) == typeof(IFormFeature)) { _currentIFormFeature = feature; - return; } - if (key == IHttpUpgradeFeatureType) + else if (typeof(TFeature) == typeof(IHttpUpgradeFeature)) { _currentIHttpUpgradeFeature = feature; - return; } - if (key == IHttp2StreamIdFeatureType) + else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature)) { _currentIHttp2StreamIdFeature = feature; - return; } - if (key == IResponseCookiesFeatureType) + else if (typeof(TFeature) == typeof(IResponseCookiesFeature)) { _currentIResponseCookiesFeature = feature; - return; } - if (key == IItemsFeatureType) + else if (typeof(TFeature) == typeof(IItemsFeature)) { _currentIItemsFeature = feature; - return; } - if (key == ITlsConnectionFeatureType) + else if (typeof(TFeature) == typeof(ITlsConnectionFeature)) { _currentITlsConnectionFeature = feature; - return; } - if (key == IHttpWebSocketFeatureType) + else if (typeof(TFeature) == typeof(IHttpWebSocketFeature)) { _currentIHttpWebSocketFeature = feature; - return; } - if (key == ISessionFeatureType) + else if (typeof(TFeature) == typeof(ISessionFeature)) { _currentISessionFeature = feature; - return; } - if (key == IHttpMaxRequestBodySizeFeatureType) + else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature)) { _currentIHttpMaxRequestBodySizeFeature = feature; - return; } - if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature)) { _currentIHttpMinRequestBodyDataRateFeature = feature; - return; } - if (key == IHttpMinResponseDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature)) { _currentIHttpMinResponseDataRateFeature = feature; - return; } - if (key == IHttpBodyControlFeatureType) + else if (typeof(TFeature) == typeof(IHttpBodyControlFeature)) { _currentIHttpBodyControlFeature = feature; - return; } - if (key == IHttpSendFileFeatureType) + else if (typeof(TFeature) == typeof(IHttpSendFileFeature)) { _currentIHttpSendFileFeature = feature; - return; - }; - ExtraFeatureSet(key, feature); + } + else + { + ExtraFeatureSet(typeof(TFeature), feature); + } + } + + TFeature IFeatureCollection.Get() + { + TFeature feature = default; + if (typeof(TFeature) == typeof(IHttpRequestFeature)) + { + feature = (TFeature)_currentIHttpRequestFeature; + } + else if (typeof(TFeature) == typeof(IHttpResponseFeature)) + { + feature = (TFeature)_currentIHttpResponseFeature; + } + else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature)) + { + feature = (TFeature)_currentIHttpRequestIdentifierFeature; + } + else if (typeof(TFeature) == typeof(IServiceProvidersFeature)) + { + feature = (TFeature)_currentIServiceProvidersFeature; + } + else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature)) + { + feature = (TFeature)_currentIHttpRequestLifetimeFeature; + } + else if (typeof(TFeature) == typeof(IHttpConnectionFeature)) + { + feature = (TFeature)_currentIHttpConnectionFeature; + } + else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) + { + feature = (TFeature)_currentIHttpAuthenticationFeature; + } + else if (typeof(TFeature) == typeof(IQueryFeature)) + { + feature = (TFeature)_currentIQueryFeature; + } + else if (typeof(TFeature) == typeof(IFormFeature)) + { + feature = (TFeature)_currentIFormFeature; + } + else if (typeof(TFeature) == typeof(IHttpUpgradeFeature)) + { + feature = (TFeature)_currentIHttpUpgradeFeature; + } + else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature)) + { + feature = (TFeature)_currentIHttp2StreamIdFeature; + } + else if (typeof(TFeature) == typeof(IResponseCookiesFeature)) + { + feature = (TFeature)_currentIResponseCookiesFeature; + } + else if (typeof(TFeature) == typeof(IItemsFeature)) + { + feature = (TFeature)_currentIItemsFeature; + } + else if (typeof(TFeature) == typeof(ITlsConnectionFeature)) + { + feature = (TFeature)_currentITlsConnectionFeature; + } + else if (typeof(TFeature) == typeof(IHttpWebSocketFeature)) + { + feature = (TFeature)_currentIHttpWebSocketFeature; + } + else if (typeof(TFeature) == typeof(ISessionFeature)) + { + feature = (TFeature)_currentISessionFeature; + } + else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature)) + { + feature = (TFeature)_currentIHttpMaxRequestBodySizeFeature; + } + else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature)) + { + feature = (TFeature)_currentIHttpMinRequestBodyDataRateFeature; + } + else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature)) + { + feature = (TFeature)_currentIHttpMinResponseDataRateFeature; + } + else if (typeof(TFeature) == typeof(IHttpBodyControlFeature)) + { + feature = (TFeature)_currentIHttpBodyControlFeature; + } + else if (typeof(TFeature) == typeof(IHttpSendFileFeature)) + { + feature = (TFeature)_currentIHttpSendFileFeature; + } + else if (MaybeExtra != null) + { + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + } + + if (feature == null) + { + feature = ConnectionFeatures.Get(); + } + + return feature; } private IEnumerable> FastEnumerable() diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs index 6d8fc9136f..873cb1fbea 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 protected override void OnReset() { - FastFeatureSet(typeof(IHttp2StreamIdFeature), this); + ResetIHttp2StreamIdFeature(); } protected override void OnRequestProcessingEnded() diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs index ad794fa879..acd9e0dab1 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs @@ -111,69 +111,101 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal object IFeatureCollection.this[Type key] { - get => FastFeatureGet(key); - set => FastFeatureSet(key, value); + get + { + if (key == IHttpConnectionFeatureType) + { + return _currentIHttpConnectionFeature; + } + + if (key == IConnectionIdFeatureType) + { + return _currentIConnectionIdFeature; + } + + if (key == IConnectionTransportFeatureType) + { + return _currentIConnectionTransportFeature; + } + + if (MaybeExtra != null) + { + return ExtraFeatureGet(key); + } + + return null; + } + set + { + _featureRevision++; + + if (key == IHttpConnectionFeatureType) + { + _currentIHttpConnectionFeature = value; + } + else if (key == IConnectionIdFeatureType) + { + _currentIConnectionIdFeature = value; + } + else if (key == IConnectionTransportFeatureType) + { + _currentIConnectionTransportFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } + } } TFeature IFeatureCollection.Get() { - return (TFeature)FastFeatureGet(typeof(TFeature)); + if (typeof(TFeature) == typeof(IHttpConnectionFeature)) + { + return (TFeature)_currentIHttpConnectionFeature; + } + else if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + return (TFeature)_currentIConnectionIdFeature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + return (TFeature)_currentIConnectionTransportFeature; + } + else if (MaybeExtra != null) + { + return (TFeature)ExtraFeatureGet(typeof(TFeature)); + } + + return default; } void IFeatureCollection.Set(TFeature instance) { - FastFeatureSet(typeof(TFeature), instance); + _featureRevision++; + + if (typeof(TFeature) == typeof(IHttpConnectionFeature)) + { + _currentIHttpConnectionFeature = instance; + } + else if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + _currentIConnectionIdFeature = instance; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + _currentIConnectionTransportFeature = instance; + } + else + { + ExtraFeatureSet(typeof(TFeature), instance); + } } IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); - private object FastFeatureGet(Type key) - { - if (key == IHttpConnectionFeatureType) - { - return _currentIHttpConnectionFeature; - } - - if (key == IConnectionIdFeatureType) - { - return _currentIConnectionIdFeature; - } - - if (key == IConnectionTransportFeatureType) - { - return _currentIConnectionTransportFeature; - } - - return ExtraFeatureGet(key); - } - - private void FastFeatureSet(Type key, object feature) - { - _featureRevision++; - - if (key == IHttpConnectionFeatureType) - { - _currentIHttpConnectionFeature = feature; - return; - } - - if (key == IConnectionIdFeatureType) - { - _currentIConnectionIdFeature = feature; - return; - } - - if (key == IConnectionTransportFeatureType) - { - _currentIConnectionTransportFeature = feature; - return; - } - - ExtraFeatureSet(key, feature); - } - private IEnumerable> FastEnumerable() { if (_currentIHttpConnectionFeature != null) diff --git a/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs b/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs new file mode 100644 index 0000000000..4167386340 --- /dev/null +++ b/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs @@ -0,0 +1,224 @@ +// 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; +using System.Buffers; +using System.IO.Pipelines; +using System.Linq; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class HttpProtocolFeatureCollectionTests : IDisposable + { + private readonly IDuplexPipe _transport; + private readonly IDuplexPipe _application; + private readonly TestHttp1Connection _http1Connection; + private readonly ServiceContext _serviceContext; + private readonly Http1ConnectionContext _http1ConnectionContext; + private readonly MemoryPool _pipelineFactory; + private Mock _timeoutControl; + + private readonly IFeatureCollection _collection; + + public HttpProtocolFeatureCollectionTests() + { + _pipelineFactory = new MemoryPool(); + var pair = DuplexPipe.CreateConnectionPair(_pipelineFactory); + + _transport = pair.Transport; + _application = pair.Application; + + _serviceContext = new TestServiceContext(); + _timeoutControl = new Mock(); + _http1ConnectionContext = new Http1ConnectionContext + { + ServiceContext = _serviceContext, + ConnectionFeatures = new FeatureCollection(), + MemoryPool = _pipelineFactory, + TimeoutControl = _timeoutControl.Object, + Application = pair.Application, + Transport = pair.Transport + }; + + _http1Connection = new TestHttp1Connection(_http1ConnectionContext); + _http1Connection.Reset(); + _collection = _http1Connection; + } + + public void Dispose() + { + _transport.Input.Complete(); + _transport.Output.Complete(); + + _application.Input.Complete(); + _application.Output.Complete(); + + _pipelineFactory.Dispose(); + } + + [Fact] + public int FeaturesStartAsSelf() + { + var featureCount = 0; + foreach (var featureIter in _collection) + { + Type type = featureIter.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + var featureLookup = _collection[type]; + Assert.Same(featureLookup, featureIter.Value); + Assert.Same(featureLookup, _collection); + featureCount++; + } + } + + Assert.NotEqual(0, featureCount); + + return featureCount; + } + + [Fact] + public int FeaturesCanBeAssignedTo() + { + var featureCount = SetFeaturesToNonDefault(); + Assert.NotEqual(0, featureCount); + + featureCount = 0; + foreach (var feature in _collection) + { + Type type = feature.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + Assert.Same(_collection[type], feature.Value); + Assert.NotSame(_collection[type], _collection); + featureCount++; + } + } + + Assert.NotEqual(0, featureCount); + + return featureCount; + } + + [Fact] + public void FeaturesResetToSelf() + { + var featuresAssigned = SetFeaturesToNonDefault(); + _http1Connection.ResetFeatureCollection(); + var featuresReset = FeaturesStartAsSelf(); + + Assert.Equal(featuresAssigned, featuresReset); + } + + [Fact] + public void FeaturesByGenericSameAsByType() + { + var featuresAssigned = SetFeaturesToNonDefault(); + + CompareGenericGetterToIndexer(); + + _http1Connection.ResetFeatureCollection(); + var featuresReset = FeaturesStartAsSelf(); + + Assert.Equal(featuresAssigned, featuresReset); + } + + [Fact] + public void FeaturesSetByTypeSameAsGeneric() + { + _collection[typeof(IHttpRequestFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpResponseFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpRequestIdentifierFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpRequestLifetimeFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpConnectionFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpMaxRequestBodySizeFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpMinRequestBodyDataRateFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpMinResponseDataRateFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpBodyControlFeature)] = CreateHttp1Connection(); + + CompareGenericGetterToIndexer(); + + EachHttpProtocolFeatureSetAndUnique(); + } + + [Fact] + public void FeaturesSetByGenericSameAsByType() + { + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + + CompareGenericGetterToIndexer(); + + EachHttpProtocolFeatureSetAndUnique(); + } + + private void CompareGenericGetterToIndexer() + { + Assert.Same(_collection.Get(), _collection[typeof(IHttpRequestFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpResponseFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpRequestIdentifierFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpRequestLifetimeFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpConnectionFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpMaxRequestBodySizeFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpMinRequestBodyDataRateFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpMinResponseDataRateFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpBodyControlFeature)]); + } + + private int EachHttpProtocolFeatureSetAndUnique() + { + int featureCount = 0; + foreach (var item in _collection) + { + Type type = item.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + Assert.Equal(1, _collection.Count(kv => ReferenceEquals(kv.Value, item.Value))); + + featureCount++; + } + } + + Assert.NotEqual(0, featureCount); + + return featureCount; + } + + private int SetFeaturesToNonDefault() + { + int featureCount = 0; + foreach (var feature in _collection) + { + Type type = feature.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + _collection[type] = CreateHttp1Connection(); + featureCount++; + } + } + + var protocolFeaturesCount = EachHttpProtocolFeatureSetAndUnique(); + + Assert.Equal(protocolFeaturesCount, featureCount); + + return featureCount; + } + + private HttpProtocol CreateHttp1Connection() => new TestHttp1Connection(_http1ConnectionContext); + } +} diff --git a/tools/CodeGenerator/HttpProtocolFeatureCollection.cs b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs index 3865658e70..db6508080e 100644 --- a/tools/CodeGenerator/HttpProtocolFeatureCollection.cs +++ b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -16,6 +16,12 @@ namespace CodeGenerator return values.Select(formatter).Aggregate((a, b) => a + b); } + public class KnownFeature + { + public string Name; + public int Index; + } + public static string GeneratedFile(string className) { var alwaysFeatures = new[] @@ -55,7 +61,15 @@ namespace CodeGenerator "IHttpSendFileFeature", }; - var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures); + var allFeatures = alwaysFeatures + .Concat(commonFeatures) + .Concat(sometimesFeatures) + .Concat(rareFeatures) + .Select((type, index) => new KnownFeature + { + Name = type, + Index = index + }); // NOTE: This list MUST always match the set of feature interfaces implemented by HttpProtocol. // See also: src/Kestrel/Http/HttpProtocol.FeatureCollection.cs @@ -86,43 +100,87 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http {{ public partial class {className} {{{Each(allFeatures, feature => $@" - private static readonly Type {feature}Type = typeof({feature});")} + private static readonly Type {feature.Name}Type = typeof({feature.Name});")} {Each(allFeatures, feature => $@" - private object _current{feature};")} + private object _current{feature.Name};")} private void FastReset() {{{Each(implementedFeatures, feature => $@" _current{feature} = this;")} - {Each(allFeatures.Where(f => !implementedFeatures.Contains(f)), feature => $@" - _current{feature} = null;")} + {Each(allFeatures.Where(f => !implementedFeatures.Contains(f.Name)), feature => $@" + _current{feature.Name} = null;")} }} - internal object FastFeatureGet(Type key) - {{{Each(allFeatures, feature => $@" - if (key == {feature}Type) - {{ - return _current{feature}; - }}")} - return ExtraFeatureGet(key); - }} - - protected void FastFeatureSet(Type key, object feature) + object IFeatureCollection.this[Type key] {{ - _featureRevision++; - {Each(allFeatures, feature => $@" - if (key == {feature}Type) + get {{ - _current{feature} = feature; - return; - }}")}; - ExtraFeatureSet(key, feature); + object feature = null;{Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (key == {feature.Name}Type) + {{ + feature = _current{feature.Name}; + }}")} + else if (MaybeExtra != null) + {{ + feature = ExtraFeatureGet(key); + }} + + return feature ?? ConnectionFeatures[key]; + }} + + set + {{ + _featureRevision++; + {Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (key == {feature.Name}Type) + {{ + _current{feature.Name} = value; + }}")} + else + {{ + ExtraFeatureSet(key, value); + }} + }} + }} + + void IFeatureCollection.Set(TFeature feature) + {{ + _featureRevision++;{Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (typeof(TFeature) == typeof({feature.Name})) + {{ + _current{feature.Name} = feature; + }}")} + else + {{ + ExtraFeatureSet(typeof(TFeature), feature); + }} + }} + + TFeature IFeatureCollection.Get() + {{ + TFeature feature = default;{Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (typeof(TFeature) == typeof({feature.Name})) + {{ + feature = (TFeature)_current{feature.Name}; + }}")} + else if (MaybeExtra != null) + {{ + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + }} + + if (feature == null) + {{ + feature = ConnectionFeatures.Get(); + }} + + return feature; }} private IEnumerable> FastEnumerable() {{{Each(allFeatures, feature => $@" - if (_current{feature} != null) + if (_current{feature.Name} != null) {{ - yield return new KeyValuePair({feature}Type, _current{feature} as {feature}); + yield return new KeyValuePair({feature.Name}Type, _current{feature.Name} as {feature.Name}); }}")} if (MaybeExtra != null)