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

Circular Dependency Changes #1148

Merged
merged 5 commits into from Jun 10, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Autofac/Autofac.csproj
Expand Up @@ -188,6 +188,11 @@
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Core\Resolving\SegmentedStackResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>SegmentedStackResources.resx</DependentUpon>
</Compile>
<Compile Update="Core\ServiceResources.Designer.cs">
<DependentUpon>ServiceResources.resx</DependentUpon>
<DesignTime>True</DesignTime>
Expand Down Expand Up @@ -397,6 +402,10 @@
<LastGenOutput>DisposerResources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Core\Resolving\SegmentedStackResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>SegmentedStackResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Core\ServiceResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ServiceResources.Designer.cs</LastGenOutput>
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -67,7 +68,16 @@ public void Execute(ResolveRequestContextBase context, Action<ResolveRequestCont

if (activationDepth > _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;
Expand All @@ -78,7 +88,6 @@ public void Execute(ResolveRequestContextBase context, Action<ResolveRequestCont
{
var registration = context.Registration;

// Only check the stack for shared components.
foreach (var requestEntry in requestStack)
{
if (requestEntry.Registration == registration)
Expand Down
6 changes: 4 additions & 2 deletions src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs
Expand Up @@ -267,6 +267,8 @@ private static IResolvePipeline BuildPipeline(MiddlewareDeclaration? lastDecl)

Action<ResolveRequestContextBase> Chain(Action<ResolveRequestContextBase> next, IResolveMiddleware stage)
{
var stagePhase = stage.Phase;

return (ctxt) =>
{
// Optimise the path depending on whether a tracer is attached.
Expand All @@ -276,7 +278,7 @@ Action<ResolveRequestContextBase> Chain(Action<ResolveRequestContextBase> next,
var succeeded = false;
try
{
ctxt.PhaseReached = stage.Phase;
ctxt.PhaseReached = stagePhase;
stage.Execute(ctxt, next);
succeeded = true;
}
Expand All @@ -287,7 +289,7 @@ Action<ResolveRequestContextBase> Chain(Action<ResolveRequestContextBase> next,
}
else
{
ctxt.PhaseReached = stage.Phase;
ctxt.PhaseReached = stagePhase;
stage.Execute(ctxt, next);
}
};
Expand Down
15 changes: 0 additions & 15 deletions src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs
Expand Up @@ -48,21 +48,6 @@ internal sealed class ResolveRequestContext : ResolveRequestContextBase
{
}

/// <inheritdoc/>
public override object ResolveComponent(ResolveRequest request)
{
return Operation.GetOrCreateInstance(ActivationScope, request);
}

/// <inheritdoc/>
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);
}

/// <summary>
/// Completes the request context; invokes any event handlers.
/// </summary>
Expand Down
19 changes: 4 additions & 15 deletions src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs
Expand Up @@ -162,21 +162,10 @@ public void ChangeParameters(IEnumerable<Parameter> newParameters)
}

/// <inheritdoc />
public abstract object ResolveComponent(ResolveRequest request);

/// <summary>
/// Resolve an instance of the provided registration within the context, but isolated inside a new
/// <see cref="ResolveOperationBase"/>.
/// This method should only be used instead of <see cref="IComponentContext.ResolveComponent(ResolveRequest)"/>
/// if you need to resolve a component with a completely separate operation and circular dependency verification stack.
/// </summary>
/// <param name="request">The resolve request.</param>
/// <returns>
/// The component instance.
/// </returns>
/// <exception cref="ComponentNotRegisteredException"/>
/// <exception cref="DependencyResolutionException"/>
public abstract object ResolveComponentWithNewOperation(ResolveRequest request);
public virtual object ResolveComponent(ResolveRequest request)
{
return Operation.GetOrCreateInstance(ActivationScope, request);
}

/// <summary>
/// Complete the request, raising any appropriate events.
Expand Down
12 changes: 11 additions & 1 deletion src/Autofac/Core/Resolving/ResolveOperationBase.cs
Expand Up @@ -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 <see cref="CircularDependencyDetectorMiddleware"/>,
/// hence it's internal.
/// </remarks>
internal Stack<ResolveRequestContextBase> RequestStack { get; } = new Stack<ResolveRequestContextBase>();
internal SegmentedStack<ResolveRequestContextBase> RequestStack { get; } = new SegmentedStack<ResolveRequestContextBase>();

/// <summary>
/// Enter a new dependency chain block where subsequent requests inside the operation are allowed to repeat
/// registrations from before the block.
/// </summary>
/// <returns>A disposable that should be disposed to exit the block.</returns>
public IDisposable EnterNewDependencyDetectionBlock()
{
return RequestStack.EnterSegment();
tillig marked this conversation as resolved.
Show resolved Hide resolved
}

/// <inheritdoc />
public event EventHandler<ResolveRequestBeginningEventArgs>? ResolveRequestBeginning;
Expand Down
239 changes: 239 additions & 0 deletions src/Autofac/Core/Resolving/SegmentedStack.cs
@@ -0,0 +1,239 @@
// 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
{
/// <summary>
/// Implements a segmented stack of items, which functions like a regular <see cref="Stack{T}"/>, but allows segments
/// of the stack to be enumerated without including items pushed before the segment.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
internal sealed class SegmentedStack<T> : IEnumerable<T>
where T : class
{
private T[] _array;
private int _next;
private int _activeSegmentBase;

private const int InitialCapacity = 16;

/// <summary>
/// Initializes a new instance of the <see cref="SegmentedStack{T}"/> class.
/// </summary>
public SegmentedStack()
{
_array = new T[InitialCapacity];
}

/// <summary>
/// Push an item onto the stack.
/// </summary>
/// <param name="item">The item.</param>
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;
tillig marked this conversation as resolved.
Show resolved Hide resolved
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++;
}

/// <summary>
/// Pop the item at the top of the stack (and return it).
/// </summary>
/// <returns>The item that has just been popped.</returns>
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;
}

/// <summary>
/// Gets the count of the items in the active segment.
/// </summary>
public int Count => _next - _activeSegmentBase;

/// <summary>
/// Enter a new segment. When this method returns <see cref="Count"/> will be zero, and the stack will appear empty.
/// </summary>
/// <returns>An <see cref="IDisposable"/> that will return the stack to the previously active segment when disposed.</returns>
public IDisposable EnterSegment()
{
var reset = new StackSegment(this, _activeSegmentBase);

_activeSegmentBase = _next;

return reset;
}

/// <inheritdoc/>
public IEnumerator<T> GetEnumerator()
{
return new Enumerator(this);
}

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}

private struct StackSegment : IDisposable
{
private readonly SegmentedStack<T> _stack;
private readonly int _resetPosition;

public StackSegment(SegmentedStack<T> 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<T>, IEnumerator
{
private readonly SegmentedStack<T> _stack;
private readonly int _activeSegmentBase;
private int _index;
private T? _currentElement;

internal Enumerator(SegmentedStack<T> 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()
{
var index = _index;

if (index == -2)
{
// Start the enumerator.
_index = 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];
_index = index;
return true;
}

_currentElement = null!;
return false;
}

public void Reset()
{
_index = -2;
_currentElement = null!;
}
}
}
}