From 2d1739895aef1edffabdc014e8d5a2069dcbb622 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 3 Feb 2018 02:17:12 +0000 Subject: [PATCH] Faster IFeatureCollection.Get --- .../HttpProtocolFeatureCollection.cs | 80 ++-- .../Internal/Http/Http1Connection.cs | 2 +- .../Http/HttpProtocol.FeatureCollection.cs | 15 +- .../Internal/Http/HttpProtocol.Generated.cs | 408 +++++++++++++----- .../Internal/Http2/Http2Stream.cs | 2 +- .../Internal/TransportConnection.Features.cs | 106 +++-- .../HttpProtocolFeatureCollectionTests.cs | 224 ++++++++++ .../HttpProtocolFeatureCollection.cs | 98 ++++- 8 files changed, 715 insertions(+), 220 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 ddf7fa055..69f07faf3 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; - - [Benchmark(Baseline = true)] - public IHttpRequestFeature GetFirstViaFastFeature() - { - return (IHttpRequestFeature)GetFastFeature(typeof(IHttpRequestFeature)); - } - - [Benchmark] - public IHttpRequestFeature GetFirstViaType() - { - return (IHttpRequestFeature)Get(typeof(IHttpRequestFeature)); - } + private readonly IFeatureCollection _collection; [Benchmark] - public IHttpRequestFeature GetFirstViaExtension() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpRequestFeature GetViaTypeOf_First() { - return _collection.GetType(); + return (IHttpRequestFeature)_collection[typeof(IHttpRequestFeature)]; } [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() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpSendFileFeature GetViaGeneric_Last() { - return (IHttpSendFileFeature)Get(typeof(IHttpSendFileFeature)); + return _collection.Get(); } [Benchmark] - public IHttpSendFileFeature GetLastViaExtension() + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaTypeOf_Custom() { - return _collection.GetType(); + return (IHttpCustomFeature)_collection[typeof(IHttpCustomFeature)]; } [Benchmark] - public IHttpSendFileFeature GetLastViaGeneric() + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaGeneric_Custom() { - return _collection.Get(); + return _collection.Get(); } - private object Get(Type type) + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaTypeOf_NotFound() { - return _collection[type]; + return (IHttpNotFoundFeature)_collection[typeof(IHttpNotFoundFeature)]; } - private object GetFastFeature(Type type) + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaGeneric_NotFound() { - return _http1Connection.FastFeatureGet(type); + return _collection.Get(); } public HttpProtocolFeatureCollection() @@ -99,21 +100,24 @@ public HttpProtocolFeatureCollection() 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 e0e5c7d43..1c1b81649 100644 --- a/src/Kestrel.Core/Internal/Http/Http1Connection.cs +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs @@ -401,7 +401,7 @@ private void EnsureHostHeaderExists() 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 230ba868a..cb475ca05 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -243,21 +243,14 @@ MinDataRate IHttpMinResponseDataRateFeature.MinDataRate get => MinResponseDataRate; set => MinResponseDataRate = value; } - - object IFeatureCollection.this[Type key] - { - get => FastFeatureGet(key) ?? ConnectionFeatures[key]; - set => FastFeatureSet(key, value); - } - - TFeature IFeatureCollection.Get() + protected void ResetIHttpUpgradeFeature() { - return (TFeature)(FastFeatureGet(typeof(TFeature)) ?? ConnectionFeatures[typeof(TFeature)]); + _currentIHttpUpgradeFeature = this; } - void IFeatureCollection.Set(TFeature instance) + protected void ResetIHttp2StreamIdFeature() { - 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 319d36865..3bac0a077 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs @@ -82,205 +82,389 @@ private void FastReset() _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) + } + + void IFeatureCollection.Set(TFeature feature) + { + _featureRevision++; + if (typeof(TFeature) == typeof(IHttpRequestFeature)) { - return _currentIHttpRequestIdentifierFeature; + _currentIHttpRequestFeature = feature; } - if (key == IServiceProvidersFeatureType) + else if (typeof(TFeature) == typeof(IHttpResponseFeature)) { - return _currentIServiceProvidersFeature; + _currentIHttpResponseFeature = feature; } - if (key == IHttpRequestLifetimeFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature)) { - return _currentIHttpRequestLifetimeFeature; + _currentIHttpRequestIdentifierFeature = feature; } - if (key == IHttpConnectionFeatureType) + else if (typeof(TFeature) == typeof(IServiceProvidersFeature)) { - return _currentIHttpConnectionFeature; + _currentIServiceProvidersFeature = feature; } - if (key == IHttpAuthenticationFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature)) { - return _currentIHttpAuthenticationFeature; + _currentIHttpRequestLifetimeFeature = feature; } - if (key == IQueryFeatureType) + else if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { - return _currentIQueryFeature; + _currentIHttpConnectionFeature = feature; } - if (key == IFormFeatureType) + else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { - return _currentIFormFeature; + _currentIHttpAuthenticationFeature = feature; } - if (key == IHttpUpgradeFeatureType) + else if (typeof(TFeature) == typeof(IQueryFeature)) { - return _currentIHttpUpgradeFeature; + _currentIQueryFeature = feature; } - if (key == IHttp2StreamIdFeatureType) + else if (typeof(TFeature) == typeof(IFormFeature)) { - return _currentIHttp2StreamIdFeature; + _currentIFormFeature = feature; } - if (key == IResponseCookiesFeatureType) + else if (typeof(TFeature) == typeof(IHttpUpgradeFeature)) { - return _currentIResponseCookiesFeature; + _currentIHttpUpgradeFeature = feature; } - if (key == IItemsFeatureType) + else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature)) { - return _currentIItemsFeature; + _currentIHttp2StreamIdFeature = feature; } - if (key == ITlsConnectionFeatureType) + else if (typeof(TFeature) == typeof(IResponseCookiesFeature)) { - return _currentITlsConnectionFeature; + _currentIResponseCookiesFeature = feature; } - if (key == IHttpWebSocketFeatureType) + else if (typeof(TFeature) == typeof(IItemsFeature)) { - return _currentIHttpWebSocketFeature; + _currentIItemsFeature = feature; } - if (key == ISessionFeatureType) + else if (typeof(TFeature) == typeof(ITlsConnectionFeature)) { - return _currentISessionFeature; + _currentITlsConnectionFeature = feature; } - if (key == IHttpMaxRequestBodySizeFeatureType) + else if (typeof(TFeature) == typeof(IHttpWebSocketFeature)) { - return _currentIHttpMaxRequestBodySizeFeature; + _currentIHttpWebSocketFeature = feature; } - if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (typeof(TFeature) == typeof(ISessionFeature)) { - return _currentIHttpMinRequestBodyDataRateFeature; + _currentISessionFeature = feature; } - if (key == IHttpMinResponseDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature)) { - return _currentIHttpMinResponseDataRateFeature; + _currentIHttpMaxRequestBodySizeFeature = feature; } - if (key == IHttpBodyControlFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature)) { - return _currentIHttpBodyControlFeature; + _currentIHttpMinRequestBodyDataRateFeature = feature; } - if (key == IHttpSendFileFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature)) { - return _currentIHttpSendFileFeature; + _currentIHttpMinResponseDataRateFeature = feature; + } + else if (typeof(TFeature) == typeof(IHttpBodyControlFeature)) + { + _currentIHttpBodyControlFeature = feature; + } + else if (typeof(TFeature) == typeof(IHttpSendFileFeature)) + { + _currentIHttpSendFileFeature = feature; + } + else + { + ExtraFeatureSet(typeof(TFeature), feature); } - return ExtraFeatureGet(key); } - protected void FastFeatureSet(Type key, object feature) + TFeature IFeatureCollection.Get() { - _featureRevision++; - - if (key == IHttpRequestFeatureType) + TFeature feature = default; + if (typeof(TFeature) == typeof(IHttpRequestFeature)) { - _currentIHttpRequestFeature = feature; - return; + feature = (TFeature)_currentIHttpRequestFeature; } - if (key == IHttpResponseFeatureType) + else if (typeof(TFeature) == typeof(IHttpResponseFeature)) { - _currentIHttpResponseFeature = feature; - return; + feature = (TFeature)_currentIHttpResponseFeature; } - if (key == IHttpRequestIdentifierFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature)) { - _currentIHttpRequestIdentifierFeature = feature; - return; + feature = (TFeature)_currentIHttpRequestIdentifierFeature; } - if (key == IServiceProvidersFeatureType) + else if (typeof(TFeature) == typeof(IServiceProvidersFeature)) { - _currentIServiceProvidersFeature = feature; - return; + feature = (TFeature)_currentIServiceProvidersFeature; } - if (key == IHttpRequestLifetimeFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature)) { - _currentIHttpRequestLifetimeFeature = feature; - return; + feature = (TFeature)_currentIHttpRequestLifetimeFeature; } - if (key == IHttpConnectionFeatureType) + else if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { - _currentIHttpConnectionFeature = feature; - return; + feature = (TFeature)_currentIHttpConnectionFeature; } - if (key == IHttpAuthenticationFeatureType) + else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { - _currentIHttpAuthenticationFeature = feature; - return; + feature = (TFeature)_currentIHttpAuthenticationFeature; } - if (key == IQueryFeatureType) + else if (typeof(TFeature) == typeof(IQueryFeature)) { - _currentIQueryFeature = feature; - return; + feature = (TFeature)_currentIQueryFeature; } - if (key == IFormFeatureType) + else if (typeof(TFeature) == typeof(IFormFeature)) { - _currentIFormFeature = feature; - return; + feature = (TFeature)_currentIFormFeature; } - if (key == IHttpUpgradeFeatureType) + else if (typeof(TFeature) == typeof(IHttpUpgradeFeature)) { - _currentIHttpUpgradeFeature = feature; - return; + feature = (TFeature)_currentIHttpUpgradeFeature; } - if (key == IHttp2StreamIdFeatureType) + else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature)) { - _currentIHttp2StreamIdFeature = feature; - return; + feature = (TFeature)_currentIHttp2StreamIdFeature; } - if (key == IResponseCookiesFeatureType) + else if (typeof(TFeature) == typeof(IResponseCookiesFeature)) { - _currentIResponseCookiesFeature = feature; - return; + feature = (TFeature)_currentIResponseCookiesFeature; } - if (key == IItemsFeatureType) + else if (typeof(TFeature) == typeof(IItemsFeature)) { - _currentIItemsFeature = feature; - return; + feature = (TFeature)_currentIItemsFeature; } - if (key == ITlsConnectionFeatureType) + else if (typeof(TFeature) == typeof(ITlsConnectionFeature)) { - _currentITlsConnectionFeature = feature; - return; + feature = (TFeature)_currentITlsConnectionFeature; } - if (key == IHttpWebSocketFeatureType) + else if (typeof(TFeature) == typeof(IHttpWebSocketFeature)) { - _currentIHttpWebSocketFeature = feature; - return; + feature = (TFeature)_currentIHttpWebSocketFeature; } - if (key == ISessionFeatureType) + else if (typeof(TFeature) == typeof(ISessionFeature)) { - _currentISessionFeature = feature; - return; + feature = (TFeature)_currentISessionFeature; } - if (key == IHttpMaxRequestBodySizeFeatureType) + else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature)) { - _currentIHttpMaxRequestBodySizeFeature = feature; - return; + feature = (TFeature)_currentIHttpMaxRequestBodySizeFeature; } - if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature)) { - _currentIHttpMinRequestBodyDataRateFeature = feature; - return; + feature = (TFeature)_currentIHttpMinRequestBodyDataRateFeature; } - if (key == IHttpMinResponseDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature)) { - _currentIHttpMinResponseDataRateFeature = feature; - return; + feature = (TFeature)_currentIHttpMinResponseDataRateFeature; } - if (key == IHttpBodyControlFeatureType) + else if (typeof(TFeature) == typeof(IHttpBodyControlFeature)) { - _currentIHttpBodyControlFeature = feature; - return; + feature = (TFeature)_currentIHttpBodyControlFeature; } - if (key == IHttpSendFileFeatureType) + else if (typeof(TFeature) == typeof(IHttpSendFileFeature)) { - _currentIHttpSendFileFeature = feature; - return; - }; - ExtraFeatureSet(key, feature); + 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 6d8fc9136..873cb1fbe 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs @@ -34,7 +34,7 @@ public Http2Stream(Http2StreamContext context) 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 98308725e..46d514bdc 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs @@ -112,69 +112,101 @@ IDuplexPipe IConnectionTransportFeature.Application object IFeatureCollection.this[Type key] { - get => FastFeatureGet(key); - set => FastFeatureSet(key, value); - } + get + { + if (key == IHttpConnectionFeatureType) + { + return _currentIHttpConnectionFeature; + } - TFeature IFeatureCollection.Get() - { - return (TFeature)FastFeatureGet(typeof(TFeature)); - } + if (key == IConnectionIdFeatureType) + { + return _currentIConnectionIdFeature; + } - void IFeatureCollection.Set(TFeature instance) - { - FastFeatureSet(typeof(TFeature), instance); - } + if (key == IConnectionTransportFeatureType) + { + return _currentIConnectionTransportFeature; + } - IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + if (MaybeExtra != null) + { + return ExtraFeatureGet(key); + } - IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + return null; + } + set + { + _featureRevision++; - private object FastFeatureGet(Type key) + if (key == IHttpConnectionFeatureType) + { + _currentIHttpConnectionFeature = value; + } + else if (key == IConnectionIdFeatureType) + { + _currentIConnectionIdFeature = value; + } + else if (key == IConnectionTransportFeatureType) + { + _currentIConnectionTransportFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } + } + } + + TFeature IFeatureCollection.Get() { - if (key == IHttpConnectionFeatureType) + if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { - return _currentIHttpConnectionFeature; + return (TFeature)_currentIHttpConnectionFeature; } - - if (key == IConnectionIdFeatureType) + else if (typeof(TFeature) == typeof(IConnectionIdFeature)) { - return _currentIConnectionIdFeature; + return (TFeature)_currentIConnectionIdFeature; } - - if (key == IConnectionTransportFeatureType) + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) { - return _currentIConnectionTransportFeature; + return (TFeature)_currentIConnectionTransportFeature; + } + else if (MaybeExtra != null) + { + return (TFeature)ExtraFeatureGet(typeof(TFeature)); } - return ExtraFeatureGet(key); + return default; } - private void FastFeatureSet(Type key, object feature) + void IFeatureCollection.Set(TFeature instance) { _featureRevision++; - if (key == IHttpConnectionFeatureType) + if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { - _currentIHttpConnectionFeature = feature; - return; + _currentIHttpConnectionFeature = instance; } - - if (key == IConnectionIdFeatureType) + else if (typeof(TFeature) == typeof(IConnectionIdFeature)) { - _currentIConnectionIdFeature = feature; - return; + _currentIConnectionIdFeature = instance; } - - if (key == IConnectionTransportFeatureType) + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) { - _currentIConnectionTransportFeature = feature; - return; + _currentIConnectionTransportFeature = instance; + } + else + { + ExtraFeatureSet(typeof(TFeature), instance); } - - ExtraFeatureSet(key, feature); } + IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + 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 000000000..416738634 --- /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 b960180f2..f12162f5f 100644 --- a/tools/CodeGenerator/HttpProtocolFeatureCollection.cs +++ b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -19,6 +19,12 @@ static string Each(IEnumerable values, Func formatter) 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[] @@ -58,7 +64,15 @@ public static string GeneratedFile(string className) "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 @@ -89,43 +103,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) + object IFeatureCollection.this[Type key] + {{ + get + {{ + 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})) {{ - return _current{feature}; + _current{feature.Name} = feature; }}")} - return ExtraFeatureGet(key); + else + {{ + ExtraFeatureSet(typeof(TFeature), feature); + }} }} - protected void FastFeatureSet(Type key, object feature) + TFeature IFeatureCollection.Get() {{ - _featureRevision++; - {Each(allFeatures, feature => $@" - if (key == {feature}Type) + 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) {{ - _current{feature} = feature; - return; - }}")}; - ExtraFeatureSet(key, feature); + 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)