Skip to content

Add IPersistentStateFeature #34360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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.Generic;

namespace Microsoft.AspNetCore.Connections.Features
{
/// <summary>
/// Provides access to a key/value collection that can be used to persist state between connections and requests.
/// Whether a transport supports persisting state depends on the implementation. The transport must support
/// pooling and reusing connection instances for state to be persisted.
/// <para>
/// Because values added to persistent state can live in memory until a connection is no longer pooled,
/// use caution when adding items to this collection to avoid excessive memory use.
/// </para>
/// </summary>
public interface IPersistentStateFeature
{
/// <summary>
/// Gets a key/value collection that can be used to persist state between connections and requests.
/// </summary>
IDictionary<object, object?> State { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
*REMOVED*Microsoft.AspNetCore.Connections.IConnectionListener.AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext!>
Microsoft.AspNetCore.Connections.Features.IConnectionSocketFeature
Microsoft.AspNetCore.Connections.Features.IConnectionSocketFeature.Socket.get -> System.Net.Sockets.Socket!
Microsoft.AspNetCore.Connections.Features.IPersistentStateFeature
Microsoft.AspNetCore.Connections.Features.IPersistentStateFeature.State.get -> System.Collections.Generic.IDictionary<object!, object?>!
Microsoft.AspNetCore.Connections.IConnectionListener.AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext?>
Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder
Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.ApplicationServices.get -> System.IServiceProvider!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
// 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.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal partial class Http1Connection : IHttpMinRequestBodyDataRateFeature,
IHttpMinResponseDataRateFeature
IHttpMinResponseDataRateFeature,
IPersistentStateFeature
{
// Persistent state collection is not reset with a request by design.
// If SocketsConections are pooled in the future this state could be moved
// to the transport layer.
private IDictionary<object, object?>? _persistentState;

MinDataRate? IHttpMinRequestBodyDataRateFeature.MinDataRate
{
get => MinRequestBodyDataRate;
Expand All @@ -19,5 +27,14 @@ internal partial class Http1Connection : IHttpMinRequestBodyDataRateFeature,
get => MinResponseDataRate;
set => MinResponseDataRate = value;
}

IDictionary<object, object?> IPersistentStateFeature.State
{
get
{
// Lazily allocate persistent state
return _persistentState ?? (_persistentState = new ConnectionItems());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,6 @@ private void ValidateNonOriginHostHeader(string hostText)

protected override void OnReset()
{

_requestTimedOut = false;
_requestTargetForm = HttpRequestTarget.Unknown;
_absoluteRequestTarget = null;
Expand All @@ -628,6 +627,7 @@ protected override void OnReset()
// Reset Http1 Features
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpMinResponseDataRateFeature = this;
_currentIPersistentStateFeature = this;
}

protected override void OnRequestProcessingEnding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;

using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
Expand Down Expand Up @@ -62,6 +63,7 @@ internal partial class HttpProtocol : IFeatureCollection,
internal protected IHttpMinRequestBodyDataRateFeature? _currentIHttpMinRequestBodyDataRateFeature;
internal protected IHttpMinResponseDataRateFeature? _currentIHttpMinResponseDataRateFeature;
internal protected IHttpResetFeature? _currentIHttpResetFeature;
internal protected IPersistentStateFeature? _currentIPersistentStateFeature;

private int _featureRevision;

Expand Down Expand Up @@ -99,6 +101,7 @@ private void FastReset()
_currentIHttpMinRequestBodyDataRateFeature = null;
_currentIHttpMinResponseDataRateFeature = null;
_currentIHttpResetFeature = null;
_currentIPersistentStateFeature = null;
}

// Internal for testing
Expand Down Expand Up @@ -286,6 +289,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
feature = _currentIHttpResetFeature;
}
else if (key == typeof(IPersistentStateFeature))
{
feature = _currentIPersistentStateFeature;
}
else if (MaybeExtra != null)
{
feature = ExtraFeatureGet(key);
Expand Down Expand Up @@ -414,6 +421,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
_currentIHttpResetFeature = (IHttpResetFeature?)value;
}
else if (key == typeof(IPersistentStateFeature))
{
_currentIPersistentStateFeature = (IPersistentStateFeature?)value;
}
else
{
ExtraFeatureSet(key, value);
Expand Down Expand Up @@ -544,6 +555,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
feature = Unsafe.As<IHttpResetFeature?, TFeature?>(ref _currentIHttpResetFeature);
}
else if (typeof(TFeature) == typeof(IPersistentStateFeature))
{
feature = Unsafe.As<IPersistentStateFeature?, TFeature?>(ref _currentIPersistentStateFeature);
}
else if (MaybeExtra != null)
{
feature = (TFeature?)(ExtraFeatureGet(typeof(TFeature)));
Expand Down Expand Up @@ -680,6 +695,10 @@ private void ExtraFeatureSet(Type key, object? value)
{
_currentIHttpResetFeature = Unsafe.As<TFeature?, IHttpResetFeature?>(ref feature);
}
else if (typeof(TFeature) == typeof(IPersistentStateFeature))
{
_currentIPersistentStateFeature = Unsafe.As<TFeature?, IPersistentStateFeature?>(ref feature);
}
else
{
ExtraFeatureSet(typeof(TFeature), feature);
Expand Down Expand Up @@ -804,6 +823,10 @@ private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
{
yield return new KeyValuePair<Type, object>(typeof(IHttpResetFeature), _currentIHttpResetFeature);
}
if (_currentIPersistentStateFeature != null)
{
yield return new KeyValuePair<Type, object>(typeof(IPersistentStateFeature), _currentIPersistentStateFeature);
}

if (MaybeExtra != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
Expand All @@ -14,11 +15,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
internal partial class Http2Stream : IHttp2StreamIdFeature,
IHttpMinRequestBodyDataRateFeature,
IHttpResetFeature,
IHttpResponseTrailersFeature

IHttpResponseTrailersFeature,
IPersistentStateFeature
{
private IHeaderDictionary? _userTrailers;

// Persistent state collection is not reset with a stream by design.
private IDictionary<object, object?>? _persistentState;

IHeaderDictionary IHttpResponseTrailersFeature.Trailers
{
get
Expand Down Expand Up @@ -65,5 +69,14 @@ void IHttpResetFeature.Reset(int errorCode)
var abortReason = new ConnectionAbortedException(CoreStrings.FormatHttp2StreamResetByApplication((Http2ErrorCode)errorCode));
ApplicationAbort(abortReason, (Http2ErrorCode)errorCode);
}

IDictionary<object, object?> IPersistentStateFeature.State
{
get
{
// Lazily allocate persistent state
return _persistentState ?? (_persistentState = new ConnectionItems());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ protected override void OnReset()
_currentIHttp2StreamIdFeature = this;
_currentIHttpResponseTrailersFeature = this;
_currentIHttpResetFeature = this;
_currentIPersistentStateFeature = this;
}

protected override void OnRequestProcessingEnded()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO.Pipelines;
using System.Linq;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
Expand Down Expand Up @@ -123,6 +124,8 @@ public void FeaturesSetByTypeSameAsGeneric()
_collection[typeof(IHttpBodyControlFeature)] = CreateHttp1Connection();
_collection[typeof(IRouteValuesFeature)] = CreateHttp1Connection();
_collection[typeof(IEndpointFeature)] = CreateHttp1Connection();
_collection[typeof(IHttpUpgradeFeature)] = CreateHttp1Connection();
_collection[typeof(IPersistentStateFeature)] = CreateHttp1Connection();

CompareGenericGetterToIndexer();

Expand All @@ -147,6 +150,8 @@ public void FeaturesSetByGenericSameAsByType()
_collection.Set<IHttpBodyControlFeature>(CreateHttp1Connection());
_collection.Set<IRouteValuesFeature>(CreateHttp1Connection());
_collection.Set<IEndpointFeature>(CreateHttp1Connection());
_collection.Set<IHttpUpgradeFeature>(CreateHttp1Connection());
_collection.Set<IPersistentStateFeature>(CreateHttp1Connection());

CompareGenericGetterToIndexer();

Expand Down Expand Up @@ -190,13 +195,21 @@ private void CompareGenericGetterToIndexer()

private int EachHttpProtocolFeatureSetAndUnique()
{
int featureCount = 0;
var featureCount = 0;
foreach (var item in _collection)
{
Type type = item.Key;
var type = item.Key;
if (type.IsAssignableFrom(typeof(HttpProtocol)))
{
Assert.Equal(1, _collection.Count(kv => ReferenceEquals(kv.Value, item.Value)));
var matches = _collection.Where(kv => ReferenceEquals(kv.Value, item.Value)).ToList();
try
{
Assert.Single(matches);
}
catch (Exception ex)
{
throw new Exception($"Error for feature {type}.", ex);
}

featureCount++;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.Net.Sockets;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;

namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
internal sealed partial class QuicStreamContext : IPersistentStateFeature
{
private IDictionary<object, object?>? _persistentState;

IDictionary<object, object?> IPersistentStateFeature.State
{
get
{
// Lazily allocate persistent state
return _persistentState ?? (_persistentState = new ConnectionItems());
}
}

private void InitializeFeatures()
{
_currentIPersistentStateFeature = this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
internal class QuicStreamContext : TransportConnection, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature, IPooledStream
internal partial class QuicStreamContext : TransportConnection, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature, IPooledStream
{
// Internal for testing.
internal Task _processingTask = Task.CompletedTask;
Expand Down Expand Up @@ -87,12 +87,16 @@ public void Initialize(QuicStream stream)
}

ConnectionClosed = _streamClosedTokenSource.Token;

// TODO - add to generated features
Features.Set<IStreamDirectionFeature>(this);
Features.Set<IProtocolErrorCodeFeature>(this);
Features.Set<IStreamIdFeature>(this);

// TODO populate the ITlsConnectionFeature (requires client certs).
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());

InitializeFeatures();

CanRead = _stream.CanRead;
CanWrite = _stream.CanWrite;
Error = 0;
Expand Down Expand Up @@ -132,6 +136,8 @@ public override string ConnectionId

public void Start()
{
Debug.Assert(_processingTask.IsCompletedSuccessfully);

_processingTask = StartAsync();
}

Expand Down
Loading