Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Reduce FeatureReference checking, lazy init FormFeature #999

Closed
wants to merge 4 commits into from
Closed
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
56 changes: 56 additions & 0 deletions src/Microsoft.AspNetCore.Http.Features/FeatureReferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public FeatureReferences(IFeatureCollection collection)
Revision = collection.Revision;
}

public FeatureReferences(IFeatureCollection collection, int revision)
{
Collection = collection;
Cache = default(TCache);
Revision = revision;
}

public IFeatureCollection Collection { get; private set; }
public int Revision { get; private set; }

Expand Down Expand Up @@ -63,6 +70,26 @@ public TFeature Fetch<TFeature, TState>(
return cached ?? UpdateCached(ref cached, state, factory, revision, flush);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TFeature Fetch<TFeature, TState1, TState2>(
ref TFeature cached,
TState1 state1,
TState2 state2,
Func<TState1, TState2, TFeature> factory) where TFeature : class
{
var flush = false;
var revision = Collection.Revision;
if (Revision != revision)
{
// Clear cached value to force call to UpdateCached
cached = null;
// Collection changed, clear whole feature cache
flush = true;
}

return cached ?? UpdateCached(ref cached, state1, state2, factory, revision, flush);
}

// Update and cache clearing logic, when the fast-path in Fetch isn't applicable
private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory, int revision, bool flush) where TFeature : class
{
Expand Down Expand Up @@ -92,6 +119,35 @@ private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState stat
return cached;
}

// Update and cache clearing logic, when the fast-path in Fetch isn't applicable
private TFeature UpdateCached<TFeature, TState1, TState2>(ref TFeature cached, TState1 state1, TState2 state2, Func<TState1, TState2, TFeature> factory, int revision, bool flush) where TFeature : class
{
if (flush)
{
// Collection detected as changed, clear cache
Cache = default(TCache);
}

cached = Collection.Get<TFeature>();
if (cached == null)
{
// Item not in collection, create it with factory
cached = factory(state1, state2);
// Add item to IFeatureCollection
Collection.Set(cached);
// Revision changed by .Set, update revision to new value
Revision = Collection.Revision;
}
else if (flush)
{
// Cache was cleared, but item retrived from current Collection for version
// so use passed in revision rather than making another virtual call
Revision = revision;
}

return cached;
}

public TFeature Fetch<TFeature>(ref TFeature cached, Func<IFeatureCollection, TFeature> factory)
where TFeature : class => Fetch(ref cached, Collection, factory);
}
Expand Down
51 changes: 45 additions & 6 deletions src/Microsoft.AspNetCore.Http/DefaultHttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Threading;
using Microsoft.AspNetCore.Http.Authentication;
Expand All @@ -24,6 +25,7 @@ public class DefaultHttpContext : HttpContext
private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null;
private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();

private FormOptions _formOptions;
private FeatureReferences<FeatureInterfaces> _features;

private HttpRequest _request;
Expand All @@ -44,15 +46,26 @@ public DefaultHttpContext()
}

public DefaultHttpContext(IFeatureCollection features)
: this(features, FormOptions.Default)
{
Initialize(features);
}

public virtual void Initialize(IFeatureCollection features)
public DefaultHttpContext(IFeatureCollection features, FormOptions formOptions)
{
Initialize(features, formOptions);
}

public virtual void Initialize(IFeatureCollection features, FormOptions formOptions)
{
_features = new FeatureReferences<FeatureInterfaces>(features);
_request = InitializeHttpRequest();
_response = InitializeHttpResponse();
_formOptions = formOptions;
}

public virtual void Initialize(IFeatureCollection features)
{
Initialize(features, FormOptions.Default);
}

public virtual void Uninitialize()
Expand Down Expand Up @@ -85,6 +98,7 @@ public virtual void Uninitialize()
UninitializeWebSocketManager(_websockets);
_websockets = null;
}
_formOptions = null;
}

private IItemsFeature ItemsFeature =>
Expand Down Expand Up @@ -185,18 +199,43 @@ public override ISession Session
}
}



public override void Abort()
{
LifetimeFeature.Abort();
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void InitializeRequestResponse()
{
var revision = _features.Revision;
var collection = _features.Collection;

_request = new DefaultHttpRequest(this, revision, collection, _formOptions);
_response = new DefaultHttpResponse(this, revision, collection);
}

protected virtual HttpRequest InitializeHttpRequest()
{
if (_request == null)
{
InitializeRequestResponse();
}

return _request;
}

protected virtual HttpRequest InitializeHttpRequest() => new DefaultHttpRequest(this);
protected virtual void UninitializeHttpRequest(HttpRequest instance) { }

protected virtual HttpResponse InitializeHttpResponse() => new DefaultHttpResponse(this);
protected virtual HttpResponse InitializeHttpResponse()
{
if (_response == null)
{
InitializeRequestResponse();
}

return _response;
}

protected virtual void UninitializeHttpResponse(HttpResponse instance) { }

protected virtual ConnectionInfo InitializeConnectionInfo() => new DefaultConnectionInfo(Features);
Expand Down
4 changes: 1 addition & 3 deletions src/Microsoft.AspNetCore.Http/Features/FormFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ namespace Microsoft.AspNetCore.Http.Features
{
public class FormFeature : IFormFeature
{
private static readonly FormOptions DefaultFormOptions = new FormOptions();

private readonly HttpRequest _request;
private readonly FormOptions _options;
private Task<IFormCollection> _parsedFormTask;
Expand All @@ -32,7 +30,7 @@ public FormFeature(IFormCollection form)
Form = form;
}
public FormFeature(HttpRequest request)
: this(request, DefaultFormOptions)
: this(request, FormOptions.Default)
{
}

Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.AspNetCore.Http/Features/FormOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Http.Features
{
public class FormOptions
{
internal static readonly FormOptions Default = new FormOptions();

public const int DefaultMemoryBufferThreshold = 1024 * 64;
public const int DefaultBufferBodyLengthLimit = 1024 * 1024 * 128;
public const int DefaultMultipartBoundaryLengthLimit = 128;
Expand Down
7 changes: 2 additions & 5 deletions src/Microsoft.AspNetCore.Http/HttpContextFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public HttpContextFactory(IOptions<FormOptions> formOptions, IHttpContextAccesso
throw new ArgumentNullException(nameof(formOptions));
}

_formOptions = formOptions.Value;
_formOptions = formOptions.Value ?? FormOptions.Default;
_httpContextAccessor = httpContextAccessor;
}

Expand All @@ -35,15 +35,12 @@ public HttpContext Create(IFeatureCollection featureCollection)
throw new ArgumentNullException(nameof(featureCollection));
}

var httpContext = new DefaultHttpContext(featureCollection);
var httpContext = new DefaultHttpContext(featureCollection, _formOptions);
if (_httpContextAccessor != null)
{
_httpContextAccessor.HttpContext = httpContext;
}

var formFeature = new FormFeature(httpContext.Request, _formOptions);
featureCollection.Set<IFormFeature>(formFeature);

return httpContext;
}

Expand Down
23 changes: 18 additions & 5 deletions src/Microsoft.AspNetCore.Http/Internal/DefaultHttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,39 @@ public class DefaultHttpRequest : HttpRequest
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f);
private readonly static Func<HttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r);
private readonly static Func<HttpRequest, FormOptions, IFormFeature> _newFormFeature = (r, o) => new FormFeature(r, o ?? FormOptions.Default);
private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);

private FormOptions _formOptions;
private HttpContext _context;
private FeatureReferences<FeatureInterfaces> _features;

public DefaultHttpRequest(HttpContext context)
: this (context, context.Features.Revision, context.Features, FormOptions.Default)
{
Initialize(context);
}

public virtual void Initialize(HttpContext context)
internal DefaultHttpRequest(HttpContext context, int revision, IFeatureCollection features, FormOptions formOptions)
{
_context = context;
_features = new FeatureReferences<FeatureInterfaces>(context.Features);
Initialize(features, revision, formOptions);
}

public virtual void Initialize(HttpContext context)
{
Initialize(context.Features, context.Features.Revision, FormOptions.Default);
}

private void Initialize(IFeatureCollection features, int revision, FormOptions formOptions)
{
_features = new FeatureReferences<FeatureInterfaces>(features, revision);
_formOptions = formOptions;
}

public virtual void Uninitialize()
{
_context = null;
_formOptions = null;
_features = default(FeatureReferences<FeatureInterfaces>);
}

Expand All @@ -47,7 +60,7 @@ public virtual void Uninitialize()
_features.Fetch(ref _features.Cache.Query, _newQueryFeature);

private IFormFeature FormFeature =>
_features.Fetch(ref _features.Cache.Form, this, _newFormFeature);
_features.Fetch(ref _features.Cache.Form, this, _formOptions, _newFormFeature);

private IRequestCookiesFeature RequestCookiesFeature =>
_features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature);
Expand Down
15 changes: 13 additions & 2 deletions src/Microsoft.AspNetCore.Http/Internal/DefaultHttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,25 @@ public class DefaultHttpResponse : HttpResponse
private FeatureReferences<FeatureInterfaces> _features;

public DefaultHttpResponse(HttpContext context)
: this(context, context.Features.Revision, context.Features)
{
Initialize(context);
}

internal DefaultHttpResponse(HttpContext context, int revision, IFeatureCollection features)
{
_context = context;
Initialize(features, revision);
}

public virtual void Initialize(HttpContext context)
{
_context = context;
_features = new FeatureReferences<FeatureInterfaces>(context.Features);
Initialize(context.Features, context.Features.Revision);
}

private void Initialize(IFeatureCollection features, int revision)
{
_features = new FeatureReferences<FeatureInterfaces>(features, revision);
}

public virtual void Uninitialize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ public void UpdateFeatures_ClearsCachedFeatures()

// featurecollection is set. all cached interfaces are null.
var context = new DefaultHttpContext(features);
// Trigger initalization
Assert.NotNull(context.Request);
TestAllCachedFeaturesAreNull(context, features);
Assert.Equal(3, features.Count());

Expand All @@ -179,6 +181,8 @@ public void UpdateFeatures_ClearsCachedFeatures()

// featurecollection is set to newFeatures. all cached interfaces are null.
context.Initialize(newFeatures);
// Trigger initalization
Assert.NotNull(context.Request);
TestAllCachedFeaturesAreNull(context, newFeatures);
Assert.Equal(3, newFeatures.Count());

Expand Down