Skip to content
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

Move all reflection caches out of statics to a centrally-stored location #1341

Merged
merged 14 commits into from
Sep 19, 2022
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
9 changes: 9 additions & 0 deletions src/Autofac/Autofac.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>ContainerResources.resx</DependentUpon>
</Compile>
<Compile Update="Core\ReflectionCacheSetResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ReflectionCacheSetResources.resx</DependentUpon>
</Compile>
<Compile Update="Diagnostics\TracerMessages.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
Expand Down Expand Up @@ -385,6 +390,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ContainerResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Core\ReflectionCacheSetResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ReflectionCacheSetResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Diagnostics\TracerMessages.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
Expand Down
20 changes: 19 additions & 1 deletion src/Autofac/ContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ namespace Autofac;
/// <see cref="RegistrationExtensions"/>
public sealed class ContainerBuilder
{
private static int _builderAlreadyAllocated;

private readonly bool _clearRegistrationCaches;
private readonly IList<DeferredCallback> _configurationCallbacks = new List<DeferredCallback>();
private BuildCallbackService? _buildCallbacks;

private bool _wasBuilt;

/// <summary>
Expand All @@ -47,6 +49,12 @@ public sealed class ContainerBuilder
public ContainerBuilder()
: this(new Dictionary<string, object?>())
{
// If this is not the first container builder we have constructed in this process,
// it's entirely likely we are going to create more (for example, in unit tests).
// So, all container builders after the first will preserve cache's that
// only have the RegistrationCacheUsage.Registration flag, to improve
// the performance of subsequent container builds.
_clearRegistrationCaches = IsFirstContainerBuilder();
}

/// <summary>
Expand Down Expand Up @@ -172,6 +180,9 @@ public IContainer Build(ContainerBuildOptions options = ContainerBuildOptions.No
// Run any build callbacks.
BuildCallbackManager.RunBuildCallbacks(result);

// Allow the reflection cache to empty any registration-time caches to save memory.
ReflectionCacheSet.Shared.OnContainerBuildClearCaches(_clearRegistrationCaches);

return result;
}

Expand Down Expand Up @@ -231,4 +242,11 @@ private void RegisterDefaultAdapters(IComponentRegistryBuilder componentRegistry
componentRegistry.AddRegistrationSource(new StronglyTypedMetaRegistrationSource());
componentRegistry.AddRegistrationSource(new GeneratedFactoryRegistrationSource());
}

private static bool IsFirstContainerBuilder()
{
// First container will start with a value of 0, we will try and set it to 1;
// if the value is 0, it means it's the first builder.
return Interlocked.CompareExchange(ref _builderAlreadyAllocated, 1, 0) == 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ internal static class AutowiringPropertyInjector
/// </summary>
internal const string InstanceTypeNamedParameter = "Autofac.AutowiringPropertyInjector.InstanceType";

private static readonly ConcurrentDictionary<PropertyInfo, Action<object, object?>> PropertySetters = new();

private static readonly ConcurrentDictionary<Type, PropertyInfo[]> InjectableProperties = new();

private static readonly MethodInfo CallPropertySetterOpenGenericMethod =
typeof(AutowiringPropertyInjector).GetDeclaredMethod(nameof(CallPropertySetter));

Expand Down Expand Up @@ -55,10 +51,12 @@ public static void InjectProperties(IComponentContext context, object instance,

var resolveParameters = parameters as Parameter[] ?? parameters.ToArray();

var injectablePropertiesCache = ReflectionCacheSet.Shared.Internal.AutowiringInjectableProperties;

var instanceType = instance.GetType();
var injectableProperties = InjectableProperties.GetOrAdd(instanceType, type => GetInjectableProperties(type).ToArray());
var injectableProperties = injectablePropertiesCache.GetOrAdd(instanceType, type => GetInjectableProperties(type).ToList());

for (var index = 0; index < injectableProperties.Length; index++)
for (var index = 0; index < injectableProperties.Count; index++)
{
var property = injectableProperties[index];

Expand All @@ -78,7 +76,7 @@ public static void InjectProperties(IComponentContext context, object instance,
!(p is NamedParameter n && n.Name.Equals("value", StringComparison.Ordinal)));
if (parameter != null)
{
var setter = PropertySetters.GetOrAdd(property, MakeFastPropertySetter);
var setter = ReflectionCacheSet.Shared.Internal.AutowiringPropertySetters.GetOrAdd(property, MakeFastPropertySetter);
setter(instance, valueProvider!());
continue;
}
Expand All @@ -87,7 +85,7 @@ public static void InjectProperties(IComponentContext context, object instance,
var instanceTypeParameter = new NamedParameter(InstanceTypeNamedParameter, instanceType);
if (context.TryResolveService(propertyService, new Parameter[] { instanceTypeParameter }, out var propertyValue))
{
var setter = PropertySetters.GetOrAdd(property, MakeFastPropertySetter);
var setter = ReflectionCacheSet.Shared.Internal.AutowiringPropertySetters.GetOrAdd(property, MakeFastPropertySetter);
setter(instance, propertyValue);
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;

Expand All @@ -14,8 +13,6 @@ public class ConstructorBinder
{
private static readonly Func<ConstructorInfo, Func<object?[], object>> FactoryBuilder = GetConstructorInvoker;

private static readonly ConcurrentDictionary<ConstructorInfo, Func<object?[], object>> FactoryCache = new();

private readonly ParameterInfo[] _constructorArgs;
private readonly Func<object?[], object>? _factory;
private readonly ParameterInfo? _illegalParameter;
Expand All @@ -35,8 +32,10 @@ public ConstructorBinder(ConstructorInfo constructorInfo)

if (_illegalParameter is null)
{
var factoryCache = ReflectionCacheSet.Shared.Internal.ConstructorBinderFactory;

// Build the invoker.
_factory = FactoryCache.GetOrAdd(Constructor, FactoryBuilder);
_factory = factoryCache.GetOrAdd(constructorInfo, FactoryBuilder);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public class DefaultConstructorFinder : IConstructorFinder
{
private readonly Func<Type, ConstructorInfo[]> _finder;

private static readonly ConcurrentDictionary<Type, ConstructorInfo[]> DefaultPublicConstructorsCache = new();

/// <summary>
/// Initializes a new instance of the <see cref="DefaultConstructorFinder" /> class.
/// </summary>
Expand Down Expand Up @@ -47,7 +45,8 @@ public ConstructorInfo[] FindConstructors(Type targetType)

private static ConstructorInfo[] GetDefaultPublicConstructors(Type type)
{
var retval = DefaultPublicConstructorsCache.GetOrAdd(type, t => t.GetDeclaredPublicConstructors());
var retval = ReflectionCacheSet.Shared.Internal.DefaultPublicConstructors
.GetOrAdd(type, t => t.GetDeclaredPublicConstructors());

if (retval.Length == 0)
{
Expand Down
45 changes: 45 additions & 0 deletions src/Autofac/Core/IReflectionCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Reflection;

namespace Autofac.Core;

/// <summary>
/// Delegate for predicates that can choose whether to remove a member from the
/// reflection cache.
/// </summary>
/// <param name="assembly">
/// The assembly the cache entry relates to (i.e. the source of a type of
/// member).
/// </param>
/// <param name="member">
/// The member information (will be an instance of a more-derived type). This
/// value may be null if the cache entry relates only to an assembly.
/// </param>
/// <returns>
/// True to remove the member from the cache, false to leave it.
/// </returns>
public delegate bool ReflectionCacheClearPredicate(Assembly assembly, MemberInfo? member);

/// <summary>
/// Defines an individual store of cached reflection data.
/// </summary>
public interface IReflectionCache
{
/// <summary>
/// Gets a value indicating when the cache is used.
/// </summary>
ReflectionCacheUsage Usage { get; }

/// <summary>
/// Clear the cache.
/// </summary>
void Clear();

/// <summary>
/// Conditionally clear the cache, based on the provided predicate.
/// </summary>
/// <param name="predicate">A predicate that returns true for cache entries that should be cleared.</param>
void Clear(ReflectionCacheClearPredicate predicate);
}
11 changes: 7 additions & 4 deletions src/Autofac/Core/ImplicitRegistrationSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Reflection;
using Autofac.Builder;
using Autofac.Util;
using Autofac.Util.Cache;

namespace Autofac.Core;

Expand All @@ -18,7 +19,7 @@ public abstract class ImplicitRegistrationSource : IRegistrationSource
private static readonly MethodInfo CreateRegistrationMethod = typeof(ImplicitRegistrationSource).GetDeclaredMethod(nameof(CreateRegistration));

private readonly Type _type;
private readonly ConcurrentDictionary<Type, RegistrationCreator> _methodCache;
private readonly string _cacheKey;

/// <summary>
/// Initializes a new instance of the <see cref="ImplicitRegistrationSource"/> class.
Expand All @@ -27,6 +28,7 @@ public abstract class ImplicitRegistrationSource : IRegistrationSource
protected ImplicitRegistrationSource(Type type)
{
_type = type ?? throw new ArgumentNullException(nameof(type));
_cacheKey = $"{nameof(ImplicitRegistrationSource)}.{Guid.NewGuid()}";

if (!type.IsGenericType)
{
Expand All @@ -37,8 +39,6 @@ protected ImplicitRegistrationSource(Type type)
{
throw new InvalidOperationException(ImplicitRegistrationSourceResources.GenericTypeMustBeUnary);
}

_methodCache = new ConcurrentDictionary<Type, RegistrationCreator>();
}

/// <inheritdoc />
Expand All @@ -56,7 +56,10 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Fun

var valueType = swt.ServiceType.GenericTypeArguments[0];
var valueService = swt.ChangeType(valueType);
var registrationCreator = _methodCache.GetOrAdd(valueType, t =>

var methodCache = ReflectionCacheSet.Shared.GetOrCreateCache<ReflectionCacheDictionary<Type, RegistrationCreator>>(_cacheKey);

var registrationCreator = methodCache.GetOrAdd(valueType, t =>
{
return CreateRegistrationMethod.MakeGenericMethod(t).CreateDelegate<RegistrationCreator>(this);
});
Expand Down
76 changes: 76 additions & 0 deletions src/Autofac/Core/InternalReflectionCaches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Reflection;
using Autofac.Core.Activators.Reflection;
using Autofac.Util;
using Autofac.Util.Cache;

namespace Autofac.Core;

/// <summary>
/// Defines known, internal-only caches that are used in relatively hot paths, so we want to
/// avoid the additional dictionary lookup in <see cref="ReflectionCacheSet.GetOrCreateCache(string)"/>.
/// </summary>
internal class InternalReflectionCaches
{
/// <summary>
/// Gets the cache used by <see cref="Util.AssemblyExtensions.GetPermittedTypesForAssemblyScanning"/>.
/// </summary>
public ReflectionCacheAssemblyDictionary<Assembly, IEnumerable<Type>> AssemblyScanAllowedTypes { get; }

/// <summary>
/// Gets the cache used by <see cref="InternalTypeExtensions.IsGenericEnumerableInterfaceType"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> IsGenericEnumerableInterface { get; }

/// <summary>
/// Gets the cache used by <see cref="InternalTypeExtensions.IsGenericListOrCollectionInterfaceType"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> IsGenericListOrCollectionInterfaceType { get; }

/// <summary>
/// Gets the cache used by <see cref="InternalTypeExtensions.IsGenericTypeDefinedBy"/>.
/// </summary>
public ReflectionCacheTupleDictionary<Type, bool> IsGenericTypeDefinedBy { get; }

/// <summary>
/// Gets the cache used by <see cref="ConstructorBinder"/>.
/// </summary>
public ReflectionCacheDictionary<ConstructorInfo, Func<object?[], object>> ConstructorBinderFactory { get; }

/// <summary>
/// Gets a cache used by <see cref="AutowiringPropertyInjector.InjectProperties"/>.
/// </summary>
public ReflectionCacheDictionary<PropertyInfo, Action<object, object?>> AutowiringPropertySetters { get; }

/// <summary>
/// Gets a cache used by <see cref="AutowiringPropertyInjector.InjectProperties"/>.
/// </summary>
public ReflectionCacheDictionary<Type, IReadOnlyList<PropertyInfo>> AutowiringInjectableProperties { get; }

/// <summary>
/// Gets a cache used by <see cref="DefaultConstructorFinder"/>.
/// </summary>
public ReflectionCacheDictionary<Type, ConstructorInfo[]> DefaultPublicConstructors { get; }

/// <summary>
/// Initializes a new instance of the <see cref="InternalReflectionCaches"/> class.
/// </summary>
/// <param name="set">The cache set used to retrieve the required caches.</param>
public InternalReflectionCaches(ReflectionCacheSet set)
{
AssemblyScanAllowedTypes = set.GetOrCreateCache(nameof(AssemblyScanAllowedTypes), _ => new ReflectionCacheAssemblyDictionary<Assembly, IEnumerable<Type>>
{
Usage = ReflectionCacheUsage.Registration,
});

IsGenericEnumerableInterface = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(IsGenericEnumerableInterface));
IsGenericListOrCollectionInterfaceType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(IsGenericListOrCollectionInterfaceType));
IsGenericTypeDefinedBy = set.GetOrCreateCache<ReflectionCacheTupleDictionary<Type, bool>>(nameof(IsGenericTypeDefinedBy));
ConstructorBinderFactory = set.GetOrCreateCache<ReflectionCacheDictionary<ConstructorInfo, Func<object?[], object>>>(nameof(ConstructorBinderFactory));
AutowiringPropertySetters = set.GetOrCreateCache<ReflectionCacheDictionary<PropertyInfo, Action<object, object?>>>(nameof(AutowiringPropertySetters));
AutowiringInjectableProperties = set.GetOrCreateCache<ReflectionCacheDictionary<Type, IReadOnlyList<PropertyInfo>>>(nameof(AutowiringInjectableProperties));
DefaultPublicConstructors = set.GetOrCreateCache<ReflectionCacheDictionary<Type, ConstructorInfo[]>>(nameof(DefaultPublicConstructors));
}
}
Loading