Skip to content

Commit

Permalink
Merge pull request #1341 from autofac/feature/reflection-cache-clear
Browse files Browse the repository at this point in the history
Move all reflection caches out of statics to a centrally-stored location
  • Loading branch information
tillig authored Sep 19, 2022
2 parents e5bef7c + efce62c commit 86c36ab
Show file tree
Hide file tree
Showing 31 changed files with 1,079 additions and 61 deletions.
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

0 comments on commit 86c36ab

Please sign in to comment.