diff --git a/bench/Autofac.Benchmarks/BenchmarkSet.cs b/bench/Autofac.Benchmarks/BenchmarkSet.cs index 3b8463a25..8c114b668 100644 --- a/bench/Autofac.Benchmarks/BenchmarkSet.cs +++ b/bench/Autofac.Benchmarks/BenchmarkSet.cs @@ -13,6 +13,7 @@ public static class BenchmarkSet typeof(ConcurrencyBenchmark), typeof(ConcurrencyNestedScopeBenchmark), typeof(KeyedGenericBenchmark), + typeof(KeyedAnyKeySimpleBenchmark), typeof(KeyedNestedBenchmark), typeof(KeyedSimpleBenchmark), typeof(KeylessGenericBenchmark), diff --git a/bench/Autofac.Benchmarks/Decorators/DecoratorBenchmarkBase.cs b/bench/Autofac.Benchmarks/Decorators/DecoratorBenchmarkBase.cs index 42ed50f32..cbb7e9fb2 100644 --- a/bench/Autofac.Benchmarks/Decorators/DecoratorBenchmarkBase.cs +++ b/bench/Autofac.Benchmarks/Decorators/DecoratorBenchmarkBase.cs @@ -1,8 +1,6 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Microsoft.CodeAnalysis.CSharp.Syntax; - namespace Autofac.Benchmarks.Decorators; public abstract class DecoratorBenchmarkBase diff --git a/bench/Autofac.Benchmarks/Decorators/KeyedAnyKeySimpleBenchmark.cs b/bench/Autofac.Benchmarks/Decorators/KeyedAnyKeySimpleBenchmark.cs new file mode 100644 index 000000000..5c5bc717d --- /dev/null +++ b/bench/Autofac.Benchmarks/Decorators/KeyedAnyKeySimpleBenchmark.cs @@ -0,0 +1,29 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac.Benchmarks.Decorators.Scenario; +using Autofac.Core; + +namespace Autofac.Benchmarks.Decorators; + +/// +/// Benchmarks keyed decorators when components are registered with AnyKey. +/// +public class KeyedAnyKeySimpleBenchmark : DecoratorBenchmarkBase +{ + [GlobalSetup] + public void Setup() + { + var builder = new ContainerBuilder(); + + builder.RegisterType() + .Keyed(KeyedService.AnyKey); + builder.RegisterType() + .Keyed(KeyedService.AnyKey); + builder.RegisterDecorator( + (c, inner) => new CommandHandlerDecoratorOne(inner), + fromKey: "handler"); + + Container = builder.Build(); + } +} diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs index c761c345c..cf79ed6ce 100644 --- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs +++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs @@ -228,7 +228,11 @@ private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuil var instance = boundConstructor.Instantiate(); - InjectProperties(instance, context, boundConstructor, GetAllParameters(context.Parameters)); + if (ShouldInjectProperties(boundConstructor)) + { + var prioritizedParameters = GetAllParameters(context.Parameters); + InjectProperties(instance, context, boundConstructor, prioritizedParameters); + } context.Instance = instance; @@ -422,20 +426,12 @@ private string GetBindingFailureMessage(BoundConstructor[] constructorBindings) private void InjectProperties(object instance, IComponentContext context, BoundConstructor constructor, IEnumerable allParameters) { - // We only need to do any injection if we have a set of property information. - if (_defaultFoundPropertySet is null) - { - return; - } - - // If this constructor sets all required members, - // and we have no configured properties, we can just jump out. - if (_configuredProperties.Length == 0 && constructor.SetsRequiredMembers) + if (!ShouldInjectProperties(constructor)) { return; } - var workingSetOfProperties = (InjectablePropertyState[])_defaultFoundPropertySet.Clone(); + var workingSetOfProperties = (InjectablePropertyState[])_defaultFoundPropertySet!.Clone(); foreach (var configuredProperty in _configuredProperties) { @@ -509,6 +505,16 @@ private void InjectProperties(object instance, IComponentContext context, BoundC } } + private bool ShouldInjectProperties(BoundConstructor constructor) + { + if (_defaultFoundPropertySet is null) + { + return false; + } + + return _configuredProperties.Length != 0 || !constructor.SetsRequiredMembers; + } + private string BuildRequiredPropertyResolutionMessage(IReadOnlyList failingRequiredProperties) { var propertyDescriptions = new StringBuilder(); diff --git a/src/Autofac/Core/KeyedServiceParameterInjector.cs b/src/Autofac/Core/KeyedServiceParameterInjector.cs index 421b56537..709046cf7 100644 --- a/src/Autofac/Core/KeyedServiceParameterInjector.cs +++ b/src/Autofac/Core/KeyedServiceParameterInjector.cs @@ -132,6 +132,21 @@ private static IEnumerable AppendKeyParameter(IEnumerable return new Parameter[] { keyParameter }; } + // Build a concrete array to avoid LINQ AppendIterator allocation. + if (parameters is IReadOnlyCollection collection) + { + var result = new Parameter[collection.Count + 1]; + var i = 0; + foreach (var p in collection) + { + result[i++] = p; + } + + result[i] = keyParameter; + return result; + } + + // Fallback for unknown enumerable types. return parameters.Append(keyParameter); } } diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index b0160473a..665109597 100644 --- a/src/Autofac/Core/Lifetime/LifetimeScope.cs +++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; -using System.Threading; #if NET5_0_OR_GREATER using System.Runtime.Loader; #endif diff --git a/src/Autofac/Core/ReflectionCacheSet.cs b/src/Autofac/Core/ReflectionCacheSet.cs index 63020f146..8c1c8d9bb 100644 --- a/src/Autofac/Core/ReflectionCacheSet.cs +++ b/src/Autofac/Core/ReflectionCacheSet.cs @@ -18,6 +18,8 @@ public sealed class ReflectionCacheSet private readonly ConcurrentDictionary _caches = new(); + private readonly List> _externalCaches = new(); + /// /// Initializes a new instance of the class. /// @@ -101,6 +103,26 @@ public TCacheStore GetOrCreateCache(string cacheName, Func + /// Register an externally-owned so it participates + /// in and calls. + /// The cache is held via a weak reference so it does not prevent garbage collection + /// of the owning object (e.g., a container or registration source). + /// + /// The cache to register. + public void RegisterExternalCache(IReflectionCache cache) + { + if (cache is null) + { + throw new ArgumentNullException(nameof(cache)); + } + + lock (_externalCaches) + { + _externalCaches.Add(new WeakReference(cache)); + } + } + /// /// Clear the internal reflection cache. Only call this method if you are /// dynamically unloading types from the process; calling this method @@ -117,6 +139,8 @@ public void Clear() { cache.Clear(); } + + ClearExternalCaches(static cache => cache.Clear()); } /// @@ -139,6 +163,8 @@ public void Clear(ReflectionCacheClearPredicate predicate) { cache.Clear(predicate); } + + ClearExternalCaches(cache => cache.Clear(predicate)); } /// @@ -173,6 +199,25 @@ private static bool TryGetSharedCache([NotNullWhen(true)] out ReflectionCacheSet return _sharedSet.TryGetTarget(out sharedCache); } + private void ClearExternalCaches(Action clearAction) + { + lock (_externalCaches) + { + for (var i = _externalCaches.Count - 1; i >= 0; i--) + { + if (_externalCaches[i].TryGetTarget(out var cache)) + { + clearAction(cache); + } + else + { + // Prune dead references. + _externalCaches.RemoveAt(i); + } + } + } + } + private IEnumerable GetAllCaches() { foreach (var externalItem in _caches) diff --git a/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs index e62ab0275..2077e40cc 100644 --- a/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs @@ -3,7 +3,6 @@ using System.Globalization; using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -29,20 +28,23 @@ private ActivatorErrorHandlingMiddleware() /// public void Execute(ResolveRequestContext context, Action next) { - if (!AutofacMetrics.MetricsEnabled) + try { - ExecuteCore(context, next); - return; - } + next(context); - var timer = ValueStopwatch.StartNew(); - try + if (context.Instance is null) + { + // Exited the Activation Stage without creating an instance. + throw new DependencyResolutionException(MiddlewareMessages.ActivatorDidNotPopulateInstance); + } + } + catch (ObjectDisposedException) { - ExecuteCore(context, next); + throw; } - finally + catch (Exception ex) { - AutofacMetrics.RecordMiddlewareExecution(nameof(ActivatorErrorHandlingMiddleware), timer.GetElapsedTime()); + throw PropagateActivationException(context.Registration.Activator, ex); } } @@ -65,26 +67,4 @@ private static DependencyResolutionException PropagateActivationException(IInsta result.Data[ActivatorChainExceptionData] = activatorChain; return result; } - - private static void ExecuteCore(ResolveRequestContext context, Action next) - { - try - { - next(context); - - if (context.Instance is null) - { - // Exited the Activation Stage without creating an instance. - throw new DependencyResolutionException(MiddlewareMessages.ActivatorDidNotPopulateInstance); - } - } - catch (ObjectDisposedException) - { - throw; - } - catch (Exception ex) - { - throw PropagateActivationException(context.Registration.Activator, ex); - } - } } diff --git a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs index d5d7e2f34..a10cd6b17 100644 --- a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Runtime.CompilerServices; using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -39,51 +38,6 @@ public CircularDependencyDetectorMiddleware(int maxResolveDepth) /// public void Execute(ResolveRequestContext context, Action next) - { - if (!AutofacMetrics.MetricsEnabled) - { - ExecuteCore(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - ExecuteCore(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(CircularDependencyDetectorMiddleware), timer.GetElapsedTime()); - } - } - - /// - public override string ToString() => nameof(CircularDependencyDetectorMiddleware); - - private static string CreateDependencyGraphTo(IComponentRegistration registration, IEnumerable requestStack) - { - if (registration == null) - { - throw new ArgumentNullException(nameof(registration)); - } - - if (requestStack == null) - { - throw new ArgumentNullException(nameof(requestStack)); - } - - var dependencyGraph = Display(registration); - - return requestStack.Select(a => a.Registration) - .Aggregate(dependencyGraph, (current, requestor) => Display(requestor) + " -> " + current); - } - - private static string Display(IComponentRegistration registration) - { - return registration.Activator.DisplayName(); - } - - private void ExecuteCore(ResolveRequestContext context, Action next) { if (context.Operation is not IDependencyTrackingResolveOperation dependencyTrackingResolveOperation) { @@ -141,4 +95,30 @@ private void ExecuteCore(ResolveRequestContext context, Action + public override string ToString() => nameof(CircularDependencyDetectorMiddleware); + + private static string CreateDependencyGraphTo(IComponentRegistration registration, IEnumerable requestStack) + { + if (registration == null) + { + throw new ArgumentNullException(nameof(registration)); + } + + if (requestStack == null) + { + throw new ArgumentNullException(nameof(requestStack)); + } + + var dependencyGraph = Display(registration); + + return requestStack.Select(a => a.Registration) + .Aggregate(dependencyGraph, (current, requestor) => Display(requestor) + " -> " + current); + } + + private static string Display(IComponentRegistration registration) + { + return registration.Activator.DisplayName(); + } } diff --git a/src/Autofac/Core/Resolving/Middleware/CoreEventMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/CoreEventMiddleware.cs index 1bc5c3647..90d23e72d 100644 --- a/src/Autofac/Core/Resolving/Middleware/CoreEventMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/CoreEventMiddleware.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -40,20 +39,6 @@ internal CoreEventMiddleware(ResolveEventType eventType, PipelinePhase phase, Ac /// public void Execute(ResolveRequestContext context, Action next) { - if (!AutofacMetrics.MetricsEnabled) - { - _callback(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - _callback(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(ToString(), timer.GetElapsedTime()); - } + _callback(context, next); } } diff --git a/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs index 4220b7375..252681e3c 100644 --- a/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -33,21 +32,7 @@ public DelegateMiddleware(string descriptor, PipelinePhase phase, Action public void Execute(ResolveRequestContext context, Action next) { - if (!AutofacMetrics.MetricsEnabled) - { - _callback(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - _callback(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(ToString(), timer.GetElapsedTime()); - } + _callback(context, next); } /// diff --git a/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs index d471b49a7..69b69d02b 100644 --- a/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -26,28 +25,6 @@ private DisposalTrackingMiddleware() /// public void Execute(ResolveRequestContext context, Action next) - { - if (!AutofacMetrics.MetricsEnabled) - { - ExecuteCore(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - ExecuteCore(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(DisposalTrackingMiddleware), timer.GetElapsedTime()); - } - } - - /// - public override string ToString() => nameof(DisposalTrackingMiddleware); - - private static void ExecuteCore(ResolveRequestContext context, Action next) { next(context); @@ -67,4 +44,7 @@ private static void ExecuteCore(ResolveRequestContext context, Action + public override string ToString() => nameof(DisposalTrackingMiddleware); } diff --git a/src/Autofac/Core/Resolving/Middleware/RegistrationPipelineInvokeMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/RegistrationPipelineInvokeMiddleware.cs index 2b8244b84..c9a9bd6f0 100644 --- a/src/Autofac/Core/Resolving/Middleware/RegistrationPipelineInvokeMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/RegistrationPipelineInvokeMiddleware.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -26,21 +25,7 @@ private RegistrationPipelineInvokeMiddleware() /// public void Execute(ResolveRequestContext context, Action next) { - if (!AutofacMetrics.MetricsEnabled) - { - context.Registration.ResolvePipeline.Invoke(context); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - context.Registration.ResolvePipeline.Invoke(context); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(RegistrationPipelineInvokeMiddleware), timer.GetElapsedTime()); - } + context.Registration.ResolvePipeline.Invoke(context); } /// diff --git a/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs index 4d467ccf8..3d56550d1 100644 --- a/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Text; using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -21,35 +20,13 @@ private ScopeSelectionMiddleware() /// /// Gets the singleton instance of the . /// - public static ScopeSelectionMiddleware Instance => new(); + public static ScopeSelectionMiddleware Instance { get; } = new ScopeSelectionMiddleware(); /// public PipelinePhase Phase => PipelinePhase.ScopeSelection; /// public void Execute(ResolveRequestContext context, Action next) - { - if (!AutofacMetrics.MetricsEnabled) - { - ExecuteCore(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - ExecuteCore(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(ScopeSelectionMiddleware), timer.GetElapsedTime()); - } - } - - /// - public override string ToString() => nameof(ScopeSelectionMiddleware); - - private static void ExecuteCore(ResolveRequestContext context, Action next) { try { @@ -70,4 +47,7 @@ private static void ExecuteCore(ResolveRequestContext context, Action + public override string ToString() => nameof(ScopeSelectionMiddleware); } diff --git a/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs index e21ca07e4..ad0837f86 100644 --- a/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -21,28 +20,6 @@ internal class SharingMiddleware : IResolveMiddleware /// public void Execute(ResolveRequestContext context, Action next) - { - if (!AutofacMetrics.MetricsEnabled) - { - ExecuteCore(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - ExecuteCore(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(SharingMiddleware), timer.GetElapsedTime()); - } - } - - /// - public override string ToString() => nameof(SharingMiddleware); - - private static void ExecuteCore(ResolveRequestContext context, Action next) { var registration = context.Registration; var decoratorRegistration = context.DecoratorTarget; @@ -78,4 +55,7 @@ private static void ExecuteCore(ResolveRequestContext context, Action + public override string ToString() => nameof(SharingMiddleware); } diff --git a/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs index dd1623109..20a46c765 100644 --- a/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs @@ -3,7 +3,6 @@ using Autofac.Builder; using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Middleware; @@ -26,28 +25,6 @@ private StartableMiddleware() /// public void Execute(ResolveRequestContext context, Action next) - { - if (!AutofacMetrics.MetricsEnabled) - { - ExecuteCore(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - ExecuteCore(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(StartableMiddleware), timer.GetElapsedTime()); - } - } - - /// - public override string ToString() => nameof(StartableMiddleware); - - private static void ExecuteCore(ResolveRequestContext context, Action next) { next(context); @@ -62,4 +39,7 @@ private static void ExecuteCore(ResolveRequestContext context, Action + public override string ToString() => nameof(StartableMiddleware); } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 8de32ea66..23b750d1d 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -210,6 +210,61 @@ Action Chain(Action next, IResolve { var stagePhase = stage.Phase; + // MetricsEnabled is static readonly (set once at startup), so checking here + // at pipeline build time avoids a per-invocation branch in every middleware. + if (AutofacMetrics.MetricsEnabled) + { + var stageName = stage.ToString()!; + + return (context) => + { + if (context.DiagnosticSource.IsEnabled()) + { + context.DiagnosticSource.MiddlewareStart(context, stage); + var succeeded = false; + try + { + context.PhaseReached = stagePhase; + var timer = ValueStopwatch.StartNew(); + try + { + stage.Execute(context, next); + } + finally + { + AutofacMetrics.RecordMiddlewareExecution(stageName, timer.GetElapsedTime()); + } + + succeeded = true; + } + finally + { + if (succeeded) + { + context.DiagnosticSource.MiddlewareSuccess(context, stage); + } + else + { + context.DiagnosticSource.MiddlewareFailure(context, stage); + } + } + } + else + { + context.PhaseReached = stagePhase; + var timer = ValueStopwatch.StartNew(); + try + { + stage.Execute(context, next); + } + finally + { + AutofacMetrics.RecordMiddlewareExecution(stageName, timer.GetElapsedTime()); + } + } + }; + } + return (context) => { // Same basic flow in if/else, but doing a one-time check for diagnostics diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 0e1a9b673..7ed0fc08a 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -99,6 +99,19 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, i throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null); } + // Fast-path: if the registration is shared and already cached, skip the entire + // pipeline (context allocation, middleware chain, etc.). Only safe when: + // - no event handlers or diagnostics are observing, + // - the service pipeline is the default (no decorators or custom service middleware). + if (request.Registration.Sharing == InstanceSharing.Shared && + request.ResolvePipeline == ServicePipelines.DefaultServicePipeline && + ResolveRequestBeginning is null && + !DiagnosticSource.IsEnabled() && + currentOperationScope.TryGetSharedInstance(request.Registration.Id, request.DecoratorTarget?.Id, out var cached)) + { + return cached; + } + // Create a new request context. var requestContext = new DefaultResolveRequestContext(this, request, currentOperationScope, DiagnosticSource); diff --git a/src/Autofac/Features/Collections/CollectionRegistrationSource.cs b/src/Autofac/Features/Collections/CollectionRegistrationSource.cs index 79b96e437..e497d9987 100644 --- a/src/Autofac/Features/Collections/CollectionRegistrationSource.cs +++ b/src/Autofac/Features/Collections/CollectionRegistrationSource.cs @@ -203,8 +203,13 @@ private static Func GenerateArrayFactory(Type elementType) continue; } - foreach (var keyed in registration.Services.OfType()) + foreach (var svc in registration.Services) { + if (svc is not KeyedService keyed) + { + continue; + } + if (keyed.ServiceType != elementType || KeyedService.IsAnyKey(keyed.ServiceKey)) { continue; @@ -215,34 +220,38 @@ private static Func GenerateArrayFactory(Type elementType) continue; } - var serviceRegistrations = registry - .ServiceRegistrationsFor(keyed) - .Where(cr => - !cr.Registration.Options.HasOption(RegistrationOptions.ExcludeFromCollections) && - !cr.Registration.Metadata.ContainsKey(MetadataKeys.AnyKeyAdapter)); - - foreach (var serviceRegistration in serviceRegistrations) + foreach (var serviceRegistration in registry.ServiceRegistrationsFor(keyed)) { - // Return both the keyed service and the registration so callers can issue - // resolve requests that still know the original key. + if (serviceRegistration.Registration.Options.HasOption(RegistrationOptions.ExcludeFromCollections) || + serviceRegistration.Registration.Metadata.ContainsKey(MetadataKeys.AnyKeyAdapter)) + { + continue; + } + result.Add((keyed, serviceRegistration)); } } } - return result - .OrderBy(tuple => tuple.Item2.Registration.GetRegistrationOrder()) - .ToList(); + result.Sort(static (a, b) => a.Item2.GetRegistrationOrder().CompareTo(b.Item2.GetRegistrationOrder())); + return result; } private static List<(Service Service, ServiceRegistration Registration)> BuildStandardRegistrationList(IComponentRegistry registry, Service elementTypeService) { - return registry - .ServiceRegistrationsFor(elementTypeService) - .Where(cr => !cr.Registration.Options.HasOption(RegistrationOptions.ExcludeFromCollections)) - .OrderBy(cr => cr.Registration.GetRegistrationOrder()) - .Select(cr => ((Service)elementTypeService, cr)) - .ToList(); + var registrations = registry.ServiceRegistrationsFor(elementTypeService); + var result = new List<(Service, ServiceRegistration)>(); + + foreach (var cr in registrations) + { + if (!cr.Registration.Options.HasOption(RegistrationOptions.ExcludeFromCollections)) + { + result.Add((elementTypeService, cr)); + } + } + + result.Sort(static (a, b) => a.Item2.GetRegistrationOrder().CompareTo(b.Item2.GetRegistrationOrder())); + return result; } private static IList BuildCollection( diff --git a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs index ef86d6d70..36245e7d3 100644 --- a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs +++ b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs @@ -5,7 +5,6 @@ using Autofac.Core.Registration; using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Features.Decorators; @@ -31,30 +30,11 @@ public DecoratorMiddleware(DecoratorService decoratorService, IComponentRegistra /// public PipelinePhase Phase => PipelinePhase.Decoration; - /// - public void Execute(ResolveRequestContext context, Action next) - { - if (!AutofacMetrics.MetricsEnabled) - { - ExecuteCore(context, next); - return; - } - - var timer = ValueStopwatch.StartNew(); - try - { - ExecuteCore(context, next); - } - finally - { - AutofacMetrics.RecordMiddlewareExecution(nameof(DecoratorMiddleware), timer.GetElapsedTime()); - } - } - /// public override string ToString() => nameof(DecoratorMiddleware) + " [" + _decoratorRegistration.Activator.LimitType.Name + "]"; - private void ExecuteCore(ResolveRequestContext context, Action next) + /// + public void Execute(ResolveRequestContext context, Action next) { // Go down the pipeline first, need that instance. next(context); diff --git a/src/Autofac/Features/KeyedServices/AnyKeyRegistrationSource.cs b/src/Autofac/Features/KeyedServices/AnyKeyRegistrationSource.cs index 83ebd9efb..ca034d406 100644 --- a/src/Autofac/Features/KeyedServices/AnyKeyRegistrationSource.cs +++ b/src/Autofac/Features/KeyedServices/AnyKeyRegistrationSource.cs @@ -6,6 +6,7 @@ using Autofac.Core.Activators.Delegate; using Autofac.Core.Registration; using Autofac.Util; +using Autofac.Util.Cache; namespace Autofac.Features.KeyedServices; @@ -14,6 +15,21 @@ namespace Autofac.Features.KeyedServices; /// internal sealed class AnyKeyRegistrationSource : IRegistrationSource { + private readonly ReflectionCacheKeyedServiceDictionary _adapterCache; + + /// + /// Initializes a new instance of the class. + /// + public AnyKeyRegistrationSource() + { + _adapterCache = new ReflectionCacheKeyedServiceDictionary + { + Usage = ReflectionCacheUsage.Resolution, + }; + + ReflectionCacheSet.Shared.RegisterExternalCache(_adapterCache); + } + /// public bool IsAdapterForIndividualComponents => true; @@ -37,6 +53,11 @@ public IEnumerable RegistrationsFor(Service service, Fun return Enumerable.Empty(); } + if (_adapterCache.TryGetValue(keyedService, out var cached)) + { + return cached; + } + // If there are already specific registrations for this key, do nothing. if (registrationAccessor(service).Any()) { @@ -51,7 +72,14 @@ public IEnumerable RegistrationsFor(Service service, Fun return Enumerable.Empty(); } - return anyKeyRegistrations.Select(r => CreateAdapterRegistration(r, keyedService)); + var adapters = new IComponentRegistration[anyKeyRegistrations.Length]; + for (var i = 0; i < anyKeyRegistrations.Length; i++) + { + adapters[i] = CreateAdapterRegistration(anyKeyRegistrations[i], keyedService); + } + + _adapterCache.TryAdd(keyedService, adapters); + return adapters; } private static ComponentRegistration CreateAdapterRegistration(ServiceRegistration anyKeyRegistration, KeyedService requestedService) diff --git a/src/Autofac/Util/Cache/ReflectionCacheKeyedServiceDictionary.cs b/src/Autofac/Util/Cache/ReflectionCacheKeyedServiceDictionary.cs new file mode 100644 index 000000000..4ba80adee --- /dev/null +++ b/src/Autofac/Util/Cache/ReflectionCacheKeyedServiceDictionary.cs @@ -0,0 +1,44 @@ +// 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.Reflection; +using Autofac.Core; + +namespace Autofac.Util.Cache; + +/// +/// A reflection cache dictionary keyed on , using the service type for cache eviction. +/// +/// The value type. +internal sealed class ReflectionCacheKeyedServiceDictionary + : ConcurrentDictionary, IReflectionCache +{ + /// + public ReflectionCacheUsage Usage { get; set; } = ReflectionCacheUsage.All; + + /// + public void Clear(ReflectionCacheClearPredicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (Count == 0) + { + return; + } + + var reusableAssemblySet = new HashSet(); + + foreach (var kvp in this) + { + var serviceType = kvp.Key.ServiceType; + if (predicate(serviceType, TypeAssemblyReferenceProvider.GetAllReferencedAssemblies(serviceType, reusableAssemblySet))) + { + TryRemove(kvp.Key, out _); + } + } + } +} diff --git a/src/Autofac/Util/Cache/ReflectionCacheParameterDictionary.cs b/src/Autofac/Util/Cache/ReflectionCacheParameterDictionary.cs index a510fc266..b6efa5cb4 100644 --- a/src/Autofac/Util/Cache/ReflectionCacheParameterDictionary.cs +++ b/src/Autofac/Util/Cache/ReflectionCacheParameterDictionary.cs @@ -11,7 +11,7 @@ namespace Autofac.Util.Cache; /// A reflection cache dictionary, keyed on a . /// /// The value type. -public sealed class ReflectionCacheParameterDictionary +internal sealed class ReflectionCacheParameterDictionary : ConcurrentDictionary, IReflectionCache { /// diff --git a/test/Autofac.Test/ResolutionExtensionsTests.cs b/test/Autofac.Test/ResolutionExtensionsTests.cs index 23b3e1562..dd9ec8155 100644 --- a/test/Autofac.Test/ResolutionExtensionsTests.cs +++ b/test/Autofac.Test/ResolutionExtensionsTests.cs @@ -1,8 +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.Generic; -using System.Linq; using Autofac.Core; using Autofac.Core.Activators.ProvidedInstance; using Autofac.Core.Registration;