-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Use factory pattern for caching in ControllerActionInvoker #6243
Conversation
private readonly Func<ControllerContext, object> _controllerActivatorCreate; | ||
private readonly Action<ControllerContext, object> _controllerActivatorRelease; | ||
|
||
public ControllerActivatorProvider(IEnumerable<IControllerActivator> activators) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we does
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
||
if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(descriptor.ControllerTypeInfo)) | ||
{ | ||
return _controllerActivatorRelease; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to return _dispose
@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. |
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Where's a bug tracking this work?
- If we delete
DefaultControllerActivator
, what would the alternative be to anyone who used/derived from it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- We spoke about it offline, but I don't think there's a tracking work item for it. I'll make for now.
- I'll revive the types. We can do an exact match test so that it leaves users relying on the type unaffected.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👎
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ControllerBinderDelegate
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Where's a bug tracking this work?
- If we delete
DefaultControllerActivator
, what would the alternative be to anyone who used/derived from it?
7ee0aa8
to
37f7e72
Compare
🆙 📅 |
BUMP |
ControllerFactory = controllerFactory; | ||
ControllerReleaser = controllerReleaser; | ||
ControllerBinderDelegate = controllerBinderDelegate; | ||
Filters = filters; |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use LazyInitializer
This is looking good overall |
37f7e72
to
0baba4d
Compare
0baba4d
to
267ea01
Compare
🆙 📅 |
@@ -43,14 +57,13 @@ private InnerCache CurrentCache | |||
} | |||
} | |||
|
|||
public ControllerActionInvokerState GetState(ControllerContext controllerContext) | |||
public (ControllerActionInvokerCacheEntry CacheEntry, IFilterMetadata[] Filters) GetCachedResult(ControllerContext controllerContext) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
Fixes #6249