Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Use factory pattern for caching in ControllerActionInvoker #6243

Closed
wants to merge 2 commits into from

Conversation

pranavkm
Copy link
Contributor

@pranavkm pranavkm commented May 8, 2017

Fixes #6249

private readonly Func<ControllerContext, object> _controllerActivatorCreate;
private readonly Action<ControllerContext, object> _controllerActivatorRelease;

public ControllerActivatorProvider(IEnumerable<IControllerActivator> activators)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably we want to keep IControllerActivator \ IControllerFactory around. Thoughts on an alternate pattern to invoke them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we does

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is your idea that we just wouldn't register the default implementations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually removed the implementations. My plan was to leave the contract in to continue other systems that implemented it, but not carry forward our implementation. Alternatively, we can register the current one and check for an exact type match.

@pranavkm
Copy link
Contributor Author

pranavkm commented May 8, 2017

Before

image
image

After

image


if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(descriptor.ControllerTypeInfo))
{
return _controllerActivatorRelease;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to return _dispose

@rynowak
Copy link
Member

rynowak commented May 8, 2017

@pranavkm that's not really enough data to be meaningful. You really need to get some significant CPU usage and profile for 30s - or collect some real world measurements with RPS.

@rynowak
Copy link
Member

rynowak commented May 8, 2017

I still think we want to use this pattern even if it's a sideways move for perf, it's a cleaner design overall.

/// <summary>
/// <see cref="IControllerActivator"/> that uses type activation to create controllers.
/// </summary>
public class DefaultControllerActivator : IControllerActivator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a breaking change for anyone that's subclassed it. Get approval from @Eilon

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. The alternative would be to leave this type in and registered, but use the factory if typeof(DefaultControllerActivator) == controllerActivator.GetType()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Where's a bug tracking this work?
  2. If we delete DefaultControllerActivator, what would the alternative be to anyone who used/derived from it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We spoke about it offline, but I don't think there's a tracking work item for it. I'll make for now.
  2. I'll revive the types. We can do an exact match test so that it leaves users relying on the type unaffected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then that sounds reasonable to me.

/// <summary>
/// Default implementation for <see cref="IControllerFactory"/>.
/// </summary>
public class DefaultControllerFactory : IControllerFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing

@@ -43,14 +57,13 @@ private InnerCache CurrentCache
}
}

public ControllerActionInvokerState GetState(ControllerContext controllerContext)
public (ControllerActionInvokerState State, IFilterMetadata[] Filters) GetState(ControllerContext controllerContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to return two pieces of data (the cache entry and the filters. Any suggestions on how you'd want to surface this?

public ConcurrentDictionary<ActionDescriptor, Entry> Entries { get; } =
new ConcurrentDictionary<ActionDescriptor, Entry>();
public ConcurrentDictionary<ActionDescriptor, ControllerActionInvokerState> Entries { get; } =
new ConcurrentDictionary<ActionDescriptor, ControllerActionInvokerState>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call this FooCacheEntry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

{
var controllerContext = new ControllerContext(context.ActionContext);
// PERF: These are rarely going to be changed, so let's go copy-on-write.
controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories);
controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;

var cacheState = _controllerActionInvokerCache.GetState(controllerContext);
var cacheResult = _controllerActionInvokerCache.GetState(controllerContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cacheEntry

FilterItem[] filters,
Func<ControllerContext, object> controllerFactory,
Action<ControllerContext, object> controllerReleaser,
ControllerBinderFactoryProvider.BinderFactory propertyBinderFactory,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gross


public ControllerBinderFactoryProvider.BinderFactory BinderFactory { get; }

public ObjectMethodExecutor ActionMethodExecutor { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to avoid making this public... this is a shared source type. It can create all kinds of havoc.

{
public static class ControllerBinderFactoryProvider
{
public delegate Task BinderFactory(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a terrible name. ControllerBinder? perhaps?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ControllerBinderDelegate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ControllerBinderHelper 👍

namespace Microsoft.AspNetCore.Mvc.Internal
{
public interface IControllerPropertyActivator
public interface IControllerPropertyActivatorFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider rather than factory would be consistent with the other changes in this PR

IModelBinder modelBinder,
IValueProvider valueProvider,
ParameterDescriptor parameter,
ModelMetadata modelMetadata,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is modelMetadata allowed to be null? It's also called metadata elsewhere in this file, choose one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

/// <summary>
/// <see cref="IControllerActivator"/> that uses type activation to create controllers.
/// </summary>
public class DefaultControllerActivator : IControllerActivator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Where's a bug tracking this work?
  2. If we delete DefaultControllerActivator, what would the alternative be to anyone who used/derived from it?

@pranavkm pranavkm force-pushed the prkrishn/controller-caching branch 2 times, most recently from 7ee0aa8 to 37f7e72 Compare May 15, 2017 19:40
@pranavkm
Copy link
Contributor Author

🆙 📅

@pranavkm
Copy link
Contributor Author

BUMP

ControllerFactory = controllerFactory;
ControllerReleaser = controllerReleaser;
ControllerBinderDelegate = controllerBinderDelegate;
Filters = filters;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you call this cachedFilters or something to make it clear that it's not supposed to be used as-is?


public void Activate(ControllerContext context, object controller)
{
EnsureInitialized();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use LazyInitializer here instead?

@@ -1,10 +1,15 @@
// 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 Microsoft.AspNetCore.Mvc.Controllers;

namespace Microsoft.AspNetCore.Mvc.Internal
{
public interface IControllerPropertyActivator
{
void Activate(ControllerContext context, object controller);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed? Why/where?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use it in DefaultControllerFactory (since that type wasn't changed).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I'm not super worried about this because it's internal any way.

}

public void Activate(ControllerContext actionContext, object controller)
{
EnsureInitialized();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use LazyInitializer

@rynowak
Copy link
Member

rynowak commented May 19, 2017

This is looking good overall

@pranavkm pranavkm force-pushed the prkrishn/controller-caching branch from 37f7e72 to 0baba4d Compare May 19, 2017 18:17
@pranavkm pranavkm force-pushed the prkrishn/controller-caching branch from 0baba4d to 267ea01 Compare May 19, 2017 22:37
@pranavkm
Copy link
Contributor Author

🆙 📅

@@ -43,14 +57,13 @@ private InnerCache CurrentCache
}
}

public ControllerActionInvokerState GetState(ControllerContext controllerContext)
public (ControllerActionInvokerCacheEntry CacheEntry, IFilterMetadata[] Filters) GetCachedResult(ControllerContext controllerContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lowercase names with tuples

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what the C# picked as the convention

@pranavkm pranavkm closed this May 22, 2017
@pranavkm pranavkm deleted the prkrishn/controller-caching branch May 22, 2017 21:20
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants