From d31512528dd1d945b0eb7d6eefccb49647c4686f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 14 Mar 2018 20:56:30 -0700 Subject: [PATCH] Items is now a first class property on ConnectionContext (#2395) * Metadata is now a first class property on ConnectionContext - Make IConnectionMetadata a manatory top level feature on ConnectionContext - TransportConnection will lazily manifest ConnectionMetadata on first access. This should avoid allocations since Kestrel isn't using this today. --- .../Internal/ConnectionItems.cs | 118 ++++++++++++++++++ .../Internal/TransportConnection.Features.cs | 31 +++++ .../Internal/TransportConnection.cs | 17 +++ .../ConnectionContext.cs | 5 +- .../DefaultConnectionContext.cs | 14 ++- ...aFeature.cs => IConnectionItemsFeature.cs} | 4 +- 6 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 src/Kestrel.Transport.Abstractions/Internal/ConnectionItems.cs rename src/Protocols.Abstractions/Features/{IConnectionMetadataFeature.cs => IConnectionItemsFeature.cs} (72%) diff --git a/src/Kestrel.Transport.Abstractions/Internal/ConnectionItems.cs b/src/Kestrel.Transport.Abstractions/Internal/ConnectionItems.cs new file mode 100644 index 0000000000..adb346df81 --- /dev/null +++ b/src/Kestrel.Transport.Abstractions/Internal/ConnectionItems.cs @@ -0,0 +1,118 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +{ + internal class ConnectionItems : IDictionary + { + public ConnectionItems() + : this(new Dictionary()) + { + } + + public ConnectionItems(IDictionary items) + { + Items = items; + } + + public IDictionary Items { get; } + + // Replace the indexer with one that returns null for missing values + object IDictionary.this[object key] + { + get + { + if (Items.TryGetValue(key, out var value)) + { + return value; + } + return null; + } + set { Items[key] = value; } + } + + void IDictionary.Add(object key, object value) + { + Items.Add(key, value); + } + + bool IDictionary.ContainsKey(object key) + { + return Items.ContainsKey(key); + } + + ICollection IDictionary.Keys + { + get { return Items.Keys; } + } + + bool IDictionary.Remove(object key) + { + return Items.Remove(key); + } + + bool IDictionary.TryGetValue(object key, out object value) + { + return Items.TryGetValue(key, out value); + } + + ICollection IDictionary.Values + { + get { return Items.Values; } + } + + void ICollection>.Add(KeyValuePair item) + { + Items.Add(item); + } + + void ICollection>.Clear() + { + Items.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return Items.Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + Items.CopyTo(array, arrayIndex); + } + + int ICollection>.Count + { + get { return Items.Count; } + } + + bool ICollection>.IsReadOnly + { + get { return Items.IsReadOnly; } + } + + bool ICollection>.Remove(KeyValuePair item) + { + object value; + if (Items.TryGetValue(item.Key, out value) && Equals(item.Value, value)) + { + return Items.Remove(item.Key); + } + return false; + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return Items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return Items.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs index 9e91865b86..c9e316557f 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal IHttpConnectionFeature, IConnectionIdFeature, IConnectionTransportFeature, + IConnectionItemsFeature, IMemoryPoolFeature, IApplicationTransportFeature, ITransportSchedulerFeature @@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); + private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); private static readonly Type IApplicationTransportFeatureType = typeof(IApplicationTransportFeature); private static readonly Type ITransportSchedulerFeatureType = typeof(ITransportSchedulerFeature); @@ -27,6 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal private object _currentIHttpConnectionFeature; private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; + private object _currentIConnectionItemsFeature; private object _currentIMemoryPoolFeature; private object _currentIApplicationTransportFeature; private object _currentITransportSchedulerFeature; @@ -118,6 +121,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal set => Application = value; } + IDictionary IConnectionItemsFeature.Items + { + get => Items; + set => Items = value; + } + PipeScheduler ITransportSchedulerFeature.InputWriterScheduler => InputWriterScheduler; PipeScheduler ITransportSchedulerFeature.OutputReaderScheduler => OutputReaderScheduler; @@ -140,6 +149,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal return _currentIConnectionTransportFeature; } + if (key == IConnectionItemsFeatureType) + { + return _currentIConnectionItemsFeature; + } + if (key == IMemoryPoolFeatureType) { return _currentIMemoryPoolFeature; @@ -178,6 +192,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { _currentIConnectionTransportFeature = value; } + else if (key == IConnectionItemsFeatureType) + { + _currentIConnectionItemsFeature = value; + } else if (key == IMemoryPoolFeatureType) { _currentIMemoryPoolFeature = value; @@ -211,6 +229,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { return (TFeature)_currentIConnectionTransportFeature; } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + return (TFeature)_currentIConnectionItemsFeature; + } else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) { return (TFeature)_currentIMemoryPoolFeature; @@ -247,6 +269,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { _currentIConnectionTransportFeature = instance; } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + _currentIConnectionItemsFeature = instance; + } else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) { _currentIMemoryPoolFeature = instance; @@ -286,6 +312,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); } + if (_currentIConnectionItemsFeature != null) + { + yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); + } + if (_currentIMemoryPoolFeature != null) { yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs index bfe7e53458..b753dae64a 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Collections.Generic; using System.IO.Pipelines; using System.Net; using System.Threading; @@ -7,11 +8,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { public abstract partial class TransportConnection { + private IDictionary _items; + public TransportConnection() { _currentIConnectionIdFeature = this; _currentIConnectionTransportFeature = this; _currentIHttpConnectionFeature = this; + _currentIConnectionItemsFeature = this; _currentIApplicationTransportFeature = this; _currentIMemoryPoolFeature = this; _currentITransportSchedulerFeature = this; @@ -31,6 +35,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal public IDuplexPipe Transport { get; set; } public IDuplexPipe Application { get; set; } + public IDictionary Items + { + get + { + // Lazily allocate connection metadata + return _items ?? (_items = new ConnectionItems()); + } + set + { + _items = value; + } + } + public PipeWriter Input => Application.Output; public PipeReader Output => Application.Input; } diff --git a/src/Protocols.Abstractions/ConnectionContext.cs b/src/Protocols.Abstractions/ConnectionContext.cs index 329afa4b11..f8175d3897 100644 --- a/src/Protocols.Abstractions/ConnectionContext.cs +++ b/src/Protocols.Abstractions/ConnectionContext.cs @@ -1,4 +1,5 @@ -using System.IO.Pipelines; +using System.Collections.Generic; +using System.IO.Pipelines; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Protocols @@ -9,6 +10,8 @@ namespace Microsoft.AspNetCore.Protocols public abstract IFeatureCollection Features { get; } + public abstract IDictionary Items { get; set; } + public abstract IDuplexPipe Transport { get; set; } } } diff --git a/src/Protocols.Abstractions/DefaultConnectionContext.cs b/src/Protocols.Abstractions/DefaultConnectionContext.cs index d38657061c..6959062e69 100644 --- a/src/Protocols.Abstractions/DefaultConnectionContext.cs +++ b/src/Protocols.Abstractions/DefaultConnectionContext.cs @@ -1,4 +1,5 @@ -using System.IO.Pipelines; +using System.Collections.Generic; +using System.IO.Pipelines; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Protocols.Features; @@ -19,6 +20,9 @@ namespace Microsoft.AspNetCore.Protocols private IConnectionTransportFeature ConnectionTransportFeature => _features.Fetch(ref _features.Cache.ConnectionTransport, _ => null); + private IConnectionItemsFeature ConnectionItemsFeature => + _features.Fetch(ref _features.Cache.ConnectionItems, _ => null); + public override string ConnectionId { get => ConnectionIdFeature.ConnectionId; @@ -33,11 +37,19 @@ namespace Microsoft.AspNetCore.Protocols set => ConnectionTransportFeature.Transport = value; } + public override IDictionary Items + { + get => ConnectionItemsFeature.Items; + set => ConnectionItemsFeature.Items = value; + } + struct FeatureInterfaces { public IConnectionIdFeature ConnectionId; public IConnectionTransportFeature ConnectionTransport; + + public IConnectionItemsFeature ConnectionItems; } } } diff --git a/src/Protocols.Abstractions/Features/IConnectionMetadataFeature.cs b/src/Protocols.Abstractions/Features/IConnectionItemsFeature.cs similarity index 72% rename from src/Protocols.Abstractions/Features/IConnectionMetadataFeature.cs rename to src/Protocols.Abstractions/Features/IConnectionItemsFeature.cs index b2e0f67789..136006b5a3 100644 --- a/src/Protocols.Abstractions/Features/IConnectionMetadataFeature.cs +++ b/src/Protocols.Abstractions/Features/IConnectionItemsFeature.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; namespace Microsoft.AspNetCore.Protocols.Features { - public interface IConnectionMetadataFeature + public interface IConnectionItemsFeature { - IDictionary Metadata { get; set; } + IDictionary Items { get; set; } } } \ No newline at end of file