From d3f98aaeeaf84f0cffbf942542e65b247d853bae Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Tue, 2 Jun 2020 15:29:27 +0100 Subject: [PATCH 1/5] Implement custom segmented stack for requests Define a new SegmentedStack that allows us maintain segments of the request stack, meaning we can resolve decorators without allocating an entirely new operation. --- src/Autofac/Autofac.csproj | 9 + .../CircularDependencyDetectorMiddleware.cs | 2 +- .../Pipeline/ResolveRequestContext.cs | 15 -- .../Pipeline/ResolveRequestContextBase.cs | 19 +- .../Core/Resolving/ResolveOperationBase.cs | 12 +- src/Autofac/Core/Resolving/SegmentedStack.cs | 236 ++++++++++++++++++ .../SegmentedStackResources.Designer.cs | 90 +++++++ .../Resolving/SegmentedStackResources.resx | 129 ++++++++++ .../Decorators/DecoratorMiddleware.cs | 9 +- .../Core/Pipeline/PipelineBuilderTests.cs | 10 - .../Core/Resolving/SegmentedStackTests.cs | 148 +++++++++++ 11 files changed, 634 insertions(+), 45 deletions(-) create mode 100644 src/Autofac/Core/Resolving/SegmentedStack.cs create mode 100644 src/Autofac/Core/Resolving/SegmentedStackResources.Designer.cs create mode 100644 src/Autofac/Core/Resolving/SegmentedStackResources.resx create mode 100644 test/Autofac.Test/Core/Resolving/SegmentedStackTests.cs diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 8bbf1f015..899f7f14c 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -188,6 +188,11 @@ True True + + True + True + SegmentedStackResources.resx + ServiceResources.resx True @@ -397,6 +402,10 @@ DisposerResources.Designer.cs ResXFileCodeGenerator + + ResXFileCodeGenerator + SegmentedStackResources.Designer.cs + ResXFileCodeGenerator ServiceResources.Designer.cs diff --git a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs index 0a8b982b9..27a3c1d0f 100644 --- a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Resolving.Middleware @@ -78,7 +79,6 @@ public void Execute(ResolveRequestContextBase context, Action - public override object ResolveComponent(ResolveRequest request) - { - return Operation.GetOrCreateInstance(ActivationScope, request); - } - - /// - public override object ResolveComponentWithNewOperation(ResolveRequest request) - { - // Create a new operation, with the current ActivationScope and Tracer. - // Pass in the current operation as a tracing reference. - var operation = new ResolveOperation(ActivationScope, Tracer, Operation); - return operation.Execute(request); - } - /// /// Completes the request context; invokes any event handlers. /// diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 28b8e22ab..77e5f1b0f 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -162,21 +162,10 @@ public void ChangeParameters(IEnumerable newParameters) } /// - public abstract object ResolveComponent(ResolveRequest request); - - /// - /// Resolve an instance of the provided registration within the context, but isolated inside a new - /// . - /// This method should only be used instead of - /// if you need to resolve a component with a completely separate operation and circular dependency verification stack. - /// - /// The resolve request. - /// - /// The component instance. - /// - /// - /// - public abstract object ResolveComponentWithNewOperation(ResolveRequest request); + public object ResolveComponent(ResolveRequest request) + { + return Operation.GetOrCreateInstance(ActivationScope, request); + } /// /// Complete the request, raising any appropriate events. diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index f87cf1589..8a0497526 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -124,7 +124,17 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IR /// Don't want this exposed to the outside world, but we do want it available in the , /// hence it's internal. /// - internal Stack RequestStack { get; } = new Stack(); + internal SegmentedStack RequestStack { get; } = new SegmentedStack(); + + /// + /// Enter a new dependency chain block where subsequent requests inside the operation are allowed to repeat + /// registrations from before the block. + /// + /// A disposable that should be disposed to exit the block. + public IDisposable EnterNewDependencyDetectionBlock() + { + return RequestStack.EnterSegment(); + } /// public event EventHandler? ResolveRequestBeginning; diff --git a/src/Autofac/Core/Resolving/SegmentedStack.cs b/src/Autofac/Core/Resolving/SegmentedStack.cs new file mode 100644 index 000000000..b6e74fed4 --- /dev/null +++ b/src/Autofac/Core/Resolving/SegmentedStack.cs @@ -0,0 +1,236 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Autofac.Core.Resolving +{ + /// + /// Implements a segmented stack of items, which functions like a regular , but allows segments + /// of the stack to be enumerated without including items pushed before the segment. + /// + /// The item type. + internal class SegmentedStack : IEnumerable + where T : class + { + private T[] _array; + private int _next; + private int _activeSegmentBase; + + private const int InitialCapacity = 16; + + /// + /// Initializes a new instance of the class. + /// + public SegmentedStack() + { + _array = new T[InitialCapacity]; + } + + /// + /// Push an item onto the stack. + /// + /// The item. + public void Push(T item) + { + // No null check for item here; internally called method only, known to never be null, and is a very hot path. + var next = _next; + T[] arr = _array; + + // Array bounds checking cast. + if ((uint)next < (uint)arr.Length) + { + arr[next] = item; + _next = next + 1; + } + else + { + PushWithResize(item); + } + } + + // Do not inline; makes it easier to profile stack resizing. + [MethodImpl(MethodImplOptions.NoInlining)] + private void PushWithResize(T item) + { + Array.Resize(ref _array, 2 * _array.Length); + _array[_next] = item; + _next++; + } + + /// + /// Pop the item at the top of the stack (and return it). + /// + /// The item that has just been popped. + public T Pop() + { + int next = _next - 1; + var array = _array; + + // Array bounds checking cast. + if ((uint)next >= (uint)array.Length || next < _activeSegmentBase) + { + // Cannot pop below the active segment position. + throw new InvalidOperationException(SegmentedStackResources.CurrentStackSegmentEmpty); + } + + _next = next; + var item = array[next]; + array[next] = null!; + return item; + } + + /// + /// Gets the count of the items in the active segment. + /// + public int Count => _next - _activeSegmentBase; + + /// + /// Enter a new segment. When this method returns will be zero, and the stack will appear empty. + /// + /// An that will return the stack to the previously active segment when disposed. + public IDisposable EnterSegment() + { + var reset = new StackSegment(this, _activeSegmentBase); + + _activeSegmentBase = _next; + + return reset; + } + + /// + public IEnumerator GetEnumerator() + { + return new Enumerator(this); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + private struct StackSegment : IDisposable + { + private readonly SegmentedStack _stack; + private readonly int _resetPosition; + + public StackSegment(SegmentedStack stack, int resetPosition) + { + _stack = stack; + _resetPosition = resetPosition; + } + + public void Dispose() + { + // If the stack 'next' is not just above the active segment base, then + // the segment was not fully popped before exiting the segment. + if (_stack.Count > 0) + { + throw new InvalidOperationException(SegmentedStackResources.CannotExitSegmentWithRemainingItems); + } + + _stack._activeSegmentBase = _resetPosition; + } + } + + private struct Enumerator : IEnumerator, IEnumerator + { + private readonly SegmentedStack _stack; + private readonly int _activeSegmentBase; + private int _index; + private T? _currentElement; + + internal Enumerator(SegmentedStack stack) + { + _stack = stack; + _index = -2; + _activeSegmentBase = _stack._activeSegmentBase; + _currentElement = null; + } + + public T Current + { + get + { + if (_index < 0) + { + throw new InvalidOperationException(SegmentedStackResources.EnumeratorNotValid); + } + + return _currentElement!; + } + } + + object IEnumerator.Current => Current; + + public void Dispose() + { + _index = -1; + } + + public bool MoveNext() + { + if (_index == -2) + { + // Start the enumerator. + _index = _stack._next - 1; + + if (_index > _activeSegmentBase) + { + _currentElement = _stack._array[_index]; + return true; + } + + return false; + } + + if (_index == -1) + { + // Finished. + return false; + } + + if (--_index >= _activeSegmentBase) + { + _currentElement = _stack._array[_index]; + return true; + } + + _currentElement = null!; + return false; + } + + public void Reset() + { + _index = -2; + _currentElement = null!; + } + } + } +} diff --git a/src/Autofac/Core/Resolving/SegmentedStackResources.Designer.cs b/src/Autofac/Core/Resolving/SegmentedStackResources.Designer.cs new file mode 100644 index 000000000..0921eda06 --- /dev/null +++ b/src/Autofac/Core/Resolving/SegmentedStackResources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Autofac.Core.Resolving { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SegmentedStackResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SegmentedStackResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Resolving.SegmentedStackResources", typeof(SegmentedStackResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cannot exit a stack segment without popping all elements of the segment.. + /// + internal static string CannotExitSegmentWithRemainingItems { + get { + return ResourceManager.GetString("CannotExitSegmentWithRemainingItems", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Current stack segment is empty, cannot pop.. + /// + internal static string CurrentStackSegmentEmpty { + get { + return ResourceManager.GetString("CurrentStackSegmentEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enumerator not in valid state.. + /// + internal static string EnumeratorNotValid { + get { + return ResourceManager.GetString("EnumeratorNotValid", resourceCulture); + } + } + } +} diff --git a/src/Autofac/Core/Resolving/SegmentedStackResources.resx b/src/Autofac/Core/Resolving/SegmentedStackResources.resx new file mode 100644 index 000000000..ead377cb2 --- /dev/null +++ b/src/Autofac/Core/Resolving/SegmentedStackResources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot exit a stack segment without popping all elements of the segment. + + + Current stack segment is empty, cannot pop. + + + Enumerator not in valid state. + + \ No newline at end of file diff --git a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs index 41d4b3d9a..89105a2a8 100644 --- a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs +++ b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs @@ -114,11 +114,14 @@ public void Execute(ResolveRequestContextBase context, Action diff --git a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs index 76013cde5..f54a72209 100644 --- a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -428,16 +428,6 @@ public MockPipelineRequestContext() null) { } - - public override object ResolveComponent(ResolveRequest request) - { - throw new NotImplementedException(); - } - - public override object ResolveComponentWithNewOperation(ResolveRequest request) - { - throw new NotImplementedException(); - } } private class MockLifetimeScope : ISharingLifetimeScope diff --git a/test/Autofac.Test/Core/Resolving/SegmentedStackTests.cs b/test/Autofac.Test/Core/Resolving/SegmentedStackTests.cs new file mode 100644 index 000000000..24cfc7908 --- /dev/null +++ b/test/Autofac.Test/Core/Resolving/SegmentedStackTests.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; +using Autofac.Core.Resolving; +using Xunit; + +namespace Autofac.Test.Core.Resolving +{ + public class SegmentedStackTests + { + [Fact] + public void CanPushAndPopWithoutSegment() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + stack.Push("3"); + Assert.Equal("3", stack.Pop()); + Assert.Equal("2", stack.Pop()); + Assert.Equal("1", stack.Pop()); + } + + [Fact] + public void CannotPopEmptyStack() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + Assert.Equal("2", stack.Pop()); + Assert.Equal("1", stack.Pop()); + Assert.Throws(() => stack.Pop()); + } + + [Fact] + public void CanEnumerateStackItems() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + stack.Push("3"); + + Assert.Equal(stack, new[] { "3", "2", "1" }); + } + + [Fact] + public void CanCreateStackSegment() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + stack.Push("3"); + + using (stack.EnterSegment()) + { + Assert.Equal(stack, Enumerable.Empty()); + + stack.Push("4"); + stack.Push("5"); + stack.Push("6"); + + Assert.Equal(stack, new[] { "6", "5", "4" }); + + stack.Pop(); + stack.Pop(); + stack.Pop(); + + Assert.Equal(stack, Enumerable.Empty()); + } + + Assert.Equal(stack, new[] { "3", "2", "1" }); + } + + [Fact] + public void StackTracksCountForSegmentsCorrectly() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + stack.Push("3"); + + Assert.Equal(3, stack.Count); + + using (stack.EnterSegment()) + { + Assert.Equal(0, stack.Count); + + stack.Push("4"); + stack.Push("5"); + stack.Push("6"); + + Assert.Equal(3, stack.Count); + + stack.Pop(); + stack.Pop(); + stack.Pop(); + + Assert.Equal(0, stack.Count); + } + + Assert.Equal(3, stack.Count); + } + + [Fact] + public void CannotPopEmptySegment() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + stack.Push("3"); + + using (stack.EnterSegment()) + { + Assert.Throws(() => stack.Pop()); + } + } + + [Fact] + public void CannotExitSegmentWithUnpoppedItems() + { + var stack = new SegmentedStack(); + + stack.Push("1"); + stack.Push("2"); + stack.Push("3"); + + Assert.Equal(3, stack.Count); + + var segment = stack.EnterSegment(); + + Assert.Equal(0, stack.Count); + + stack.Push("4"); + stack.Push("5"); + stack.Push("6"); + + Assert.Equal(3, stack.Count); + + stack.Pop(); + + Assert.Throws(() => segment.Dispose()); + } + } +} From a847162ff3db74bf3a0c7b1a729594276289c8a5 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Tue, 9 Jun 2020 08:51:07 +0100 Subject: [PATCH 2/5] In netstandard2.1, use TryEnsureSufficientExecutionStack after we have passed our typicaly max resolve depth, and only throw once it fails. --- .../CircularDependencyDetectorMiddleware.cs | 9 +++++++ .../Features/CircularDependencyTests.cs | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs index 27a3c1d0f..73ddf54b3 100644 --- a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs @@ -68,7 +68,16 @@ public void Execute(ResolveRequestContextBase context, Action _maxResolveDepth) { +#if NETSTANDARD2_1 + // In .NET Standard 2.1 we will try and keep going until we run out of stack space. + if (!RuntimeHelpers.TryEnsureSufficientExecutionStack()) + { + throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorMessages.MaxDepthExceeded, context.Service)); + } +#else + // Pre .NET Standard 2.1 we just end at 50. throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorMessages.MaxDepthExceeded, context.Service)); +#endif } var requestStack = context.Operation.RequestStack; diff --git a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs index f3aa49680..8825ddeae 100644 --- a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs +++ b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs @@ -5,11 +5,19 @@ using Autofac.Core.Diagnostics; using Autofac.Specification.Test.Features.CircularDependency; using Xunit; +using Xunit.Abstractions; namespace Autofac.Specification.Test.Features { public class CircularDependencyTests { + private readonly ITestOutputHelper _output; + + public CircularDependencyTests(ITestOutputHelper output) + { + _output = output; + } + private interface IPlugin { } @@ -94,6 +102,23 @@ public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerR var de = Assert.Throws(() => c.Resolve()); } + [Fact] + public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerResolved_WithTracerAttached() + { + var cb = new ContainerBuilder(); + cb.RegisterType(); + cb.RegisterType().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies); + + var c = cb.Build(); + + c.AttachTrace((req, trace) => + { + _output.WriteLine(trace); + }); + + var de = Assert.Throws(() => c.Resolve()); + } + [Fact] public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDuringConstruction() { From 57904bb978c40df4a7e2c3b5aab285175e0721e0 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Tue, 9 Jun 2020 13:37:35 +0100 Subject: [PATCH 3/5] Minor performance tweaks. --- .../Pipeline/ResolvePipelineBuilder.cs | 6 ++++-- src/Autofac/Core/Resolving/SegmentedStack.cs | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 650e9822a..9daad7996 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -267,6 +267,8 @@ private static IResolvePipeline BuildPipeline(MiddlewareDeclaration? lastDecl) Action Chain(Action next, IResolveMiddleware stage) { + var stagePhase = stage.Phase; + return (ctxt) => { // Optimise the path depending on whether a tracer is attached. @@ -276,7 +278,7 @@ Action Chain(Action next, var succeeded = false; try { - ctxt.PhaseReached = stage.Phase; + ctxt.PhaseReached = stagePhase; stage.Execute(ctxt, next); succeeded = true; } @@ -287,7 +289,7 @@ Action Chain(Action next, } else { - ctxt.PhaseReached = stage.Phase; + ctxt.PhaseReached = stagePhase; stage.Execute(ctxt, next); } }; diff --git a/src/Autofac/Core/Resolving/SegmentedStack.cs b/src/Autofac/Core/Resolving/SegmentedStack.cs index b6e74fed4..0a351fbd2 100644 --- a/src/Autofac/Core/Resolving/SegmentedStack.cs +++ b/src/Autofac/Core/Resolving/SegmentedStack.cs @@ -35,7 +35,7 @@ namespace Autofac.Core.Resolving /// of the stack to be enumerated without including items pushed before the segment. /// /// The item type. - internal class SegmentedStack : IEnumerable + internal sealed class SegmentedStack : IEnumerable where T : class { private T[] _array; @@ -196,29 +196,32 @@ public void Dispose() public bool MoveNext() { - if (_index == -2) + var index = _index; + + if (index == -2) { // Start the enumerator. - _index = _stack._next - 1; + _index = index = _stack._next - 1; - if (_index > _activeSegmentBase) + if (index > _activeSegmentBase) { - _currentElement = _stack._array[_index]; + _currentElement = _stack._array[index]; return true; } return false; } - if (_index == -1) + if (index == -1) { // Finished. return false; } - if (--_index >= _activeSegmentBase) + if (--index >= _activeSegmentBase) { - _currentElement = _stack._array[_index]; + _currentElement = _stack._array[index]; + _index = index; return true; } From a29ef28eb52bef7bc67d7a3461595c388d75813b Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Wed, 10 Jun 2020 07:22:14 +0100 Subject: [PATCH 4/5] Add clear assert that the trace data is populated. --- .../Features/CircularDependencyTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs index 8825ddeae..5bfc8a216 100644 --- a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs +++ b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs @@ -111,12 +111,16 @@ public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerR var c = cb.Build(); + string capturedTrace = null; + c.AttachTrace((req, trace) => { + capturedTrace = trace; _output.WriteLine(trace); }); - var de = Assert.Throws(() => c.Resolve()); + Assert.Throws(() => c.Resolve()); + Assert.NotNull(capturedTrace); } [Fact] From fd8bc4f96f027cb78d6b77670a01f0b67eb42b13 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Wed, 10 Jun 2020 07:22:31 +0100 Subject: [PATCH 5/5] Make ResolveComponent virtual. --- .../Core/Resolving/Pipeline/ResolveRequestContextBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 77e5f1b0f..05378ebe5 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -162,7 +162,7 @@ public void ChangeParameters(IEnumerable newParameters) } /// - public object ResolveComponent(ResolveRequest request) + public virtual object ResolveComponent(ResolveRequest request) { return Operation.GetOrCreateInstance(ActivationScope, request); }