diff --git a/src/Autofac/ContainerBuilder.cs b/src/Autofac/ContainerBuilder.cs index 388dfbc80..cd1d9a4bf 100644 --- a/src/Autofac/ContainerBuilder.cs +++ b/src/Autofac/ContainerBuilder.cs @@ -30,6 +30,7 @@ using System.Linq; using Autofac.Builder; using Autofac.Core; +using Autofac.Core.Resolving; using Autofac.Features.Collections; using Autofac.Features.GeneratedFactories; using Autofac.Features.Indexed; @@ -98,6 +99,17 @@ internal ContainerBuilder(IDictionary properties) /// public IDictionary Properties { get; } + /// + /// Sets the maximum size of the stack used when resolving a single component (and all of its transitive dependencies) from the container. + /// If this limit is exceeded the component will be treated as a circular dependency. + /// + /// Maximum size of the stack used for resolve operations. + public ContainerBuilder WithMaxResolveStackDepth(int maximumStackDepth) + { + Properties[CircularDependencyDetector.MaxResolveStackDepthPropertyName] = maximumStackDepth; + return this; + } + /// /// Register a callback that will be invoked when the container is configured. /// diff --git a/src/Autofac/Core/Resolving/CircularDependencyDetector.cs b/src/Autofac/Core/Resolving/CircularDependencyDetector.cs index b5980c818..ba01cc7eb 100644 --- a/src/Autofac/Core/Resolving/CircularDependencyDetector.cs +++ b/src/Autofac/Core/Resolving/CircularDependencyDetector.cs @@ -30,12 +30,13 @@ namespace Autofac.Core.Resolving { + /// + /// Catch circular dependencies that are triggered by post-resolve processing (e.g. 'OnActivated'). + /// internal class CircularDependencyDetector { - /// - /// Catch circular dependencies that are triggered by post-resolve processing (e.g. 'OnActivated'). - /// - private const int MaxResolveDepth = 50; + internal const string MaxResolveStackDepthPropertyName = "MaxResolveStackDepth"; + private const int DefaultMaxResolveDepth = 50; private static string CreateDependencyGraphTo(IComponentRegistration registration, Stack activationStack) { @@ -53,16 +54,25 @@ private static string Display(IComponentRegistration registration) return registration.Activator.LimitType.FullName ?? string.Empty; } - public static void CheckForCircularDependency(IComponentRegistration registration, Stack activationStack, int callDepth) + public static void CheckForCircularDependency(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, Stack activationStack, int callDepth) { if (registration == null) throw new ArgumentNullException(nameof(registration)); - if (callDepth > MaxResolveDepth) - throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.MaxDepthExceeded, registration)); + if (callDepth > MaxResolveDepth(currentOperationScope)) + { + throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.MaxDepthExceeded, registration) + "\r\nGraph: " + CreateDependencyGraphTo(registration, activationStack)); + } // Checks for circular dependency if (activationStack.Any(a => a.ComponentRegistration == registration)) throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.CircularDependency, CreateDependencyGraphTo(registration, activationStack))); } + + private static int MaxResolveDepth(ISharingLifetimeScope currentOperationScope) + { + return currentOperationScope.ComponentRegistry.Properties.ContainsKey(MaxResolveStackDepthPropertyName) + ? (int)currentOperationScope.ComponentRegistry.Properties[MaxResolveStackDepthPropertyName] + : DefaultMaxResolveDepth; + } } } diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 317528308..6d1f1f55f 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -112,7 +112,7 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, I ++_callDepth; - if (_activationStack.Count > 0) CircularDependencyDetector.CheckForCircularDependency(registration, _activationStack, _callDepth); + if (_activationStack.Count > 0) CircularDependencyDetector.CheckForCircularDependency(currentOperationScope, registration, _activationStack, _callDepth); var activation = new InstanceLookup(registration, this, currentOperationScope, parameters); diff --git a/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs b/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs index c5ca13975..e5151cdf4 100644 --- a/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs +++ b/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs @@ -23,6 +23,40 @@ public void OnCircularDependency_MessageDescribesCycle() Assert.DoesNotContain("System.Object -> System.Object -> System.Object", de.Message); } + [Fact] + public void MaxStackDepthExceeded_ThrowsCircularDependencyException() + { + var builder = new ContainerBuilder().WithMaxResolveStackDepth(10); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + IContainer container = builder.Build(); + + using (var lifetimeScope = container.BeginLifetimeScope(b => b.RegisterType())) + { + Assert.Throws(() => lifetimeScope.Resolve()); + } + } + + [Fact] + public void MaxStackDepthNotExceeded_CanBeResolved() + { + var builder = new ContainerBuilder().WithMaxResolveStackDepth(11); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + IContainer container = builder.Build(); + + using (var lifetimeScope = container.BeginLifetimeScope(b => b.RegisterType())) + { + Assert.NotNull(lifetimeScope.Resolve()); + } + } + [Fact(Skip = "Issue #648")] public void ManualEnumerableRegistrationDoesNotCauseCircularDependency() { @@ -96,5 +130,39 @@ public RootViewModel(IEnumerable plugins, PluginsViewModel pluginsViewM { } } + + public class OuterScopeType + { + public OuterScopeType(OuterScopeType2 outerScopeType2) { } + } + + public class OuterScopeType2 + { + public OuterScopeType2(OuterScopeType3 outerScopeType3) { } + } + + public class OuterScopeType3 + { + public OuterScopeType3(OuterScopeType4 outerScopeType4) { } + + } + + public class OuterScopeType4 + { + public OuterScopeType4(OuterScopeType5 outerScopeType5) { } + } + + public class OuterScopeType5 { } + + public class InnerScopeType + { + public InnerScopeType( + OuterScopeType outerScopeType, + OuterScopeType4 outerScopeType4, + OuterScopeType5 outerScopeType5 + ) + { + } + } } }